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

KDE's Doxygen guidelines are available online.