Akonadi

entitytreemodel.cpp
1 /*
2  SPDX-FileCopyrightText: 2008 Stephen Kelly <[email protected]>
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 
33 Q_DECLARE_METATYPE(QSet<QByteArray>)
34 
35 using namespace Akonadi;
36 
38  : QAbstractItemModel(parent)
39  , d_ptr(new EntityTreeModelPrivate(this))
40 {
42  d->init(monitor);
43 }
44 
45 EntityTreeModel::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 
90 void EntityTreeModel::setCollectionMonitored(const Collection &col, bool monitored)
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 
115 QHash<int, QByteArray> EntityTreeModel::roleNames() const
116 {
117  return {
118  {Qt::DecorationRole, "decoration"},
119  {Qt::DisplayRole, "display"},
120 
121  {EntityTreeModel::ItemIdRole, "itemId"},
122  {EntityTreeModel::ItemRole, "item"},
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 
143 int 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 
153 QVariant 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:
161  if (const auto *attr = item.attribute<EntityDisplayAttribute>(); attr && !attr->displayName().isEmpty()) {
162  return attr->displayName();
163  } else if (!item.remoteId().isEmpty()) {
164  return item.remoteId();
165  }
166  return QString(QLatin1Char('<') + QString::number(item.id()) + QLatin1Char('>'));
167  case Qt::DecorationRole:
168  if (const auto *attr = item.attribute<EntityDisplayAttribute>(); attr && !attr->iconName().isEmpty()) {
169  return d->iconForName(attr->iconName());
170  }
171  break;
172  default:
173  break;
174  }
175  }
176 
177  return QVariant();
178 }
179 
180 QVariant EntityTreeModel::entityData(const Collection &collection, int column, int role) const
181 {
182  Q_D(const EntityTreeModel);
183 
184  if (column != 0) {
185  return QString();
186  }
187 
188  if (collection == Collection::root()) {
189  // Only display the root collection. It may not be edited.
190  if (role == Qt::DisplayRole) {
191  return d->m_rootCollectionDisplayName;
192  } else if (role == Qt::EditRole) {
193  return QVariant();
194  }
195  }
196 
197  switch (role) {
198  case Qt::DisplayRole:
199  case Qt::EditRole:
200  if (column == 0) {
201  if (const QString displayName = collection.displayName(); !displayName.isEmpty()) {
202  return displayName;
203  } else {
204  return i18nc("@info:status", "Loading...");
205  }
206  }
207  break;
208  case Qt::DecorationRole:
209  if (const auto *const attr = collection.attribute<EntityDisplayAttribute>(); attr && !attr->iconName().isEmpty()) {
210  return d->iconForName(attr->iconName());
211  }
212  return d->iconForName(CollectionUtils::defaultIconName(collection));
213  default:
214  break;
215  }
216 
217  return QVariant();
218 }
219 
220 QVariant EntityTreeModel::data(const QModelIndex &index, int role) const
221 {
222  Q_D(const EntityTreeModel);
223  if (role == SessionRole) {
224  return QVariant::fromValue(qobject_cast<QObject *>(d->m_session));
225  }
226 
227  // Ugly, but at least the API is clean.
228  const auto headerGroup = static_cast<HeaderGroup>((role / static_cast<int>(TerminalUserRole)));
229 
230  role %= TerminalUserRole;
231  if (!index.isValid()) {
232  if (ColumnCountRole != role) {
233  return QVariant();
234  }
235 
236  return entityColumnCount(headerGroup);
237  }
238 
239  if (ColumnCountRole == role) {
240  return entityColumnCount(headerGroup);
241  }
242 
243  const Node *node = reinterpret_cast<Node *>(index.internalPointer());
244 
245  if (ParentCollectionRole == role && d->m_collectionFetchStrategy != FetchNoCollections) {
246  const Collection parentCollection = d->m_collections.value(node->parent);
247  Q_ASSERT(parentCollection.isValid());
248 
249  return QVariant::fromValue(parentCollection);
250  }
251 
252  if (Node::Collection == node->type) {
253  const Collection collection = d->m_collections.value(node->id);
254  if (!collection.isValid()) {
255  return QVariant();
256  }
257 
258  switch (role) {
259  case MimeTypeRole:
260  return collection.mimeType();
261  case RemoteIdRole:
262  return collection.remoteId();
263  case CollectionIdRole:
264  return collection.id();
265  case ItemIdRole:
266  // QVariant().toInt() is 0, not -1, so we have to handle the ItemIdRole
267  // and CollectionIdRole (below) specially
268  return -1;
269  case CollectionRole:
270  return QVariant::fromValue(collection);
271  case EntityUrlRole:
272  return collection.url().url();
273  case UnreadCountRole:
274  return collection.statistics().unreadCount();
275  case FetchStateRole:
276  return d->m_pendingCollectionRetrieveJobs.contains(collection.id()) ? FetchingState : IdleState;
277  case IsPopulatedRole:
278  return d->m_populatedCols.contains(collection.id());
280  return entityData(collection, index.column(), Qt::DisplayRole);
281  case PendingCutRole:
282  return d->m_pendingCutCollections.contains(node->id);
283  case Qt::BackgroundRole:
284  if (const auto *const attr = collection.attribute<EntityDisplayAttribute>(); attr && attr->backgroundColor().isValid()) {
285  return attr->backgroundColor();
286  }
287  Q_FALLTHROUGH();
288  default:
289  return entityData(collection, index.column(), role);
290  }
291 
292  } else if (Node::Item == node->type) {
293  const Item item = d->m_items.value(node->id);
294  if (!item.isValid()) {
295  return QVariant();
296  }
297 
298  switch (role) {
300  return QVariant::fromValue(item.parentCollection());
301  case MimeTypeRole:
302  return item.mimeType();
303  case RemoteIdRole:
304  return item.remoteId();
305  case ItemRole:
306  return QVariant::fromValue(item);
307  case ItemIdRole:
308  return item.id();
309  case CollectionIdRole:
310  return -1;
311  case LoadedPartsRole:
313  case AvailablePartsRole:
315  case EntityUrlRole:
316  return item.url(Akonadi::Item::UrlWithMimeType).url();
317  case PendingCutRole:
318  return d->m_pendingCutItems.contains(node->id);
319  case Qt::BackgroundRole:
320  if (const auto *const attr = item.attribute<EntityDisplayAttribute>(); attr && attr->backgroundColor().isValid()) {
321  return attr->backgroundColor();
322  }
323  Q_FALLTHROUGH();
324  default:
325  return entityData(item, index.column(), role);
326  }
327  }
328 
329  return QVariant();
330 }
331 
332 Qt::ItemFlags EntityTreeModel::flags(const QModelIndex &index) const
333 {
334  Q_D(const EntityTreeModel);
335  // Pass modeltest.
336  if (!index.isValid()) {
337  return {};
338  }
339 
341 
342  const Node *node = reinterpret_cast<Node *>(index.internalPointer());
343 
344  if (Node::Collection == node->type) {
345  const Collection collection = d->m_collections.value(node->id);
346  if (collection.isValid()) {
347  if (collection == Collection::root()) {
348  // Selectable and displayable only.
349  return flags;
350  }
351 
352  const int rights = collection.rights();
353 
354  if (rights & Collection::CanChangeCollection) {
355  if (index.column() == 0) {
356  flags |= Qt::ItemIsEditable;
357  }
358  // Changing the collection includes changing the metadata (child entityordering).
359  // Need to allow this by drag and drop.
360  flags |= Qt::ItemIsDropEnabled;
361  }
363  // Can we drop new collections and items into this collection?
364  flags |= Qt::ItemIsDropEnabled;
365  }
366 
367  // dragging is always possible, even for read-only objects, but they can only be copied, not moved.
368  flags |= Qt::ItemIsDragEnabled;
369  }
370  } else if (Node::Item == node->type) {
371  // cut out entities are shown as disabled
372  // TODO: Not sure this is wanted, it prevents any interaction with them, better
373  // solution would be to move this to the delegate, as was done for collections.
374  if (d->m_pendingCutItems.contains(node->id)) {
375  return Qt::ItemIsSelectable;
376  }
377 
378  // Rights come from the parent collection.
379 
380  Collection parentCollection;
381  if (!index.parent().isValid()) {
382  parentCollection = d->m_rootCollection;
383  } else {
384  const Node *parentNode = reinterpret_cast<Node *>(index.parent().internalPointer());
385  parentCollection = d->m_collections.value(parentNode->id);
386  }
387  if (parentCollection.isValid()) {
388  const int rights = parentCollection.rights();
389 
390  // Can't drop onto items.
391  if (rights & Collection::CanChangeItem && index.column() == 0) {
392  flags |= Qt::ItemIsEditable;
393  }
394  // dragging is always possible, even for read-only objects, but they can only be copied, not moved.
395  flags |= Qt::ItemIsDragEnabled;
396  }
397  }
398 
399  return flags;
400 }
401 
402 Qt::DropActions EntityTreeModel::supportedDropActions() const
403 {
405 }
406 
407 QStringList EntityTreeModel::mimeTypes() const
408 {
409  // TODO: Should this return the mimetypes that the items provide? Allow dragging a contact from here for example.
410  return {QStringLiteral("text/uri-list")};
411 }
412 
413 bool EntityTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
414 {
415  Q_UNUSED(row)
416  Q_UNUSED(column)
418 
419  // Can't drop onto Collection::root.
420  if (!parent.isValid()) {
421  return false;
422  }
423 
424  // TODO Use action and collection rights and return false if necessary
425 
426  // if row and column are -1, then the drop was on parent directly.
427  // data should then be appended on the end of the items of the collections as appropriate.
428  // That will mean begin insert rows etc.
429  // Otherwise it was a sibling of the row^th item of parent.
430  // Needs to be handled when ordering is accounted for.
431 
432  // Handle dropping between items as well as on items.
433  // if ( row != -1 && column != -1 )
434  // {
435  // }
436 
437  if (action == Qt::IgnoreAction) {
438  return true;
439  }
440 
441  // Shouldn't do this. Need to be able to drop vcards for example.
442  // if ( !data->hasFormat( "text/uri-list" ) )
443  // return false;
444 
445  Node *node = reinterpret_cast<Node *>(parent.internalId());
446 
447  Q_ASSERT(node);
448 
449  if (Node::Item == node->type) {
450  if (!parent.parent().isValid()) {
451  // The drop is somehow on an item with no parent (shouldn't happen)
452  // The drop should be considered handled anyway.
453  qCWarning(AKONADICORE_LOG) << "Dropped onto item with no parent collection";
454  return true;
455  }
456 
457  // A drop onto an item should be considered as a drop onto its parent collection
458  node = reinterpret_cast<Node *>(parent.parent().internalId());
459  }
460 
461  if (Node::Collection == node->type) {
462  const Collection destCollection = d->m_collections.value(node->id);
463 
464  // Applications can't create new collections in root. Only resources can.
465  if (destCollection == Collection::root()) {
466  // Accept the event so that it doesn't propagate.
467  return true;
468  }
469 
470  if (data->hasFormat(QStringLiteral("text/uri-list"))) {
471  MimeTypeChecker mimeChecker;
472  mimeChecker.setWantedMimeTypes(destCollection.contentMimeTypes());
473 
474  const QList<QUrl> urls = data->urls();
475  for (const QUrl &url : urls) {
476  const Collection collection = d->m_collections.value(Collection::fromUrl(url).id());
477  if (collection.isValid()) {
478  if (collection.parentCollection().id() == destCollection.id() && action != Qt::CopyAction) {
479  qCWarning(AKONADICORE_LOG) << "Error: source and destination of move are the same.";
480  return false;
481  }
482 
483  if (!mimeChecker.isWantedCollection(collection)) {
484  qCDebug(AKONADICORE_LOG) << "unwanted collection" << mimeChecker.wantedMimeTypes() << collection.contentMimeTypes();
485  return false;
486  }
487 
488  QUrlQuery query(url);
489  if (query.hasQueryItem(QStringLiteral("name"))) {
490  const QString collectionName = query.queryItemValue(QStringLiteral("name"));
491  const QStringList collectionNames = d->childCollectionNames(destCollection);
492 
493  if (collectionNames.contains(collectionName)) {
495  nullptr,
496  i18nc("@window:title", "Error"),
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 
536 QModelIndex 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 
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 
612 int 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 
650 int EntityTreeModel::entityColumnCount(HeaderGroup headerGroup) const
651 {
652  // Not needed in this model.
653  Q_UNUSED(headerGroup)
654 
655  return 1;
656 }
657 
658 QVariant 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) {
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 
674 QVariant 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 
682 QMimeData *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.
718 bool 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 
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 
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 
813 bool EntityTreeModel::canFetchMore(const QModelIndex &parent) const
814 {
815  Q_UNUSED(parent)
816  return false;
817 }
818 
819 void 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 
845 bool 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 
878 QModelIndexList 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) {
883  Collection::Id id;
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 
930 bool EntityTreeModel::insertRows(int /*row*/, int /*count*/, const QModelIndex & /*parent*/)
931 {
932  return false;
933 }
934 
935 bool EntityTreeModel::insertColumns(int /*column*/, int /*count*/, const QModelIndex & /*parent*/)
936 {
937  return false;
938 }
939 
940 bool EntityTreeModel::removeRows(int /*row*/, int /*count*/, const QModelIndex & /*parent*/)
941 {
942  return false;
943 }
944 
945 bool 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 
978 {
979  Q_D(const EntityTreeModel);
980  return d->m_itemPopulation;
981 }
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,
1025  SIGNAL(collectionMoved(Akonadi::Collection, Akonadi::Collection, Akonadi::Collection)),
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 
1042 static 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 
1057 static 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 
1078 QModelIndexList 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();
1104  proxy = qobject_cast<const QAbstractProxyModel *>(_model);
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"
bool isValid() const
Returns whether the item is valid.
Definition: item.cpp:88
@ UrlWithMimeType
A url with identifier and mimetype.
Definition: item.h:617
@ ItemListHeaders
Header information for a list of items.
@ IdleState
There is no fetch of items in this collection in progress.
static Collection updatedCollection(const QAbstractItemModel *model, qint64 collectionId)
Returns an Akonadi::Collection from the model based on given collectionId.
void setCollectionsMonitored(const Akonadi::Collection::List &collections)
Monitors the specified collections and resets the model.
A model for collections and items together.
ItemPopulationStrategy itemPopulationStrategy() const
Returns the item population strategy of the model.
EntityTreeModel(Monitor *monitor, QObject *parent=nullptr)
Creates a new entity tree model.
virtual QString type() const
Definition: nodetree.cpp:25
bool isValid() const const
static Collection fromUrl(const QUrl &url)
Creates a collection from the given url.
Definition: collection.cpp:267
DecorationRole
~EntityTreeModel() override
Destroys the entity tree model.
virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const const
Job that modifies a collection in the Akonadi storage.
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QString number(int n, int base)
virtual bool removeRows(int row, int count, const QModelIndex &parent)
@ NoItemPopulation
Do not include items in the model.
QVariant fromValue(const T &value)
void * internalPointer() const const
@ CanCreateCollection
Can create new subcollections in this collection.
Definition: collection.h:95
void setUrls(const QList< QUrl > &urls)
void setWantedMimeTypes(const QStringList &mimeTypes)
Sets the list of wanted MIME types this instance checks against.
@ CanChangeItem
Can change items in this collection.
Definition: collection.h:91
void setItemPopulationStrategy(ItemPopulationStrategy strategy)
Sets the item population strategy of the model.
@ CanCreateItem
Can create new items in this collection.
Definition: collection.h:92
@ LoadedPartsRole
Parts available in the model for the item.
QString rootCollectionDisplayName() const
Returns the display name of the root collection.
QUrl url(UrlType type=UrlShort) const
Returns the url of the collection.
Definition: collection.cpp:253
int column() const const
T value() const const
@ EntityUrlRole
The akonadi:/ Url of the entity as a string. Item urls will contain the mimetype.
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QString url(QUrl::FormattingOptions options) const const
Q_SCRIPTABLE Q_NOREPLY void start()
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.
@ CollectionIdRole
The collection id.
QStringList wantedMimeTypes() const
Returns the list of wanted MIME types this instance checks against.
Represents a collection of PIM items.
Definition: collection.h:61
bool isCollectionTreeFetched() const
Returns whether the collection tree has been fetched at initialisation.
Attribute that stores the properties that are used to display an entity.
@ NoFilter
No filtering, retrieve all collections.
qlonglong toLongLong(bool *ok) const const
QString displayName() const
Returns the name that should be used for display.
@ RemoteIdRole
The remoteId of the entity.
@ OriginalCollectionNameRole
Returns original name for collection.
Definition: item.h:33
QString iconName() const
Returns the icon name of the icon returned by icon().
Monitors an item or collection for changes.
Definition: monitor.h:71
CollectionFetchStrategy collectionFetchStrategy() const
Returns the collection fetch strategy of the model.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
Akonadi::CollectionFetchScope::ListFilter listFilter() const
Returns the currently used listfilter.
void clearAndReset()
Clears and resets the model.
void setListFilter(Akonadi::CollectionFetchScope::ListFilter filter)
Sets the currently used listfilter.
void setRootCollectionDisplayName(const QString &name)
Sets the display name of the root collection of the model.
qint64 Id
Describes the unique id type.
Definition: item.h:111
Attribute * attribute(const QByteArray &name)
Returns the attribute of the given type name if available, 0 otherwise.
Definition: collection.cpp:176
QString mimeType() const
Returns the mime type of the item.
Definition: item.cpp:331
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
QVariant data(int role) const const
int size() const const
void setBackgroundColor(const QColor &color)
Sets the backgroundColor to color.
void prepend(const T &value)
QUrl url(UrlType type=UrlShort) const
Returns the url of the item.
Definition: item.cpp:377
QModelIndex createIndex(int row, int column, void *ptr) const const
bool includeRootCollection() const
Returns whether the root collection is provided by the model.
@ CollectionTreeHeaders
Header information for a collection-only tree.
typedef ItemFlags
Job that modifies an existing item in the Akonadi storage.
Definition: itemmodifyjob.h:81
QString i18n(const char *text, const TYPE &arg...)
virtual bool removeColumns(int column, int count, const QModelIndex &parent)
@ UnreadCountRole
Returns the number of unread items in a collection.
@ MimeTypeRole
The mimetype of the entity.
@ InvisibleCollectionFetch
Fetches collections, but does not put them in the model.
@ CollectionRole
The collection.
static Item fromUrl(const QUrl &url)
Creates an item from the given url.
Definition: item.cpp:391
ListFilter
Describes the list filter.
@ CanChangeCollection
Can change this collection.
Definition: collection.h:94
bool isFullyPopulated() const
Returns whether the model is fully populated.
Orientation
bool isEmpty() const const
qint64 unreadCount() const
Returns the number of unread items in this collection or -1 if this information is not available.
static QModelIndexList modelIndexesForItem(const QAbstractItemModel *model, const Item &item)
Returns a QModelIndex in model which points to item.
bool hasAttribute(const QByteArray &name) const
Returns true if the collection has an attribute of the given type name, false otherwise.
Definition: collection.cpp:161
const T & at(int i) const const
virtual QVariant entityData(const Item &item, int column, int role=Qt::DisplayRole) const
Provided for convenience of subclasses.
Collection parentCollection() const
Returns the parent collection of this object.
Definition: item.cpp:153
virtual Qt::ItemFlags flags(const QModelIndex &index) const const
bool isWantedCollection(const Collection &collection) const
Checks whether a given collection has one of the wanted MIME types.
@ UrlWithName
A url with identifier and name.
Definition: collection.h:410
@ FetchNoCollections
Fetches nothing. This creates an empty model.
static QString mimeType()
Returns the mimetype used for collections.
Definition: collection.cpp:292
@ CollectionChildOrderRole
Ordered list of child items if available.
virtual bool hasFormat(const QString &mimeType) const const
bool isValid() const const
ItemPopulationStrategy
Describes how the model should populated its items.
static Collection root()
Returns the root collection.
Definition: collection.cpp:287
@ AvailablePartsRole
Parts available in the Akonadi server for the item.
QColor backgroundColor() const
Returns the backgroundColor or an invalid color if none is set.
bool toBool() const const
QString remoteId() const
Returns the remote id of the collection.
Definition: collection.cpp:106
Collection parentCollection() const
Returns the parent collection of this object.
Definition: collection.cpp:187
void setQuery(const QString &query, QUrl::ParsingMode mode)
@ LazyPopulation
Fetch items only when requested (using canFetchMore/fetchMore)
@ PendingCutRole
Used to indicate items which are to be cut.
void setCollectionFetchStrategy(CollectionFetchStrategy strategy)
Sets the collection fetch strategy of the model.
@ FetchingState
There is a fetch of items in this collection in progress.
void setDisplayName(const QString &name)
Sets the name that should be used for display.
QList< QUrl > urls() const const
typedef DropActions
virtual QVariant entityHeaderData(int section, Qt::Orientation orientation, int role, HeaderGroup headerGroup) const
Reimplement this to provide different header data.
@ IsPopulatedRole
Returns whether a Collection has been populated, i.e. whether its items have been fetched.
void setIncludeRootCollection(bool include)
Sets whether the root collection shall be provided by the model.
Definition: nodetree.h:12
Id id() const
Returns the unique identifier of the item.
Definition: item.cpp:63
@ ImmediatePopulation
Retrieve items immediately when their parent is in the model. This is the default.
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const const
bool hasAttribute(const QByteArray &name) const
Returns true if the item has an attribute of the given type name, false otherwise.
Definition: item.cpp:128
QSet< QByteArray > availablePayloadParts() const
Returns the parts available for this item.
Definition: item.cpp:491
CollectionStatistics statistics() const
Returns the collection statistics of the collection.
Definition: collection.cpp:326
virtual bool insertColumns(int column, int count, const QModelIndex &parent)
Helper for checking MIME types of Collections and Items.
@ AddIfMissing
Creates the attribute if it is missing.
Definition: item.h:312
QString i18nc(const char *context, const char *text, const TYPE &arg...)
bool isValid() const const
static QModelIndex modelIndexForCollection(const QAbstractItemModel *model, const Collection &collection)
Returns a QModelIndex in model which points to collection.
QModelIndex parent() const const
@ AddIfMissing
Creates the attribute if it is missing.
Definition: collection.h:281
Attribute * attribute(const QByteArray &name)
Returns the attribute of the given type name if available, 0 otherwise.
Definition: item.cpp:143
@ FetchStateRole
Returns the FetchState of a particular item.
void setCollectionMonitored(const Akonadi::Collection &col, bool monitored=true)
Adds or removes a specific collection from the monitored set without resetting the model.
@ TerminalUserRole
Last role for user extensions. Don't use a role beyond this or headerData will break.
bool isWantedItem(const Item &item) const
Checks whether a given item has one of the wanted MIME types.
virtual bool setData(const QModelIndex &index, const QVariant &value, int role)
typedef MatchFlags
QSet< QByteArray > loadedPayloadParts() const
Returns the list of loaded payload parts.
Definition: item.cpp:288
HeaderGroup
Describes what header information the model shall return.
@ ParentCollectionRole
The parent collection of the entity.
CollectionFetchStrategy
Describes what collections shall be fetched by and represent in the model.
@ CanLinkItem
Can create links to existing items in this virtual collection.
Definition: collection.h:97
qint64 Id
Describes the unique id type.
Definition: collection.h:79
void setName(const QString &name)
Sets the i18n'ed name of the collection.
Definition: collection.cpp:221
QMessageBox::StandardButton critical(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
QObject * parent() const const
T value(int i) const const
QString remoteId() const
Returns the remote id of the item.
Definition: item.cpp:73
Represents a PIM item stored in Akonadi storage.
Definition: item.h:105
Q_D(Todo)
bool isCollectionPopulated(Akonadi::Collection::Id) const
Returns whether the collection has been populated.
QString toString() const const
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,...
Helper integration between Akonadi and Qt.
virtual bool insertRows(int row, int count, const QModelIndex &parent)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon May 8 2023 03:52:16 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.