Akonadi

entitytreemodel.cpp
1/*
2 SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "entitytreemodel.h"
8#include "akonadicore_debug.h"
9#include "entitytreemodel_p.h"
10#include "monitor_p.h"
11
12#include <QAbstractProxyModel>
13#include <QHash>
14#include <QMimeData>
15
16#include <KLocalizedString>
17#include <QUrl>
18#include <QUrlQuery>
19
20#include "collectionmodifyjob.h"
21#include "entitydisplayattribute.h"
22#include "itemmodifyjob.h"
23#include "monitor.h"
24#include "session.h"
25
26#include "collectionutils.h"
27
28#include "pastehelper_p.h"
29
30// clazy:excludeall=old-style-connect
31
32Q_DECLARE_METATYPE(QSet<QByteArray>)
33
34using namespace Akonadi;
35
37 : QAbstractItemModel(parent)
38 , d_ptr(new EntityTreeModelPrivate(this))
39{
41 d->init(monitor);
42}
43
44EntityTreeModel::EntityTreeModel(Monitor *monitor, EntityTreeModelPrivate *d, QObject *parent)
45 : QAbstractItemModel(parent)
46 , d_ptr(d)
47{
48 d->init(monitor);
49}
50
52{
54
55 for (const QList<Node *> &list : std::as_const(d->m_childEntities)) {
56 qDeleteAll(list);
57 }
58}
59
61{
62 Q_D(const EntityTreeModel);
63 return d->m_listFilter;
64}
65
67{
69 d->beginResetModel();
70 d->m_listFilter = filter;
71 d->m_monitor->setAllMonitored(filter == CollectionFetchScope::NoFilter);
72 d->endResetModel();
73}
74
76{
78 d->beginResetModel();
79 const Akonadi::Collection::List lstCols = d->m_monitor->collectionsMonitored();
80 for (const Akonadi::Collection &col : lstCols) {
81 d->m_monitor->setCollectionMonitored(col, false);
82 }
83 for (const Akonadi::Collection &col : collections) {
84 d->m_monitor->setCollectionMonitored(col, true);
85 }
86 d->endResetModel();
87}
88
90{
92 d->m_monitor->setCollectionMonitored(col, monitored);
93}
94
96{
97 Q_D(const EntityTreeModel);
98 return d->m_showSystemEntities;
99}
100
102{
104 d->m_showSystemEntities = show;
105}
106
108{
110 d->beginResetModel();
111 d->endResetModel();
112}
113
114QHash<int, QByteArray> EntityTreeModel::roleNames() const
115{
116 return {
117 {Qt::DecorationRole, "decoration"},
118 {Qt::DisplayRole, "display"},
119 {EntityTreeModel::DisplayNameRole, "displayName"},
120
121 {EntityTreeModel::ItemIdRole, "itemId"},
123 {EntityTreeModel::CollectionIdRole, "collectionId"},
124 {EntityTreeModel::CollectionRole, "collection"},
125
126 {EntityTreeModel::UnreadCountRole, "unreadCount"},
128 {EntityTreeModel::RemoteIdRole, "remoteId"},
129 {EntityTreeModel::IsPopulatedRole, "isPopulated"},
130 {EntityTreeModel::CollectionRole, "collection"},
131 {EntityTreeModel::MimeTypeRole, "mimeType"},
132 {EntityTreeModel::CollectionChildOrderRole, "collectionChildOrder"},
133 {EntityTreeModel::ParentCollectionRole, "parentCollection"},
134 {EntityTreeModel::SessionRole, "session"},
135 {EntityTreeModel::PendingCutRole, "pendingCut"},
136 {EntityTreeModel::LoadedPartsRole, "loadedParts"},
137 {EntityTreeModel::AvailablePartsRole, "availableParts"},
138 {EntityTreeModel::UnreadCountRole, "unreadCount"},
139 {EntityTreeModel::FetchStateRole, "fetchState"},
140 };
141}
142
143int EntityTreeModel::columnCount(const QModelIndex &parent) const
144{
145 // TODO: Statistics?
146 if (parent.isValid() && parent.column() != 0) {
147 return 0;
148 }
149
150 return qMax(entityColumnCount(CollectionTreeHeaders), entityColumnCount(ItemListHeaders));
151}
152
153QVariant EntityTreeModel::entityData(const Item &item, int column, int role) const
154{
155 Q_D(const EntityTreeModel);
156
157 if (column == 0) {
158 switch (role) {
159 case Qt::DisplayRole:
160 case Qt::EditRole:
162 if (const auto *attr = item.attribute<EntityDisplayAttribute>(); attr && !attr->displayName().isEmpty()) {
163 return attr->displayName();
164 } else if (!item.remoteId().isEmpty()) {
165 return item.remoteId();
166 }
167 return QString(QLatin1Char('<') + QString::number(item.id()) + QLatin1Char('>'));
169 if (const auto *attr = item.attribute<EntityDisplayAttribute>(); attr && !attr->iconName().isEmpty()) {
170 return d->iconForName(attr->iconName());
171 }
172 break;
173 default:
174 break;
175 }
176 }
177
178 return QVariant();
179}
180
181QVariant EntityTreeModel::entityData(const Collection &collection, int column, int role) const
182{
183 Q_D(const EntityTreeModel);
184
185 if (column != 0) {
186 return QString();
187 }
188
189 if (collection == Collection::root()) {
190 // Only display the root collection. It may not be edited.
191 if (role == Qt::DisplayRole || role == EntityTreeModel::DisplayNameRole) {
192 return d->m_rootCollectionDisplayName;
193 } else if (role == Qt::EditRole) {
194 return QVariant();
195 }
196 }
197
198 switch (role) {
199 case Qt::DisplayRole:
200 case Qt::EditRole:
202 if (column == 0) {
203 if (const QString displayName = collection.displayName(); !displayName.isEmpty()) {
204 return displayName;
205 } else {
206 return i18nc("@info:status", "Loading...");
207 }
208 }
209 break;
211 if (const auto *const attr = collection.attribute<EntityDisplayAttribute>(); attr && !attr->iconName().isEmpty()) {
212 return d->iconForName(attr->iconName());
213 }
214 return d->iconForName(CollectionUtils::defaultIconName(collection));
215 default:
216 break;
217 }
218
219 return QVariant();
220}
221
222QVariant EntityTreeModel::data(const QModelIndex &index, int role) const
223{
224 Q_D(const EntityTreeModel);
225 if (role == SessionRole) {
226 return QVariant::fromValue(qobject_cast<QObject *>(d->m_session));
227 }
228
229 // Ugly, but at least the API is clean.
230 const auto headerGroup = static_cast<HeaderGroup>((role / static_cast<int>(TerminalUserRole)));
231
232 role %= TerminalUserRole;
233 if (!index.isValid()) {
234 if (ColumnCountRole != role) {
235 return QVariant();
236 }
237
238 return entityColumnCount(headerGroup);
239 }
240
241 if (ColumnCountRole == role) {
242 return entityColumnCount(headerGroup);
243 }
244
245 const Node *node = reinterpret_cast<Node *>(index.internalPointer());
246
247 if (ParentCollectionRole == role && d->m_collectionFetchStrategy != FetchNoCollections) {
248 const Collection parentCollection = d->m_collections.value(node->parent);
249 Q_ASSERT(parentCollection.isValid());
250
251 return QVariant::fromValue(parentCollection);
252 }
253
254 if (Node::Collection == node->type) {
255 const Collection collection = d->m_collections.value(node->id);
256 if (!collection.isValid()) {
257 return QVariant();
258 }
259
260 switch (role) {
261 case MimeTypeRole:
262 return collection.mimeType();
263 case RemoteIdRole:
264 return collection.remoteId();
265 case CollectionIdRole:
266 return collection.id();
267 case ItemIdRole:
268 // QVariant().toInt() is 0, not -1, so we have to handle the ItemIdRole
269 // and CollectionIdRole (below) specially
270 return -1;
271 case CollectionRole:
272 return QVariant::fromValue(collection);
273 case EntityUrlRole:
274 return collection.url().url();
275 case UnreadCountRole:
276 return collection.statistics().unreadCount();
277 case FetchStateRole:
278 return d->m_pendingCollectionRetrieveJobs.contains(collection.id()) ? FetchingState : IdleState;
279 case IsPopulatedRole:
280 return d->m_populatedCols.contains(collection.id());
282 return entityData(collection, index.column(), Qt::DisplayRole);
283 case PendingCutRole:
284 return d->m_pendingCutCollections.contains(node->id);
286 if (const auto *const attr = collection.attribute<EntityDisplayAttribute>(); attr && attr->backgroundColor().isValid()) {
287 return attr->backgroundColor();
288 }
289 [[fallthrough]];
290 default:
291 return entityData(collection, index.column(), role);
292 }
293
294 } else if (Node::Item == node->type) {
295 const Item item = d->m_items.value(node->id);
296 if (!item.isValid()) {
297 return QVariant();
298 }
299
300 switch (role) {
302 return QVariant::fromValue(item.parentCollection());
303 case MimeTypeRole:
304 return item.mimeType();
305 case RemoteIdRole:
306 return item.remoteId();
307 case ItemRole:
308 return QVariant::fromValue(item);
309 case ItemIdRole:
310 return item.id();
311 case CollectionIdRole:
312 return -1;
313 case LoadedPartsRole:
314 return QVariant::fromValue(item.loadedPayloadParts());
316 return QVariant::fromValue(item.availablePayloadParts());
317 case EntityUrlRole:
318 return item.url(Akonadi::Item::UrlWithMimeType).url();
319 case PendingCutRole:
320 return d->m_pendingCutItems.contains(node->id);
322 if (const auto *const attr = item.attribute<EntityDisplayAttribute>(); attr && attr->backgroundColor().isValid()) {
323 return attr->backgroundColor();
324 }
325 [[fallthrough]];
326 default:
327 return entityData(item, index.column(), role);
328 }
329 }
330
331 return QVariant();
332}
333
334Qt::ItemFlags EntityTreeModel::flags(const QModelIndex &index) const
335{
336 Q_D(const EntityTreeModel);
337 // Pass modeltest.
338 if (!index.isValid()) {
339 return {};
340 }
341
343
344 const Node *node = reinterpret_cast<Node *>(index.internalPointer());
345
346 if (Node::Collection == node->type) {
347 const Collection collection = d->m_collections.value(node->id);
348 if (collection.isValid()) {
349 if (collection == Collection::root()) {
350 // Selectable and displayable only.
351 return flags;
352 }
353
354 const int rights = collection.rights();
355
356 if (rights & Collection::CanChangeCollection) {
357 if (index.column() == 0) {
358 flags |= Qt::ItemIsEditable;
359 }
360 // Changing the collection includes changing the metadata (child entityordering).
361 // Need to allow this by drag and drop.
362 flags |= Qt::ItemIsDropEnabled;
363 }
365 // Can we drop new collections and items into this collection?
366 flags |= Qt::ItemIsDropEnabled;
367 }
368
369 // dragging is always possible, even for read-only objects, but they can only be copied, not moved.
370 flags |= Qt::ItemIsDragEnabled;
371 }
372 } else if (Node::Item == node->type) {
373 // cut out entities are shown as disabled
374 // TODO: Not sure this is wanted, it prevents any interaction with them, better
375 // solution would be to move this to the delegate, as was done for collections.
376 if (d->m_pendingCutItems.contains(node->id)) {
378 }
379
380 // Rights come from the parent collection.
381
382 Collection parentCollection;
383 if (!index.parent().isValid()) {
384 parentCollection = d->m_rootCollection;
385 } else {
386 const Node *parentNode = reinterpret_cast<Node *>(index.parent().internalPointer());
387 parentCollection = d->m_collections.value(parentNode->id);
388 }
389 if (parentCollection.isValid()) {
390 const int rights = parentCollection.rights();
391
392 // Can't drop onto items.
393 if (rights & Collection::CanChangeItem && index.column() == 0) {
394 flags |= Qt::ItemIsEditable;
395 }
396 // dragging is always possible, even for read-only objects, but they can only be copied, not moved.
397 flags |= Qt::ItemIsDragEnabled;
398 }
399 }
400
401 return flags;
402}
403
404Qt::DropActions EntityTreeModel::supportedDropActions() const
405{
407}
408
409QStringList EntityTreeModel::mimeTypes() const
410{
411 // TODO: Should this return the mimetypes that the items provide? Allow dragging a contact from here for example.
412 return {QStringLiteral("text/uri-list")};
413}
414
415bool EntityTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
416{
417 Q_UNUSED(row)
418 Q_UNUSED(column)
420
421 // Can't drop onto Collection::root.
422 if (!parent.isValid()) {
423 return false;
424 }
425
426 // TODO Use action and collection rights and return false if necessary
427
428 // if row and column are -1, then the drop was on parent directly.
429 // data should then be appended on the end of the items of the collections as appropriate.
430 // That will mean begin insert rows etc.
431 // Otherwise it was a sibling of the row^th item of parent.
432 // Needs to be handled when ordering is accounted for.
433
434 // Handle dropping between items as well as on items.
435 // if ( row != -1 && column != -1 )
436 // {
437 // }
438
439 if (action == Qt::IgnoreAction) {
440 return true;
441 }
442
443 // Shouldn't do this. Need to be able to drop vcards for example.
444 // if ( !data->hasFormat( "text/uri-list" ) )
445 // return false;
446
447 Node *node = reinterpret_cast<Node *>(parent.internalId());
448
449 Q_ASSERT(node);
450
451 if (Node::Item == node->type) {
452 if (!parent.parent().isValid()) {
453 // The drop is somehow on an item with no parent (shouldn't happen)
454 // The drop should be considered handled anyway.
455 qCWarning(AKONADICORE_LOG) << "Dropped onto item with no parent collection";
456 return true;
457 }
458
459 // A drop onto an item should be considered as a drop onto its parent collection
460 node = reinterpret_cast<Node *>(parent.parent().internalId());
461 }
462
463 if (Node::Collection == node->type) {
464 const Collection destCollection = d->m_collections.value(node->id);
465
466 // Applications can't create new collections in root. Only resources can.
467 if (destCollection == Collection::root()) {
468 // Accept the event so that it doesn't propagate.
469 return true;
470 }
471
472 if (data->hasFormat(QStringLiteral("text/uri-list"))) {
473 MimeTypeChecker mimeChecker;
474 mimeChecker.setWantedMimeTypes(destCollection.contentMimeTypes());
475
476 const QList<QUrl> urls = data->urls();
477 for (const QUrl &url : urls) {
478 const Collection collection = d->m_collections.value(Collection::fromUrl(url).id());
479 if (collection.isValid()) {
480 if (collection.parentCollection().id() == destCollection.id() && action != Qt::CopyAction) {
481 qCWarning(AKONADICORE_LOG) << "Error: source and destination of move are the same.";
482 return false;
483 }
484
485 if (!mimeChecker.isWantedCollection(collection)) {
486 qCDebug(AKONADICORE_LOG) << "unwanted collection" << mimeChecker.wantedMimeTypes() << collection.contentMimeTypes();
487 return false;
488 }
489
490 QUrlQuery query(url);
491 if (query.hasQueryItem(QStringLiteral("name"))) {
492 const QString collectionName = query.queryItemValue(QStringLiteral("name"));
493 const QStringList collectionNames = d->childCollectionNames(destCollection);
494
495 if (collectionNames.contains(collectionName)) {
497 i18n("The target collection '%1' contains already\na collection with name '%2'.", destCollection.name(), collection.name()));
498 return false;
499 }
500 }
501 } else {
502 const Item item = d->m_items.value(Item::fromUrl(url).id());
503 if (item.isValid()) {
504 if (item.parentCollection().id() == destCollection.id() && action != Qt::CopyAction) {
505 qCWarning(AKONADICORE_LOG) << "Error: source and destination of move are the same.";
506 return false;
507 }
508
509 if (!mimeChecker.isWantedItem(item)) {
510 qCDebug(AKONADICORE_LOG) << "unwanted item" << mimeChecker.wantedMimeTypes() << item.mimeType();
511 return false;
512 }
513 }
514 }
515 }
516
517 KJob *job = PasteHelper::pasteUriList(data, destCollection, action, d->m_session);
518 if (!job) {
519 return false;
520 }
521
522 connect(job, SIGNAL(result(KJob *)), SLOT(pasteJobDone(KJob *)));
523
524 // Accept the event so that it doesn't propagate.
525 return true;
526 } else {
527 // not a set of uris. Maybe vcards etc. Check if the parent supports them, and maybe do
528 // fromMimeData for them. Hmm, put it in the same transaction with the above?
529 // TODO: This should be handled first, not last.
530 }
531 }
532
533 return false;
534}
535
536QModelIndex EntityTreeModel::index(int row, int column, const QModelIndex &parent) const
537{
538 Q_D(const EntityTreeModel);
539
540 if (parent.column() > 0) {
541 return QModelIndex();
542 }
543
544 // TODO: don't use column count here? Use some d-> func.
545 if (column >= columnCount() || column < 0) {
546 return QModelIndex();
547 }
548
549 QList<Node *> childEntities;
550
551 const Node *parentNode = reinterpret_cast<Node *>(parent.internalPointer());
552 if (!parentNode || !parent.isValid()) {
553 if (d->m_showRootCollection) {
554 childEntities << d->m_childEntities.value(-1);
555 } else {
556 childEntities = d->m_childEntities.value(d->m_rootCollection.id());
557 }
558 } else if (parentNode->id >= 0) {
559 childEntities = d->m_childEntities.value(parentNode->id);
560 }
561
562 const int size = childEntities.size();
563 if (row < 0 || row >= size) {
564 return QModelIndex();
565 }
566
567 Node *node = childEntities.at(row);
568 return createIndex(row, column, reinterpret_cast<void *>(node));
569}
570
571QModelIndex EntityTreeModel::parent(const QModelIndex &index) const
572{
573 Q_D(const EntityTreeModel);
574
575 if (!index.isValid()) {
576 return QModelIndex();
577 }
578
579 if (d->m_collectionFetchStrategy == InvisibleCollectionFetch || d->m_collectionFetchStrategy == FetchNoCollections) {
580 return QModelIndex();
581 }
582
583 const Node *node = reinterpret_cast<Node *>(index.internalPointer());
584
585 if (!node) {
586 return QModelIndex();
587 }
588
589 const Collection collection = d->m_collections.value(node->parent);
590
591 if (!collection.isValid()) {
592 return QModelIndex();
593 }
594
595 if (collection.id() == d->m_rootCollection.id()) {
596 if (!d->m_showRootCollection) {
597 return QModelIndex();
598 } else {
599 return createIndex(0, 0, reinterpret_cast<void *>(d->m_rootNode));
600 }
601 }
602
603 Q_ASSERT(collection.parentCollection().isValid());
604 const int row = d->indexOf<Node::Collection>(d->m_childEntities.value(collection.parentCollection().id()), collection.id());
605
606 Q_ASSERT(row >= 0);
607 Node *parentNode = d->m_childEntities.value(collection.parentCollection().id()).at(row);
608
609 return createIndex(row, 0, reinterpret_cast<void *>(parentNode));
610}
611
612int EntityTreeModel::rowCount(const QModelIndex &parent) const
613{
614 Q_D(const EntityTreeModel);
615
616 if (d->m_collectionFetchStrategy == InvisibleCollectionFetch || d->m_collectionFetchStrategy == FetchNoCollections) {
617 if (parent.isValid()) {
618 return 0;
619 } else {
620 return d->m_items.size();
621 }
622 }
623
624 if (!parent.isValid()) {
625 // If we're showing the root collection then it will be the only child of the root.
626 if (d->m_showRootCollection) {
627 return d->m_childEntities.value(-1).size();
628 }
629 return d->m_childEntities.value(d->m_rootCollection.id()).size();
630 }
631
632 if (parent.column() != 0) {
633 return 0;
634 }
635
636 const Node *node = reinterpret_cast<Node *>(parent.internalPointer());
637
638 if (!node) {
639 return 0;
640 }
641
642 if (Node::Item == node->type) {
643 return 0;
644 }
645
646 Q_ASSERT(parent.isValid());
647 return d->m_childEntities.value(node->id).size();
648}
649
650int EntityTreeModel::entityColumnCount(HeaderGroup headerGroup) const
651{
652 // Not needed in this model.
653 Q_UNUSED(headerGroup)
654
655 return 1;
656}
657
658QVariant EntityTreeModel::entityHeaderData(int section, Qt::Orientation orientation, int role, HeaderGroup headerGroup) const
659{
660 Q_D(const EntityTreeModel);
661 // Not needed in this model.
662 Q_UNUSED(headerGroup)
663
664 if (section == 0 && orientation == Qt::Horizontal && (role == Qt::DisplayRole || role == EntityTreeModel::DisplayNameRole)) {
665 if (d->m_rootCollection == Collection::root()) {
666 return i18nc("@title:column Name of a thing", "Name");
667 }
668 return d->m_rootCollection.name();
669 }
670
671 return QAbstractItemModel::headerData(section, orientation, role);
672}
673
674QVariant EntityTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
675{
676 const auto headerGroup = static_cast<HeaderGroup>((role / static_cast<int>(TerminalUserRole)));
677
678 role %= TerminalUserRole;
679 return entityHeaderData(section, orientation, role, headerGroup);
680}
681
682QMimeData *EntityTreeModel::mimeData(const QModelIndexList &indexes) const
683{
684 Q_D(const EntityTreeModel);
685
686 auto data = new QMimeData();
687 QList<QUrl> urls;
688 for (const QModelIndex &index : indexes) {
689 if (index.column() != 0) {
690 continue;
691 }
692
693 if (!index.isValid()) {
694 continue;
695 }
696
697 const Node *node = reinterpret_cast<Node *>(index.internalPointer());
698
699 if (Node::Collection == node->type) {
700 urls << d->m_collections.value(node->id).url(Collection::UrlWithName);
701 } else if (Node::Item == node->type) {
702 QUrl url = d->m_items.value(node->id).url(Item::Item::UrlWithMimeType);
703 QUrlQuery query(url);
704 query.addQueryItem(QStringLiteral("parent"), QString::number(node->parent));
705 url.setQuery(query);
706 urls << url;
707 } else { // if that happens something went horrible wrong
708 Q_ASSERT(false);
709 }
710 }
711
712 data->setUrls(urls);
713
714 return data;
715}
716
717// Always return false for actions which take place asynchronously, eg via a Job.
718bool EntityTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
719{
721
722 const Node *node = reinterpret_cast<Node *>(index.internalPointer());
723
724 if (role == PendingCutRole) {
725 if (index.isValid() && value.toBool()) {
726 if (Node::Collection == node->type) {
727 d->m_pendingCutCollections.append(node->id);
728 } else if (Node::Item == node->type) {
729 d->m_pendingCutItems.append(node->id);
730 }
731 } else {
732 d->m_pendingCutCollections.clear();
733 d->m_pendingCutItems.clear();
734 }
735 return true;
736 }
737
738 if (index.isValid() && node->type == Node::Collection && (role == CollectionRefRole || role == CollectionDerefRole)) {
739 const Collection collection = index.data(CollectionRole).value<Collection>();
740 Q_ASSERT(collection.isValid());
741
742 if (role == CollectionDerefRole) {
743 d->deref(collection.id());
744 } else if (role == CollectionRefRole) {
745 d->ref(collection.id());
746 }
747 return true;
748 }
749
750 if (index.column() == 0 && (role & (Qt::EditRole | ItemRole | CollectionRole))) {
751 if (Node::Collection == node->type) {
752 Collection collection = d->m_collections.value(node->id);
753 if (!collection.isValid() || !value.isValid()) {
754 return false;
755 }
756
757 if (Qt::EditRole == role) {
758 collection.setName(value.toString());
759 if (collection.hasAttribute<EntityDisplayAttribute>()) {
760 auto *displayAttribute = collection.attribute<EntityDisplayAttribute>();
761 displayAttribute->setDisplayName(value.toString());
762 }
763 } else if (Qt::BackgroundRole == role) {
764 auto color = value.value<QColor>();
765 if (!color.isValid()) {
766 return false;
767 }
768
769 auto *eda = collection.attribute<EntityDisplayAttribute>(Collection::AddIfMissing);
770 eda->setBackgroundColor(color);
771 } else if (CollectionRole == role) {
772 collection = value.value<Collection>();
773 }
774
775 auto job = new CollectionModifyJob(collection, d->m_session);
776 connect(job, SIGNAL(result(KJob *)), SLOT(updateJobDone(KJob *)));
777
778 return false;
779 } else if (Node::Item == node->type) {
780 Item item = d->m_items.value(node->id);
781 if (!item.isValid() || !value.isValid()) {
782 return false;
783 }
784
785 if (Qt::EditRole == role) {
786 if (item.hasAttribute<EntityDisplayAttribute>()) {
787 auto *displayAttribute = item.attribute<EntityDisplayAttribute>(Item::AddIfMissing);
788 displayAttribute->setDisplayName(value.toString());
789 }
790 } else if (Qt::BackgroundRole == role) {
791 auto color = value.value<QColor>();
792 if (!color.isValid()) {
793 return false;
794 }
795
796 auto *eda = item.attribute<EntityDisplayAttribute>(Item::AddIfMissing);
797 eda->setBackgroundColor(color);
798 } else if (ItemRole == role) {
799 item = value.value<Item>();
800 Q_ASSERT(item.id() == node->id);
801 }
802
803 auto itemModifyJob = new ItemModifyJob(item, d->m_session);
804 connect(itemModifyJob, SIGNAL(result(KJob *)), SLOT(updateJobDone(KJob *)));
805
806 return false;
807 }
808 }
809
810 return QAbstractItemModel::setData(index, value, role);
811}
812
813bool EntityTreeModel::canFetchMore(const QModelIndex &parent) const
814{
815 Q_UNUSED(parent)
816 return false;
817}
818
819void EntityTreeModel::fetchMore(const QModelIndex &parent)
820{
822
823 if (!d->canFetchMore(parent)) {
824 return;
825 }
826
827 if (d->m_collectionFetchStrategy == InvisibleCollectionFetch) {
828 return;
829 }
830
831 if (d->m_itemPopulation == ImmediatePopulation) {
832 // Nothing to do. The items are already in the model.
833 return;
834 } else if (d->m_itemPopulation == LazyPopulation) {
835 const Collection collection = parent.data(CollectionRole).value<Collection>();
836
837 if (!collection.isValid()) {
838 return;
839 }
840
841 d->fetchItems(collection);
842 }
843}
844
845bool EntityTreeModel::hasChildren(const QModelIndex &parent) const
846{
847 Q_D(const EntityTreeModel);
848
849 if (d->m_collectionFetchStrategy == InvisibleCollectionFetch || d->m_collectionFetchStrategy == FetchNoCollections) {
850 return parent.isValid() ? false : !d->m_items.isEmpty();
851 }
852
853 // TODO: Empty collections right now will return true and get a little + to expand.
854 // There is probably no way to tell if a collection
855 // has child items in akonadi without first attempting an itemFetchJob...
856 // Figure out a way to fix this. (Statistics)
857 return ((rowCount(parent) > 0) || (d->canFetchMore(parent) && d->m_itemPopulation == LazyPopulation));
858}
859
861{
862 Q_D(const EntityTreeModel);
863 return d->m_collectionTreeFetched;
864}
865
867{
868 Q_D(const EntityTreeModel);
869 return d->m_populatedCols.contains(id);
870}
871
873{
874 Q_D(const EntityTreeModel);
875 return d->m_collectionTreeFetched && d->m_pendingCollectionRetrieveJobs.isEmpty();
876}
877
878QModelIndexList EntityTreeModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const
879{
880 Q_D(const EntityTreeModel);
881
882 if (role == CollectionIdRole || role == CollectionRole) {
884 if (role == CollectionRole) {
885 const Collection collection = value.value<Collection>();
886 id = collection.id();
887 } else {
888 id = value.toLongLong();
889 }
890
891 const Collection collection = d->m_collections.value(id);
892 if (!collection.isValid()) {
893 return {};
894 }
895
896 const QModelIndex collectionIndex = d->indexForCollection(collection);
897 Q_ASSERT(collectionIndex.isValid());
898 return {collectionIndex};
899 } else if (role == ItemIdRole || role == ItemRole) {
900 Item::Id id;
901 if (role == ItemRole) {
902 id = value.value<Item>().id();
903 } else {
904 id = value.toLongLong();
905 }
906
907 const Item item = d->m_items.value(id);
908 if (!item.isValid()) {
909 return {};
910 }
911 return d->indexesForItem(item);
912 } else if (role == EntityUrlRole) {
913 const QUrl url(value.toString());
914 const Item item = Item::fromUrl(url);
915
916 if (item.isValid()) {
917 return d->indexesForItem(d->m_items.value(item.id()));
918 }
919
920 const Collection collection = Collection::fromUrl(url);
921 if (!collection.isValid()) {
922 return {};
923 }
924 return {d->indexForCollection(collection)};
925 }
926
927 return QAbstractItemModel::match(start, role, value, hits, flags);
928}
929
930bool EntityTreeModel::insertRows(int /*row*/, int /*count*/, const QModelIndex & /*parent*/)
931{
932 return false;
933}
934
935bool EntityTreeModel::insertColumns(int /*column*/, int /*count*/, const QModelIndex & /*parent*/)
936{
937 return false;
938}
939
940bool EntityTreeModel::removeRows(int /*row*/, int /*count*/, const QModelIndex & /*parent*/)
941{
942 return false;
943}
944
945bool EntityTreeModel::removeColumns(int /*column*/, int /*count*/, const QModelIndex & /*parent*/)
946{
947 return false;
948}
949
951{
953 d->beginResetModel();
954 d->m_itemPopulation = strategy;
955
956 if (strategy == NoItemPopulation) {
957 disconnect(d->m_monitor, SIGNAL(itemAdded(Akonadi::Item, Akonadi::Collection)), this, SLOT(monitoredItemAdded(Akonadi::Item, Akonadi::Collection)));
958 disconnect(d->m_monitor, SIGNAL(itemChanged(Akonadi::Item, QSet<QByteArray>)), this, SLOT(monitoredItemChanged(Akonadi::Item, QSet<QByteArray>)));
959 disconnect(d->m_monitor, SIGNAL(itemRemoved(Akonadi::Item)), this, SLOT(monitoredItemRemoved(Akonadi::Item)));
960 disconnect(d->m_monitor,
962 this,
963 SLOT(monitoredItemMoved(Akonadi::Item, Akonadi::Collection, Akonadi::Collection)));
964
965 disconnect(d->m_monitor, SIGNAL(itemLinked(Akonadi::Item, Akonadi::Collection)), this, SLOT(monitoredItemLinked(Akonadi::Item, Akonadi::Collection)));
966 disconnect(d->m_monitor,
967 SIGNAL(itemUnlinked(Akonadi::Item, Akonadi::Collection)),
968 this,
969 SLOT(monitoredItemUnlinked(Akonadi::Item, Akonadi::Collection)));
970 }
971
972 d->m_monitor->d_ptr->useRefCounting = (strategy == LazyPopulation);
973
974 d->endResetModel();
975}
976
982
984{
986 d->beginResetModel();
987 d->m_showRootCollection = include;
988 d->endResetModel();
989}
990
992{
993 Q_D(const EntityTreeModel);
994 return d->m_showRootCollection;
995}
996
998{
1000 d->m_rootCollectionDisplayName = displayName;
1001
1002 // TODO: Emit datachanged if it is being shown.
1003}
1004
1006{
1007 Q_D(const EntityTreeModel);
1008 return d->m_rootCollectionDisplayName;
1009}
1010
1012{
1014 d->beginResetModel();
1015 d->m_collectionFetchStrategy = strategy;
1016
1017 if (strategy == FetchNoCollections || strategy == InvisibleCollectionFetch) {
1018 disconnect(d->m_monitor, SIGNAL(collectionChanged(Akonadi::Collection)), this, SLOT(monitoredCollectionChanged(Akonadi::Collection)));
1019 disconnect(d->m_monitor,
1020 SIGNAL(collectionAdded(Akonadi::Collection, Akonadi::Collection)),
1021 this,
1022 SLOT(monitoredCollectionAdded(Akonadi::Collection, Akonadi::Collection)));
1023 disconnect(d->m_monitor, SIGNAL(collectionRemoved(Akonadi::Collection)), this, SLOT(monitoredCollectionRemoved(Akonadi::Collection)));
1024 disconnect(d->m_monitor,
1026 this,
1027 SLOT(monitoredCollectionMoved(Akonadi::Collection, Akonadi::Collection, Akonadi::Collection)));
1028 d->m_monitor->fetchCollection(false);
1029 } else {
1030 d->m_monitor->fetchCollection(true);
1031 }
1032
1033 d->endResetModel();
1034}
1035
1037{
1038 Q_D(const EntityTreeModel);
1039 return d->m_collectionFetchStrategy;
1040}
1041
1042static QPair<QList<const QAbstractProxyModel *>, const EntityTreeModel *> proxiesAndModel(const QAbstractItemModel *model)
1043{
1045 const auto *proxy = qobject_cast<const QAbstractProxyModel *>(model);
1046 const QAbstractItemModel *_model = model;
1047 while (proxy) {
1048 proxyChain.prepend(proxy);
1049 _model = proxy->sourceModel();
1050 proxy = qobject_cast<const QAbstractProxyModel *>(_model);
1051 }
1052
1053 const auto *etm = qobject_cast<const EntityTreeModel *>(_model);
1054 return qMakePair(proxyChain, etm);
1055}
1056
1057static QModelIndex proxiedIndex(const QModelIndex &idx, const QList<const QAbstractProxyModel *> &proxyChain)
1058{
1059 QModelIndex _idx = idx;
1060 for (const auto *proxy : proxyChain) {
1061 _idx = proxy->mapFromSource(_idx);
1062 }
1063 return _idx;
1064}
1065
1067{
1068 const auto &[proxy, etm] = proxiesAndModel(model);
1069 if (!etm) {
1070 qCWarning(AKONADICORE_LOG) << "Model" << model << "is not derived from ETM or a proxy model on top of ETM.";
1071 return {};
1072 }
1073
1074 QModelIndex idx = etm->d_ptr->indexForCollection(collection);
1075 return proxiedIndex(idx, proxy);
1076}
1077
1078QModelIndexList EntityTreeModel::modelIndexesForItem(const QAbstractItemModel *model, const Item &item)
1079{
1080 const auto &[proxy, etm] = proxiesAndModel(model);
1081
1082 if (!etm) {
1083 qCWarning(AKONADICORE_LOG) << "Model" << model << "is not derived from ETM or a proxy model on top of ETM.";
1084 return QModelIndexList();
1085 }
1086
1087 const QModelIndexList list = etm->d_ptr->indexesForItem(item);
1088 QModelIndexList proxyList;
1089 for (const QModelIndex &idx : list) {
1090 const QModelIndex pIdx = proxiedIndex(idx, proxy);
1091 if (pIdx.isValid()) {
1092 proxyList.push_back(pIdx);
1093 }
1094 }
1095 return proxyList;
1096}
1097
1099{
1100 const auto *proxy = qobject_cast<const QAbstractProxyModel *>(model);
1101 const QAbstractItemModel *_model = model;
1102 while (proxy) {
1103 _model = proxy->sourceModel();
1105 }
1106
1107 const auto *etm = qobject_cast<const EntityTreeModel *>(_model);
1108 if (etm) {
1109 return etm->d_ptr->m_collections.value(collectionId);
1110 } else {
1111 return Collection{collectionId};
1112 }
1113}
1114
1116{
1117 return updatedCollection(model, collection.id());
1118}
1119
1120#include "moc_entitytreemodel.cpp"
ListFilter
Describes the list filter.
@ NoFilter
No filtering, retrieve all collections.
qint64 unreadCount() const
Returns the number of unread items in this collection or -1 if this information is not available.
Represents a collection of PIM items.
Definition collection.h:62
qint64 Id
Describes the unique id type.
Definition collection.h:82
@ UrlWithName
A url with identifier and name.
Definition collection.h:413
static QString mimeType()
Returns the mimetype used for collections.
bool hasAttribute(const QByteArray &name) const
Returns true if the collection has an attribute of the given type name, false otherwise.
static Collection root()
Returns the root collection.
void setName(const QString &name)
Sets the i18n'ed name of the collection.
@ AddIfMissing
Creates the attribute if it is missing.
Definition collection.h:284
static Collection fromUrl(const QUrl &url)
Creates a collection from the given url.
@ CanChangeCollection
Can change this collection.
Definition collection.h:97
@ CanCreateItem
Can create new items in this collection.
Definition collection.h:95
@ CanLinkItem
Can create links to existing items in this virtual collection.
Definition collection.h:100
@ CanCreateCollection
Can create new subcollections in this collection.
Definition collection.h:98
@ CanChangeItem
Can change items in this collection.
Definition collection.h:94
Collection parentCollection() const
Returns the parent collection of this object.
QList< Collection > List
Describes a list of collections.
Definition collection.h:87
Attribute * attribute(const QByteArray &name)
Returns the attribute of the given type name if available, 0 otherwise.
QUrl url(UrlType type=UrlShort) const
Returns the url of the collection.
QString remoteId() const
Returns the remote id of the collection.
Attribute that stores the properties that are used to display an entity.
QString displayName() const
Returns the name that should be used for display.
QString iconName() const
Returns the icon name of the icon returned by icon().
A model for collections and items together.
Akonadi::CollectionFetchScope::ListFilter listFilter() const
Returns the currently used listfilter.
bool systemEntitiesShown() const
Returns true if internal system entities are shown, and false otherwise.
void setShowSystemEntities(bool show)
Some Entities are hidden in the model, but exist for internal purposes, for example,...
void setRootCollectionDisplayName(const QString &name)
Sets the display name of the root collection of the model.
ItemPopulationStrategy
Describes how the model should populated its items.
@ ImmediatePopulation
Retrieve items immediately when their parent is in the model. This is the default.
@ NoItemPopulation
Do not include items in the model.
@ LazyPopulation
Fetch items only when requested (using canFetchMore/fetchMore)
virtual QVariant entityData(const Item &item, int column, int role=Qt::DisplayRole) const
Provided for convenience of subclasses.
bool includeRootCollection() const
Returns whether the root collection is provided by the model.
EntityTreeModel(Monitor *monitor, QObject *parent=nullptr)
Creates a new entity tree model.
void clearAndReset()
Clears and resets the model.
bool isCollectionPopulated(Akonadi::Collection::Id) const
Returns whether the collection has been populated.
QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits=1, Qt::MatchFlags flags=Qt::MatchFlags(Qt::MatchStartsWith|Qt::MatchWrap)) const override
Reimplemented to handle the AmazingCompletionRole.
void errorOccurred(const QString &message)
Emitted when an error occurred.
@ FetchingState
There is a fetch of items in this collection in progress.
@ IdleState
There is no fetch of items in this collection in progress.
QString rootCollectionDisplayName() const
Returns the display name of the root collection.
void setCollectionFetchStrategy(CollectionFetchStrategy strategy)
Sets the collection fetch strategy of the model.
HeaderGroup
Describes what header information the model shall return.
@ CollectionTreeHeaders
Header information for a collection-only tree.
@ ItemListHeaders
Header information for a list of items.
void setCollectionsMonitored(const Akonadi::Collection::List &collections)
Monitors the specified collections and resets the model.
ItemPopulationStrategy itemPopulationStrategy() const
Returns the item population strategy of the model.
~EntityTreeModel() override
Destroys the entity tree model.
CollectionFetchStrategy
Describes what collections shall be fetched by and represent in the model.
@ FetchNoCollections
Fetches nothing. This creates an empty model.
@ InvisibleCollectionFetch
Fetches collections, but does not put them in the model.
void setListFilter(Akonadi::CollectionFetchScope::ListFilter filter)
Sets the currently used listfilter.
CollectionFetchStrategy collectionFetchStrategy() const
Returns the collection fetch strategy of the model.
static Collection updatedCollection(const QAbstractItemModel *model, qint64 collectionId)
Returns an Akonadi::Collection from the model based on given collectionId.
void setCollectionMonitored(const Akonadi::Collection &col, bool monitored=true)
Adds or removes a specific collection from the monitored set without resetting the model.
void setItemPopulationStrategy(ItemPopulationStrategy strategy)
Sets the item population strategy of the model.
void setIncludeRootCollection(bool include)
Sets whether the root collection shall be provided by the model.
static QModelIndexList modelIndexesForItem(const QAbstractItemModel *model, const Item &item)
Returns a QModelIndex in model which points to item.
static QModelIndex modelIndexForCollection(const QAbstractItemModel *model, const Collection &collection)
Returns a QModelIndex in model which points to collection.
bool isFullyPopulated() const
Returns whether the model is fully populated.
bool isCollectionTreeFetched() const
Returns whether the collection tree has been fetched at initialisation.
@ AvailablePartsRole
Parts available in the Akonadi server for the item.
@ ParentCollectionRole
The parent collection of the entity.
@ TerminalUserRole
Last role for user extensions. Don't use a role beyond this or headerData will break.
@ LoadedPartsRole
Parts available in the model for the item.
@ RemoteIdRole
The remoteId of the entity.
@ OriginalCollectionNameRole
Returns original name for collection.
@ CollectionRole
The collection.
@ EntityUrlRole
The akonadi:/ Url of the entity as a string. Item urls will contain the mimetype.
@ CollectionIdRole
The collection id.
@ PendingCutRole
Used to indicate items which are to be cut.
@ DisplayNameRole
Returns the same as Qt::DisplayRole.
@ UnreadCountRole
Returns the number of unread items in a collection.
@ IsPopulatedRole
Returns whether a Collection has been populated, i.e. whether its items have been fetched.
@ MimeTypeRole
The mimetype of the entity.
@ CollectionChildOrderRole
Ordered list of child items if available.
@ FetchStateRole
Returns the FetchState of a particular item.
virtual QVariant entityHeaderData(int section, Qt::Orientation orientation, int role, HeaderGroup headerGroup) const
Reimplement this to provide different header data.
Represents a PIM item stored in Akonadi storage.
Definition item.h:100
qint64 Id
Describes the unique id type.
Definition item.h:105
@ AddIfMissing
Creates the attribute if it is missing.
Definition item.h:306
Id id() const
Returns the unique identifier of the item.
Definition item.cpp:63
Attribute * attribute(const QByteArray &name)
Returns the attribute of the given type name if available, 0 otherwise.
QString remoteId() const
Returns the remote id of the item.
Definition item.cpp:73
bool isValid() const
Returns whether the item is valid.
Definition item.cpp:88
@ UrlWithMimeType
A url with identifier and mimetype.
Definition item.h:604
static Item fromUrl(const QUrl &url)
Creates an item from the given url.
Definition item.cpp:386
bool isWantedItem(const Item &item) const
Checks whether a given item has one of the wanted MIME types.
bool isWantedCollection(const Collection &collection) const
Checks whether a given collection has one of the wanted MIME types.
QStringList wantedMimeTypes() const
Returns the list of wanted MIME types this instance checks against.
void setWantedMimeTypes(const QStringList &mimeTypes)
Sets the list of wanted MIME types this instance checks against.
Monitors an item or collection for changes.
Definition monitor.h:71
virtual QString type() const
Definition nodetree.cpp:47
Q_SCRIPTABLE QString start(QString train="")
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
Helper integration between Akonadi and Qt.
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
QAbstractItemModel(QObject *parent)
QModelIndex createIndex(int row, int column, const void *ptr) const const
virtual Qt::ItemFlags flags(const QModelIndex &index) const const
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const const
virtual bool insertColumns(int column, int count, const QModelIndex &parent)
virtual bool insertRows(int row, int count, const QModelIndex &parent)
virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const const
virtual bool removeColumns(int column, int count, const QModelIndex &parent)
virtual bool removeRows(int row, int count, const QModelIndex &parent)
virtual bool setData(const QModelIndex &index, const QVariant &value, int role)
const_reference at(qsizetype i) const const
void prepend(parameter_type value)
qsizetype size() const const
T value(qsizetype i) const const
int column() const const
bool isValid() const const
QObject(QObject *parent)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QObject * parent() const const
T qobject_cast(QObject *object)
bool isEmpty() const const
QString number(double n, char format, int precision)
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
typedef DropActions
DecorationRole
typedef ItemFlags
typedef MatchFlags
Orientation
void setQuery(const QString &query, ParsingMode mode)
QString url(FormattingOptions options) const const
QVariant fromValue(T &&value)
bool isValid() const const
bool toBool() const const
qlonglong toLongLong(bool *ok) const const
QString toString() const const
T value() const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Mar 28 2025 11:53:21 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.