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

KDE's Doxygen guidelines are available online.