File indexing completed on 2024-05-12 05:10:50
0001 /* 0002 SPDX-FileCopyrightText: 2008 Thomas Thrainer <tom_t@gmx.at> 0003 SPDX-FileCopyrightText: 2012 Sérgio Martins <iamsergio@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0006 */ 0007 0008 #include "todomodel.h" 0009 0010 #include <KCalendarCore/Attachment> 0011 #include <KCalendarCore/Event> 0012 #include <KEmailAddress> 0013 #include <KLocalizedString> 0014 0015 #include <Akonadi/CalendarUtils> 0016 #include <Akonadi/IncidenceTreeModel> 0017 #include <Akonadi/TagCache> 0018 0019 #include <KCalUtils/DndFactory> 0020 #include <KCalUtils/ICalDrag> 0021 #include <KCalUtils/IncidenceFormatter> 0022 #include <KCalUtils/VCalDrag> 0023 0024 #include "akonadicalendar_debug.h" 0025 0026 #include <QIcon> 0027 #include <QMimeData> 0028 0029 0030 namespace Akonadi 0031 { 0032 class TodoModelPrivate 0033 { 0034 public: 0035 TodoModelPrivate(TodoModel *qq); 0036 0037 Akonadi::EntityTreeModel *etm() 0038 { 0039 if (!mEtm) { 0040 auto *model = q->sourceModel(); 0041 while (model) { 0042 if (auto *proxy = qobject_cast<QAbstractProxyModel *>(model); proxy) { 0043 model = proxy->sourceModel(); 0044 } else if (auto *etm = qobject_cast<Akonadi::EntityTreeModel *>(model); etm) { 0045 mEtm = etm; 0046 break; 0047 } else { 0048 return nullptr; 0049 } 0050 } 0051 } 0052 0053 return mEtm; 0054 } 0055 0056 // TODO: O(N) complexity, see if the profiler complains about this 0057 Akonadi::Item findItemByUid(const QString &uid, const QModelIndex &parent) const; 0058 0059 Akonadi::IncidenceChanger *m_changer = nullptr; 0060 0061 void onDataChanged(const QModelIndex &begin, const QModelIndex &end); 0062 0063 TodoModel *const q; 0064 Akonadi::EntityTreeModel *mEtm = nullptr; 0065 }; 0066 } 0067 0068 using namespace Akonadi; 0069 0070 TodoModelPrivate::TodoModelPrivate(TodoModel *qq) 0071 : q(qq) 0072 { 0073 } 0074 0075 Akonadi::Item TodoModelPrivate::findItemByUid(const QString &uid, const QModelIndex &parent) const 0076 { 0077 Q_ASSERT(!uid.isEmpty()); 0078 auto treeModel = qobject_cast<Akonadi::IncidenceTreeModel *>(q->sourceModel()); 0079 if (treeModel) { // O(1) Shortcut 0080 return treeModel->item(uid); 0081 } 0082 0083 Akonadi::Item item; 0084 const int count = q->rowCount(parent); 0085 for (int i = 0; i < count; ++i) { 0086 const QModelIndex currentIndex = q->index(i, 0, parent); 0087 Q_ASSERT(currentIndex.isValid()); 0088 item = q->data(currentIndex, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); 0089 if (item.isValid()) { 0090 return item; 0091 } else { 0092 item = findItemByUid(uid, currentIndex); 0093 if (item.isValid()) { 0094 return item; 0095 } 0096 } 0097 } 0098 0099 return item; 0100 } 0101 0102 void TodoModelPrivate::onDataChanged(const QModelIndex &begin, const QModelIndex &end) 0103 { 0104 Q_ASSERT(begin.isValid()); 0105 Q_ASSERT(end.isValid()); 0106 const QModelIndex proxyBegin = q->mapFromSource(begin); 0107 Q_ASSERT(proxyBegin.column() == 0); 0108 const QModelIndex proxyEnd = q->mapFromSource(end); 0109 Q_EMIT q->dataChanged(proxyBegin, proxyEnd.sibling(proxyEnd.row(), TodoModel::ColumnCount - 1)); 0110 } 0111 0112 TodoModel::TodoModel(QObject *parent) 0113 : KExtraColumnsProxyModel(parent) 0114 , d(new TodoModelPrivate(this)) 0115 { 0116 setObjectName(QLatin1StringView("TodoModel")); 0117 } 0118 0119 TodoModel::~TodoModel() = default; 0120 0121 QVariant TodoModel::data(const QModelIndex &index, int role) const 0122 { 0123 Q_ASSERT(index.isValid()); 0124 if (!index.isValid()) { 0125 return {}; 0126 } 0127 0128 const QModelIndex sourceIndex = mapToSource(index.sibling(index.row(), 0)); 0129 if (!sourceIndex.isValid()) { 0130 return {}; 0131 } 0132 Q_ASSERT(sourceIndex.isValid()); 0133 const auto item = sourceIndex.data(Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); 0134 if (!item.isValid()) { 0135 qCWarning(AKONADICALENDAR_LOG) << "Invalid index: " << sourceIndex; 0136 // Q_ASSERT( false ); 0137 return {}; 0138 } 0139 const KCalendarCore::Todo::Ptr todo = Akonadi::CalendarUtils::todo(item); 0140 if (!todo) { 0141 qCCritical(AKONADICALENDAR_LOG) << "item.hasPayload()" << item.hasPayload(); 0142 if (item.hasPayload<KCalendarCore::Incidence::Ptr>()) { 0143 auto incidence = item.payload<KCalendarCore::Incidence::Ptr>(); 0144 if (incidence) { 0145 qCCritical(AKONADICALENDAR_LOG) << "It's actually " << incidence->type(); 0146 } 0147 } 0148 0149 Q_ASSERT(!"There's no to-do."); 0150 return {}; 0151 } 0152 0153 switch (role) { 0154 case SummaryRole: 0155 return todo->summary(); 0156 case RecurRole: 0157 if (todo->recurs()) { 0158 if (todo->hasRecurrenceId()) { 0159 return i18nc("yes, an exception to a recurring to-do", "Exception"); 0160 } else { 0161 return i18nc("yes, recurring to-do", "Yes"); 0162 } 0163 } else { 0164 return i18nc("no, not a recurring to-do", "No"); 0165 } 0166 case PriorityRole: 0167 if (todo->priority() == 0) { 0168 return QStringLiteral("--"); 0169 } 0170 return todo->priority(); 0171 case PercentRole: 0172 return todo->percentComplete(); 0173 case StartDateRole: 0174 return todo->hasStartDate() ? QLocale().toString(todo->dtStart().toLocalTime().date(), QLocale::ShortFormat) : QString(); 0175 case DueDateRole: 0176 return todo->hasDueDate() ? QLocale().toString(todo->dtDue().toLocalTime().date(), QLocale::ShortFormat) : QString(); 0177 case CategoriesRole: 0178 return todo->categories().join(i18nc("delimiter for joining category/tag names", ",")); 0179 case DescriptionRole: 0180 return todo->description(); 0181 case CalendarRole: 0182 return Akonadi::CalendarUtils::displayName(d->etm(), item.parentCollection()); 0183 default: 0184 break; // column based model handling 0185 } 0186 0187 if (role == Qt::DisplayRole) { 0188 switch (index.column()) { 0189 case SummaryColumn: 0190 return QVariant(todo->summary()); 0191 case RecurColumn: 0192 if (todo->recurs()) { 0193 if (todo->hasRecurrenceId()) { 0194 return i18nc("yes, an exception to a recurring to-do", "Exception"); 0195 } else { 0196 return i18nc("yes, recurring to-do", "Yes"); 0197 } 0198 } else { 0199 return i18nc("no, not a recurring to-do", "No"); 0200 } 0201 case PriorityColumn: 0202 if (todo->priority() == 0) { 0203 return QVariant(QStringLiteral("--")); 0204 } 0205 return {todo->priority()}; 0206 case PercentColumn: 0207 return {todo->percentComplete()}; 0208 case StartDateColumn: 0209 return todo->hasStartDate() ? QLocale().toString(todo->dtStart().toLocalTime().date(), QLocale::ShortFormat) : QVariant(QString()); 0210 case DueDateColumn: 0211 return todo->hasDueDate() ? QLocale().toString(todo->dtDue().toLocalTime().date(), QLocale::ShortFormat) : QVariant(QString()); 0212 case CompletedDateColumn: 0213 return todo->hasCompletedDate() ? QLocale().toString(todo->completed().toLocalTime().date(), QLocale::ShortFormat) : QVariant(QString()); 0214 case CategoriesColumn: { 0215 QString categories = todo->categories().join(i18nc("delimiter for joining category/tag names", ",")); 0216 return QVariant(categories); 0217 } 0218 case DescriptionColumn: 0219 return QVariant(todo->description()); 0220 case CalendarColumn: 0221 return QVariant(Akonadi::CalendarUtils::displayName(d->etm(), item.parentCollection())); 0222 } 0223 return {}; 0224 } 0225 0226 if (role == Qt::EditRole) { 0227 switch (index.column()) { 0228 case SummaryColumn: 0229 return QVariant(todo->summary()); 0230 case RecurColumn: 0231 return {todo->recurs()}; 0232 case PriorityColumn: 0233 return {todo->priority()}; 0234 case PercentColumn: 0235 return {todo->percentComplete()}; 0236 case StartDateColumn: 0237 return QVariant(todo->dtStart().date()); 0238 case DueDateColumn: 0239 return QVariant(todo->dtDue().date()); 0240 case CompletedDateColumn: 0241 return QVariant(todo->completed().date()); 0242 case CategoriesColumn: 0243 return QVariant(todo->categories()); 0244 case DescriptionColumn: 0245 return QVariant(todo->description()); 0246 case CalendarColumn: 0247 return QVariant(Akonadi::CalendarUtils::displayName(d->etm(), item.parentCollection())); 0248 } 0249 return {}; 0250 } 0251 0252 // indicate if a row is checked (=completed) only in the first column 0253 if (role == Qt::CheckStateRole && index.column() == 0) { 0254 if (hasChildren(index) && !index.parent().isValid()) { 0255 return {}; 0256 } 0257 0258 if (todo->isCompleted()) { 0259 return {Qt::Checked}; 0260 } else { 0261 return {Qt::Unchecked}; 0262 } 0263 } 0264 0265 // icon for recurring todos 0266 // It's in the summary column so you don't accidentally click 0267 // the checkbox ( which increments the next occurrence date ). 0268 // category colour 0269 if (role == Qt::DecorationRole && index.column() == SummaryColumn) { 0270 if (todo->recurs()) { 0271 return QVariant(QIcon::fromTheme(QStringLiteral("task-recurring"))); 0272 } 0273 const QStringList categories = todo->categories(); 0274 return categories.isEmpty() ? QVariant() : QVariant(Akonadi::TagCache::instance()->tagColor(categories.first())); 0275 } else if (role == Qt::DecorationRole) { 0276 return {}; 0277 } 0278 0279 if (role == TodoRole) { 0280 return QVariant::fromValue(item); 0281 } 0282 0283 if (role == TodoPtrRole) { 0284 return QVariant::fromValue(todo); 0285 } 0286 0287 if (role == IsRichTextRole) { 0288 if (index.column() == SummaryColumn) { 0289 return {todo->summaryIsRich()}; 0290 } else if (index.column() == DescriptionColumn) { 0291 return {todo->descriptionIsRich()}; 0292 } else { 0293 return {}; 0294 } 0295 } 0296 0297 if (role == Qt::TextAlignmentRole) { 0298 switch (index.column()) { 0299 // If you change this, change headerData() too. 0300 case RecurColumn: 0301 case PriorityColumn: 0302 case PercentColumn: 0303 case StartDateColumn: 0304 case DueDateColumn: 0305 case CategoriesColumn: 0306 case CalendarColumn: 0307 return {Qt::AlignHCenter | Qt::AlignVCenter}; 0308 } 0309 return {Qt::AlignLeft | Qt::AlignVCenter}; 0310 } 0311 0312 if (sourceModel()) { 0313 return sourceModel()->data(mapToSource(index.sibling(index.row(), 0)), role); 0314 } 0315 0316 return {}; 0317 } 0318 0319 QVariant TodoModel::extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role) const 0320 { 0321 // we customize all columns, not just the extra ones, and thus do all that in ::data() 0322 Q_UNUSED(parent); 0323 Q_UNUSED(row); 0324 Q_UNUSED(extraColumn); 0325 Q_UNUSED(role); 0326 return {}; 0327 } 0328 0329 bool TodoModel::setData(const QModelIndex &index, const QVariant &value, int role) 0330 { 0331 Q_ASSERT(index.isValid()); 0332 if (!d->m_changer) { 0333 return false; 0334 } 0335 const QVariant oldValue = data(index, role); 0336 0337 if (oldValue == value) { 0338 // Nothing changed, the user used one of the QStyledDelegate's editors but seted the old value 0339 // Lets just skip this then and avoid a roundtrip to akonadi, and avoid sending invitations 0340 return true; 0341 } 0342 0343 const auto item = data(index, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); 0344 const KCalendarCore::Todo::Ptr todo = Akonadi::CalendarUtils::todo(item); 0345 0346 if (!item.isValid() || !todo) { 0347 qCWarning(AKONADICALENDAR_LOG) << "TodoModel::setData() called, bug item is invalid or doesn't have payload"; 0348 Q_ASSERT(false); 0349 return false; 0350 } 0351 0352 const auto parentCol = Akonadi::EntityTreeModel::updatedCollection(d->etm(), item.parentCollection()); 0353 if (parentCol.rights() & Akonadi::Collection::CanChangeItem) { 0354 KCalendarCore::Todo::Ptr oldTodo(todo->clone()); 0355 if (role == Qt::CheckStateRole && index.column() == 0) { 0356 const bool checked = static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked; 0357 if (checked) { 0358 todo->setCompleted(QDateTime::currentDateTimeUtc()); // Because it calls Todo::recurTodo() 0359 } else { 0360 todo->setCompleted(false); 0361 } 0362 } 0363 0364 if (role == Qt::EditRole) { 0365 switch (index.column()) { 0366 case SummaryColumn: 0367 if (!value.toString().isEmpty()) { 0368 todo->setSummary(value.toString()); 0369 } 0370 break; 0371 case PriorityColumn: 0372 todo->setPriority(value.toInt()); 0373 break; 0374 case PercentColumn: 0375 todo->setPercentComplete(value.toInt()); 0376 break; 0377 case StartDateColumn: { 0378 QDateTime tmp = todo->dtStart(); 0379 tmp.setDate(value.toDate()); 0380 todo->setDtStart(tmp); 0381 break; 0382 } 0383 case DueDateColumn: { 0384 QDateTime tmp = todo->dtDue(); 0385 tmp.setDate(value.toDate()); 0386 todo->setDtDue(tmp); 0387 break; 0388 } 0389 case CategoriesColumn: 0390 todo->setCategories(value.toStringList()); 0391 break; 0392 case DescriptionColumn: 0393 todo->setDescription(value.toString()); 0394 break; 0395 } 0396 } 0397 0398 if (!todo->dirtyFields().isEmpty()) { 0399 d->m_changer->modifyIncidence(item, oldTodo); 0400 // modifyIncidence will eventually call the view's 0401 // changeIncidenceDisplay method, which in turn 0402 // will call processChange. processChange will then emit 0403 // dataChanged to the view, so we don't have to 0404 // do it here 0405 } 0406 0407 return true; 0408 } else { 0409 if (!(role == Qt::CheckStateRole && index.column() == 0)) { 0410 // KOHelper::showSaveIncidenceErrorMsg( 0, todo ); //TODO pass parent 0411 qCCritical(AKONADICALENDAR_LOG) << "Unable to modify incidence"; 0412 } 0413 return false; 0414 } 0415 } 0416 0417 int TodoModel::columnCount(const QModelIndex &) const 0418 { 0419 return ColumnCount; 0420 } 0421 0422 void TodoModel::setSourceModel(QAbstractItemModel *model) 0423 { 0424 if (model == sourceModel()) { 0425 return; 0426 } 0427 0428 beginResetModel(); 0429 0430 if (sourceModel()) { 0431 disconnect(sourceModel(), &QAbstractItemModel::dataChanged, this, nullptr); 0432 } 0433 0434 KExtraColumnsProxyModel::setSourceModel(model); 0435 0436 if (sourceModel()) { 0437 connect(sourceModel(), &QAbstractItemModel::dataChanged, this, [this](const auto &begin, const auto &end) { 0438 d->onDataChanged(begin, end); 0439 }); 0440 } 0441 0442 endResetModel(); 0443 } 0444 0445 void TodoModel::setIncidenceChanger(Akonadi::IncidenceChanger *changer) 0446 { 0447 d->m_changer = changer; 0448 } 0449 0450 QVariant TodoModel::headerData(int column, Qt::Orientation orientation, int role) const 0451 { 0452 if (orientation != Qt::Horizontal) { 0453 return {}; 0454 } 0455 0456 if (role == Qt::DisplayRole) { 0457 switch (column) { 0458 case SummaryColumn: 0459 return QVariant(i18n("Summary")); 0460 case RecurColumn: 0461 return QVariant(i18n("Recurs")); 0462 case PriorityColumn: 0463 return QVariant(i18n("Priority")); 0464 case PercentColumn: 0465 return QVariant(i18nc("@title:column percent complete", "Complete")); 0466 case StartDateColumn: 0467 return QVariant(i18n("Start Date")); 0468 case DueDateColumn: 0469 return QVariant(i18n("Due Date")); 0470 case CompletedDateColumn: 0471 return QVariant(i18nc("@title:column date completed", "Completed")); 0472 case CategoriesColumn: 0473 return QVariant(i18n("Tags")); 0474 case DescriptionColumn: 0475 return QVariant(i18n("Description")); 0476 case CalendarColumn: 0477 return QVariant(i18n("Calendar")); 0478 } 0479 } 0480 0481 if (role == Qt::TextAlignmentRole) { 0482 switch (column) { 0483 // If you change this, change data() too. 0484 case RecurColumn: 0485 case PriorityColumn: 0486 case PercentColumn: 0487 case StartDateColumn: 0488 case DueDateColumn: 0489 case CategoriesColumn: 0490 case CalendarColumn: 0491 return {Qt::AlignHCenter}; 0492 } 0493 return {}; 0494 } 0495 return {}; 0496 } 0497 0498 void TodoModel::setCalendar(const Akonadi::ETMCalendar::Ptr &calendar) 0499 { 0500 Q_UNUSED(calendar); 0501 // Deprecated, no longer does anything 0502 } 0503 0504 Qt::DropActions TodoModel::supportedDropActions() const 0505 { 0506 // Qt::CopyAction not supported yet 0507 return Qt::MoveAction; 0508 } 0509 0510 QStringList TodoModel::mimeTypes() const 0511 { 0512 static QStringList list; 0513 if (list.isEmpty()) { 0514 list << KCalUtils::ICalDrag::mimeType() << KCalUtils::VCalDrag::mimeType(); 0515 } 0516 return list; 0517 } 0518 0519 QMimeData *TodoModel::mimeData(const QModelIndexList &indexes) const 0520 { 0521 Akonadi::Item::List items; 0522 for (const QModelIndex &index : indexes) { 0523 const auto item = this->data(index, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); 0524 if (item.isValid() && !items.contains(item)) { 0525 items.push_back(item); 0526 } 0527 } 0528 return Akonadi::CalendarUtils::createMimeData(items); 0529 } 0530 0531 bool TodoModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) 0532 { 0533 Q_UNUSED(row) 0534 Q_UNUSED(column) 0535 0536 if (action != Qt::MoveAction) { 0537 qCWarning(AKONADICALENDAR_LOG) << "No action other than MoveAction currently supported!"; // TODO 0538 return false; 0539 } 0540 0541 if (d->m_changer && (KCalUtils::ICalDrag::canDecode(data) || KCalUtils::VCalDrag::canDecode(data))) { 0542 // DndFactory only needs a valid calendar for drag event, not for drop event. 0543 KCalUtils::DndFactory dndFactory(KCalendarCore::Calendar::Ptr{}); 0544 KCalendarCore::Todo::Ptr t = dndFactory.createDropTodo(data); 0545 KCalendarCore::Event::Ptr e = dndFactory.createDropEvent(data); 0546 0547 if (t) { 0548 // we don't want to change the created todo, but the one which is already 0549 // stored in our calendar / tree 0550 const Akonadi::Item item = d->findItemByUid(t->uid(), QModelIndex()); 0551 KCalendarCore::Todo::Ptr todo = Akonadi::CalendarUtils::todo(item); 0552 KCalendarCore::Todo::Ptr destTodo; 0553 if (parent.isValid()) { 0554 const auto parentItem = this->data(parent, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); 0555 if (parentItem.isValid()) { 0556 destTodo = Akonadi::CalendarUtils::todo(parentItem); 0557 } 0558 0559 auto tmpParent = parent; 0560 while (tmpParent.isValid()) { 0561 const auto parentItem = this->data(parent, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); 0562 if (!parentItem.isValid()) { 0563 break; 0564 } 0565 const auto parentTodo = Akonadi::CalendarUtils::todo(parentItem); 0566 if (!parentTodo) { 0567 break; 0568 } 0569 0570 if (parentTodo->uid() == todo->uid()) { // correct, don't use instanceIdentifier() here 0571 Q_EMIT dropOnSelfRejected(); 0572 return false; 0573 } 0574 0575 tmpParent = tmpParent.parent(); 0576 } 0577 } 0578 0579 if (!destTodo || !destTodo->hasRecurrenceId()) { 0580 KCalendarCore::Todo::Ptr oldTodo = KCalendarCore::Todo::Ptr(todo->clone()); 0581 // destTodo is empty when we drag a to-do out of a relationship 0582 todo->setRelatedTo(destTodo ? destTodo->uid() : QString()); 0583 d->m_changer->modifyIncidence(item, oldTodo); 0584 0585 // again, no need to Q_EMIT dataChanged, that's done by processChange 0586 return true; 0587 } else { 0588 qCDebug(AKONADICALENDAR_LOG) << "Todo's with recurring id can't have child todos yet."; 0589 return false; 0590 } 0591 } else if (e) { 0592 // TODO: Implement dropping an event onto a to-do: Generate a relationship to the event! 0593 } else { 0594 if (!parent.isValid()) { 0595 // TODO we should create a new todo with the data in the drop object 0596 qCDebug(AKONADICALENDAR_LOG) << "TODO: Create a new todo with the given data"; 0597 return false; 0598 } 0599 0600 const auto parentItem = this->data(parent, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); 0601 KCalendarCore::Todo::Ptr destTodo = Akonadi::CalendarUtils::todo(parentItem); 0602 0603 if (data->hasText()) { 0604 QString text = data->text(); 0605 0606 KCalendarCore::Todo::Ptr oldTodo = KCalendarCore::Todo::Ptr(destTodo->clone()); 0607 0608 if (text.startsWith(QLatin1StringView("file:"))) { 0609 destTodo->addAttachment(KCalendarCore::Attachment(text)); 0610 } else { 0611 QStringList emails = KEmailAddress::splitAddressList(text); 0612 for (QStringList::ConstIterator it = emails.constBegin(); it != emails.constEnd(); ++it) { 0613 QString name; 0614 QString email; 0615 QString comment; 0616 if (KEmailAddress::splitAddress(*it, name, email, comment) == KEmailAddress::AddressOk) { 0617 destTodo->addAttendee(KCalendarCore::Attendee(name, email)); 0618 } 0619 } 0620 } 0621 d->m_changer->modifyIncidence(parentItem, oldTodo); 0622 return true; 0623 } 0624 } 0625 } 0626 0627 return false; 0628 } 0629 0630 Qt::ItemFlags TodoModel::flags(const QModelIndex &index) const 0631 { 0632 if (!index.isValid()) { 0633 return Qt::NoItemFlags; 0634 } 0635 0636 Qt::ItemFlags ret = KExtraColumnsProxyModel::flags(index); 0637 0638 const auto item = data(index, Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>(); 0639 0640 if (!item.isValid()) { 0641 Q_ASSERT(mapToSource(index).isValid()); 0642 qCWarning(AKONADICALENDAR_LOG) << "Item is invalid " << index; 0643 Q_ASSERT(false); 0644 return Qt::NoItemFlags; 0645 } 0646 0647 ret |= Qt::ItemIsDragEnabled; 0648 0649 const KCalendarCore::Todo::Ptr todo = Akonadi::CalendarUtils::todo(item); 0650 0651 const auto parentCol = Akonadi::EntityTreeModel::updatedCollection(d->etm(), item.parentCollection()); 0652 if (parentCol.rights() & Akonadi::Collection::CanChangeItem) { 0653 // the following columns are editable: 0654 switch (index.column()) { 0655 case SummaryColumn: 0656 case PriorityColumn: 0657 case PercentColumn: 0658 case StartDateColumn: 0659 case DueDateColumn: 0660 case CategoriesColumn: 0661 ret |= Qt::ItemIsEditable; 0662 break; 0663 case DescriptionColumn: 0664 if (!todo->descriptionIsRich()) { 0665 ret |= Qt::ItemIsEditable; 0666 } 0667 break; 0668 } 0669 } 0670 0671 if (index.column() == 0) { 0672 // whole rows should have checkboxes, so append the flag for the 0673 // first item of every row only. Also, only the first item of every 0674 // row should be used as a target for a drag and drop operation. 0675 ret |= Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled; 0676 } 0677 return ret; 0678 } 0679 0680 QHash<int, QByteArray> TodoModel::roleNames() const 0681 { 0682 return { 0683 {SummaryRole, QByteArrayLiteral("summary")}, 0684 {RecurRole, QByteArrayLiteral("recur")}, 0685 {PriorityRole, QByteArrayLiteral("priority")}, 0686 {PercentRole, QByteArrayLiteral("percent")}, 0687 {StartDateRole, QByteArrayLiteral("startDate")}, 0688 {DueDateRole, QByteArrayLiteral("dueDate")}, 0689 {CategoriesRole, QByteArrayLiteral("categories")}, 0690 {DescriptionRole, QByteArrayLiteral("description")}, 0691 {CalendarRole, QByteArrayLiteral("calendar")}, 0692 {Qt::CheckStateRole, QByteArrayLiteral("checked")}, 0693 }; 0694 } 0695 0696 #include "moc_todomodel.cpp"