Akonadi

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

KDE's Doxygen guidelines are available online.