File indexing completed on 2025-01-19 04:52:01
0001 /* 0002 Copyright (c) 2018 Christian Mollekopf <mollekopf@kolabsys.com> 0003 0004 This library is free software; you can redistribute it and/or modify it 0005 under the terms of the GNU Library General Public License as published by 0006 the Free Software Foundation; either version 2 of the License, or (at your 0007 option) any later version. 0008 0009 This library is distributed in the hope that it will be useful, but WITHOUT 0010 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 0011 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 0012 License for more details. 0013 0014 You should have received a copy of the GNU Library General Public License 0015 along with this library; see the file COPYING.LIB. If not, write to the 0016 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 0017 02110-1301, USA. 0018 */ 0019 0020 #include "todomodel.h" 0021 0022 #include <sink/log.h> 0023 #include <sink/query.h> 0024 #include <sink/store.h> 0025 #include <sink/applicationdomaintype.h> 0026 0027 #include <QMetaEnum> 0028 0029 #include <KCalendarCore/ICalFormat> 0030 0031 #include <entitycache.h> 0032 0033 using namespace Sink; 0034 0035 TodoSourceModel::TodoSourceModel(QObject *parent) 0036 : QAbstractItemModel(parent), 0037 mCalendarCache{EntityCache<ApplicationDomain::Calendar>::Ptr::create(QByteArrayList{{ApplicationDomain::Calendar::Color::name}, {ApplicationDomain::Calendar::Name::name}})}, 0038 mUpdateFromSourceDebouncer{100, [this] { this->updateFromSource(); }, this} 0039 { 0040 } 0041 0042 static QList<QByteArray> toList(const QVariant &variant) { 0043 if (variant.type() == static_cast<QVariant::Type>(QMetaType::QVariantList)) { 0044 QList<QByteArray> list; 0045 for (const auto &v : variant.toList()) { 0046 list.append(v.toByteArray()); 0047 } 0048 return list; 0049 } 0050 return variant.value<QSet<QByteArray>>().values(); 0051 } 0052 0053 void TodoSourceModel::setFilter(const QVariantMap &filter) 0054 { 0055 const auto account = filter.value("account").toByteArray(); 0056 const auto calendarFilter = toList(filter.value("calendars")); 0057 using namespace Sink::ApplicationDomain; 0058 if (calendarFilter.isEmpty()) { 0059 refreshView(); 0060 return; 0061 } 0062 0063 Sink::Query query; 0064 if (!account.isEmpty()) { 0065 query.resourceFilter<SinkResource::Account>(account); 0066 } 0067 query.filter<Todo::Calendar>(Query::Comparator(QVariant::fromValue(calendarFilter), Query::Comparator::In)); 0068 query.filter<Todo::ParentUid>(filter.value("parentUid")); 0069 0070 if (filter.value("doing").toBool()) { 0071 query.filter<Todo::Status>("INPROCESS"); 0072 } else if (filter.value("inbox").toBool()) { 0073 query.filter<Todo::Status>(Query::Comparator{QVariant::fromValue(QByteArrayList{"NEEDSACTION", ""}), Query::Comparator::In}); 0074 } 0075 0076 query.setFlags(Sink::Query::LiveQuery); 0077 query.request<Todo::Summary>(); 0078 query.request<Todo::Description>(); 0079 query.request<Todo::StartDate>(); 0080 query.request<Todo::DueDate>(); 0081 query.request<Todo::CompletedDate>(); 0082 query.request<Todo::Status>(); 0083 query.request<Todo::Calendar>(); 0084 query.request<Todo::Ical>(); 0085 query.request<Todo::Priority>(); 0086 0087 mSourceModel = Store::loadModel<ApplicationDomain::Todo>(query); 0088 0089 QObject::connect(mSourceModel.data(), &QAbstractItemModel::dataChanged, this, &TodoSourceModel::refreshView); 0090 QObject::connect(mSourceModel.data(), &QAbstractItemModel::layoutChanged, this, &TodoSourceModel::refreshView); 0091 QObject::connect(mSourceModel.data(), &QAbstractItemModel::modelReset, this, &TodoSourceModel::refreshView); 0092 QObject::connect(mSourceModel.data(), &QAbstractItemModel::rowsInserted, this, &TodoSourceModel::refreshView); 0093 QObject::connect(mSourceModel.data(), &QAbstractItemModel::rowsMoved, this, &TodoSourceModel::refreshView); 0094 QObject::connect(mSourceModel.data(), &QAbstractItemModel::rowsRemoved, this, &TodoSourceModel::refreshView); 0095 0096 refreshView(); 0097 } 0098 0099 void TodoSourceModel::refreshView() 0100 { 0101 mUpdateFromSourceDebouncer.trigger(); 0102 } 0103 0104 void TodoSourceModel::updateFromSource() 0105 { 0106 beginResetModel(); 0107 0108 mTodos.clear(); 0109 0110 if (mSourceModel) { 0111 for (int i = 0; i < mSourceModel->rowCount(); ++i) { 0112 auto todo = mSourceModel->index(i, 0).data(Sink::Store::DomainObjectRole).value<ApplicationDomain::Todo::Ptr>(); 0113 //Parse the todo 0114 if(auto icalTodo = KCalendarCore::ICalFormat().readIncidence(todo->getIcal()).dynamicCast<KCalendarCore::Todo>()) { 0115 mTodos.append({icalTodo->dtStart(), icalTodo->dtDue(), icalTodo->completed(), icalTodo, getColor(todo->getCalendar()), getCalendarName(todo->getCalendar()), todo->getStatus(), todo, todo->getPriority()}); 0116 } else { 0117 SinkWarning() << "Invalid ICal to process, ignoring..."; 0118 } 0119 } 0120 } 0121 0122 endResetModel(); 0123 } 0124 0125 QModelIndex TodoSourceModel::index(int row, int column, const QModelIndex &parent) const 0126 { 0127 if (!hasIndex(row, column, parent)) { 0128 return {}; 0129 } 0130 0131 if (!parent.isValid()) { 0132 return createIndex(row, column); 0133 } 0134 return {}; 0135 } 0136 0137 QModelIndex TodoSourceModel::parent(const QModelIndex &) const 0138 { 0139 return {}; 0140 } 0141 0142 int TodoSourceModel::rowCount(const QModelIndex &parent) const 0143 { 0144 if (!parent.isValid()) { 0145 return mTodos.size(); 0146 } 0147 return 0; 0148 } 0149 0150 int TodoSourceModel::columnCount(const QModelIndex &) const 0151 { 0152 return 1; 0153 } 0154 0155 QByteArray TodoSourceModel::getColor(const QByteArray &calendar) const 0156 { 0157 const auto color = mCalendarCache->getProperty(calendar, {ApplicationDomain::Calendar::Color::name}).toByteArray(); 0158 if (color.isEmpty()) { 0159 qWarning() << "Failed to get color for calendar " << calendar; 0160 } 0161 return color; 0162 } 0163 0164 QString TodoSourceModel::getCalendarName(const QByteArray &calendar) const 0165 { 0166 const auto name = mCalendarCache->getProperty(calendar, {ApplicationDomain::Calendar::Name::name}).toString(); 0167 if (name.isEmpty()) { 0168 qWarning() << "Failed to get name for calendar " << calendar; 0169 } 0170 return name; 0171 } 0172 0173 QVariant TodoSourceModel::data(const QModelIndex &idx, int role) const 0174 { 0175 if (!hasIndex(idx.row(), idx.column())) { 0176 return {}; 0177 } 0178 auto todo = mTodos.at(idx.row()); 0179 auto icalTodo = todo.incidence; 0180 switch (role) { 0181 case Summary: 0182 return icalTodo->summary(); 0183 case Description: 0184 return icalTodo->description(); 0185 case StartDate: 0186 return todo.start; 0187 case DueDate: 0188 return todo.due; 0189 case Date: 0190 if (todo.status == "COMPLETED") { 0191 return todo.completed; 0192 } 0193 if (todo.due.isValid()) { 0194 return todo.due; 0195 } 0196 return todo.start; 0197 case CompletedDate: 0198 return todo.completed; 0199 case Color: 0200 return todo.color; 0201 case Calendar: 0202 return todo.calendarName; 0203 case Status: 0204 return todo.status; 0205 case Complete: 0206 return todo.status == "COMPLETED"; 0207 case Doing: 0208 return todo.status == "INPROCESS"; 0209 case Important: 0210 return todo.priority == 1; 0211 case Relevance: { 0212 int score = 100; 0213 if (todo.status == "COMPLETED") { 0214 score -= 100; 0215 } else { 0216 //TODO add if from current account 0217 //Due 0218 if (todo.due.isValid() && (!todo.start.isValid() || todo.start > QDateTime::currentDateTime())) { 0219 score += 10; 0220 //Overdue 0221 if (todo.due >= QDateTime::currentDateTime()) { 0222 score += 50; 0223 } 0224 } 0225 if (todo.priority == 1) { 0226 score += 50; 0227 } 0228 if (todo.status == "INPROCESS") { 0229 score += 100; 0230 } 0231 } 0232 return score; 0233 } 0234 case Todo: 0235 return QVariant::fromValue(todo.domainObject); 0236 case SortDate: 0237 if (todo.due.isValid()) { 0238 return todo.due; 0239 } 0240 if (todo.start.isValid()) { 0241 return todo.start; 0242 } 0243 return icalTodo->lastModified(); 0244 default: 0245 SinkWarning() << "Unknown role for todo:" << QMetaEnum::fromType<Roles>().valueToKey(role) << role; 0246 return {}; 0247 } 0248 } 0249 0250 QHash<int, QByteArray> TodoSourceModel::roleNames() const 0251 { 0252 return { 0253 {Summary, "summary"}, 0254 {Description, "description"}, 0255 {StartDate, "startDate"}, 0256 {DueDate, "dueDate"}, 0257 {CompletedDate, "completedDate"}, 0258 {Date, "date"}, 0259 {Color, "color"}, 0260 {Calendar, "calendar"}, 0261 {Status, "status"}, 0262 {Complete, "complete"}, 0263 {Doing, "doing"}, 0264 {Important, "important"}, 0265 {Todo, "domainObject"} 0266 }; 0267 } 0268 0269 0270 0271 TodoModel::TodoModel(QObject *parent) 0272 : QSortFilterProxyModel(parent) 0273 { 0274 setDynamicSortFilter(true); 0275 sort(0, Qt::DescendingOrder); 0276 setFilterCaseSensitivity(Qt::CaseInsensitive); 0277 setFilterRole(TodoSourceModel::Summary); 0278 setSourceModel(new TodoSourceModel(this)); 0279 } 0280 0281 QHash<int, QByteArray> TodoModel::roleNames() const 0282 { 0283 return sourceModel()->roleNames(); 0284 } 0285 0286 void TodoModel::setFilter(const QVariantMap &f) 0287 { 0288 static_cast<TodoSourceModel*>(sourceModel())->setFilter(f); 0289 mFilterNotStarted = f.value("doing").toBool(); 0290 const auto filterString = f.value("string").toString(); 0291 setFilterFixedString(filterString); 0292 } 0293 0294 bool TodoModel::lessThan(const QModelIndex &left, const QModelIndex &right) const 0295 { 0296 const auto leftScore = left.data(TodoSourceModel::Relevance).toInt(); 0297 const auto rightScore = right.data(TodoSourceModel::Relevance).toInt(); 0298 if (leftScore == rightScore) { 0299 const auto leftDate = left.data(TodoSourceModel::SortDate).toDateTime(); 0300 const auto rightDate = right.data(TodoSourceModel::SortDate).toDateTime(); 0301 if (leftDate == rightDate) { 0302 return left.data(TodoSourceModel::Summary).toString() < right.data(TodoSourceModel::Summary).toString(); 0303 } 0304 return leftDate < rightDate; 0305 } 0306 return leftScore < rightScore; 0307 } 0308 0309 bool TodoModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const 0310 { 0311 if (mFilterNotStarted) { 0312 const auto startDate = sourceModel()->index(sourceRow, 0, sourceParent).data(TodoSourceModel::StartDate).value<QDateTime>(); 0313 if (startDate.isValid() && startDate < QDateTime::currentDateTime()) { 0314 return false; 0315 } 0316 } 0317 return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); 0318 }