File indexing completed on 2024-09-22 04:41:01

0001 /*
0002     SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "entitytreemodel.h"
0008 #include "akonadicore_debug.h"
0009 #include "entitytreemodel_p.h"
0010 #include "monitor_p.h"
0011 
0012 #include <QAbstractProxyModel>
0013 #include <QHash>
0014 #include <QMessageBox>
0015 #include <QMimeData>
0016 
0017 #include <KLocalizedString>
0018 #include <QUrl>
0019 #include <QUrlQuery>
0020 
0021 #include "collectionmodifyjob.h"
0022 #include "entitydisplayattribute.h"
0023 #include "itemmodifyjob.h"
0024 #include "monitor.h"
0025 #include "session.h"
0026 
0027 #include "collectionutils.h"
0028 
0029 #include "pastehelper_p.h"
0030 
0031 // clazy:excludeall=old-style-connect
0032 
0033 Q_DECLARE_METATYPE(QSet<QByteArray>)
0034 
0035 using namespace Akonadi;
0036 
0037 EntityTreeModel::EntityTreeModel(Monitor *monitor, QObject *parent)
0038     : QAbstractItemModel(parent)
0039     , d_ptr(new EntityTreeModelPrivate(this))
0040 {
0041     Q_D(EntityTreeModel);
0042     d->init(monitor);
0043 }
0044 
0045 EntityTreeModel::EntityTreeModel(Monitor *monitor, EntityTreeModelPrivate *d, QObject *parent)
0046     : QAbstractItemModel(parent)
0047     , d_ptr(d)
0048 {
0049     d->init(monitor);
0050 }
0051 
0052 EntityTreeModel::~EntityTreeModel()
0053 {
0054     Q_D(EntityTreeModel);
0055 
0056     for (const QList<Node *> &list : std::as_const(d->m_childEntities)) {
0057         qDeleteAll(list);
0058     }
0059 }
0060 
0061 CollectionFetchScope::ListFilter EntityTreeModel::listFilter() const
0062 {
0063     Q_D(const EntityTreeModel);
0064     return d->m_listFilter;
0065 }
0066 
0067 void EntityTreeModel::setListFilter(CollectionFetchScope::ListFilter filter)
0068 {
0069     Q_D(EntityTreeModel);
0070     d->beginResetModel();
0071     d->m_listFilter = filter;
0072     d->m_monitor->setAllMonitored(filter == CollectionFetchScope::NoFilter);
0073     d->endResetModel();
0074 }
0075 
0076 void EntityTreeModel::setCollectionsMonitored(const Collection::List &collections)
0077 {
0078     Q_D(EntityTreeModel);
0079     d->beginResetModel();
0080     const Akonadi::Collection::List lstCols = d->m_monitor->collectionsMonitored();
0081     for (const Akonadi::Collection &col : lstCols) {
0082         d->m_monitor->setCollectionMonitored(col, false);
0083     }
0084     for (const Akonadi::Collection &col : collections) {
0085         d->m_monitor->setCollectionMonitored(col, true);
0086     }
0087     d->endResetModel();
0088 }
0089 
0090 void EntityTreeModel::setCollectionMonitored(const Collection &col, bool monitored)
0091 {
0092     Q_D(EntityTreeModel);
0093     d->m_monitor->setCollectionMonitored(col, monitored);
0094 }
0095 
0096 bool EntityTreeModel::systemEntitiesShown() const
0097 {
0098     Q_D(const EntityTreeModel);
0099     return d->m_showSystemEntities;
0100 }
0101 
0102 void EntityTreeModel::setShowSystemEntities(bool show)
0103 {
0104     Q_D(EntityTreeModel);
0105     d->m_showSystemEntities = show;
0106 }
0107 
0108 void EntityTreeModel::clearAndReset()
0109 {
0110     Q_D(EntityTreeModel);
0111     d->beginResetModel();
0112     d->endResetModel();
0113 }
0114 
0115 QHash<int, QByteArray> EntityTreeModel::roleNames() const
0116 {
0117     return {
0118         {Qt::DecorationRole, "decoration"},
0119         {Qt::DisplayRole, "display"},
0120         {EntityTreeModel::DisplayNameRole, "displayName"},
0121 
0122         {EntityTreeModel::ItemIdRole, "itemId"},
0123         {EntityTreeModel::ItemRole, "item"},
0124         {EntityTreeModel::CollectionIdRole, "collectionId"},
0125         {EntityTreeModel::CollectionRole, "collection"},
0126 
0127         {EntityTreeModel::UnreadCountRole, "unreadCount"},
0128         {EntityTreeModel::EntityUrlRole, "url"},
0129         {EntityTreeModel::RemoteIdRole, "remoteId"},
0130         {EntityTreeModel::IsPopulatedRole, "isPopulated"},
0131         {EntityTreeModel::CollectionRole, "collection"},
0132         {EntityTreeModel::MimeTypeRole, "mimeType"},
0133         {EntityTreeModel::CollectionChildOrderRole, "collectionChildOrder"},
0134         {EntityTreeModel::ParentCollectionRole, "parentCollection"},
0135         {EntityTreeModel::SessionRole, "session"},
0136         {EntityTreeModel::PendingCutRole, "pendingCut"},
0137         {EntityTreeModel::LoadedPartsRole, "loadedParts"},
0138         {EntityTreeModel::AvailablePartsRole, "availableParts"},
0139         {EntityTreeModel::UnreadCountRole, "unreadCount"},
0140         {EntityTreeModel::FetchStateRole, "fetchState"},
0141     };
0142 }
0143 
0144 int EntityTreeModel::columnCount(const QModelIndex &parent) const
0145 {
0146     // TODO: Statistics?
0147     if (parent.isValid() && parent.column() != 0) {
0148         return 0;
0149     }
0150 
0151     return qMax(entityColumnCount(CollectionTreeHeaders), entityColumnCount(ItemListHeaders));
0152 }
0153 
0154 QVariant EntityTreeModel::entityData(const Item &item, int column, int role) const
0155 {
0156     Q_D(const EntityTreeModel);
0157 
0158     if (column == 0) {
0159         switch (role) {
0160         case Qt::DisplayRole:
0161         case Qt::EditRole:
0162         case EntityTreeModel::DisplayNameRole:
0163             if (const auto *attr = item.attribute<EntityDisplayAttribute>(); attr && !attr->displayName().isEmpty()) {
0164                 return attr->displayName();
0165             } else if (!item.remoteId().isEmpty()) {
0166                 return item.remoteId();
0167             }
0168             return QString(QLatin1Char('<') + QString::number(item.id()) + QLatin1Char('>'));
0169         case Qt::DecorationRole:
0170             if (const auto *attr = item.attribute<EntityDisplayAttribute>(); attr && !attr->iconName().isEmpty()) {
0171                 return d->iconForName(attr->iconName());
0172             }
0173             break;
0174         default:
0175             break;
0176         }
0177     }
0178 
0179     return QVariant();
0180 }
0181 
0182 QVariant EntityTreeModel::entityData(const Collection &collection, int column, int role) const
0183 {
0184     Q_D(const EntityTreeModel);
0185 
0186     if (column != 0) {
0187         return QString();
0188     }
0189 
0190     if (collection == Collection::root()) {
0191         // Only display the root collection. It may not be edited.
0192         if (role == Qt::DisplayRole || role == EntityTreeModel::DisplayNameRole) {
0193             return d->m_rootCollectionDisplayName;
0194         } else if (role == Qt::EditRole) {
0195             return QVariant();
0196         }
0197     }
0198 
0199     switch (role) {
0200     case Qt::DisplayRole:
0201     case Qt::EditRole:
0202     case EntityTreeModel::DisplayNameRole:
0203         if (column == 0) {
0204             if (const QString displayName = collection.displayName(); !displayName.isEmpty()) {
0205                 return displayName;
0206             } else {
0207                 return i18nc("@info:status", "Loading...");
0208             }
0209         }
0210         break;
0211     case Qt::DecorationRole:
0212         if (const auto *const attr = collection.attribute<EntityDisplayAttribute>(); attr && !attr->iconName().isEmpty()) {
0213             return d->iconForName(attr->iconName());
0214         }
0215         return d->iconForName(CollectionUtils::defaultIconName(collection));
0216     default:
0217         break;
0218     }
0219 
0220     return QVariant();
0221 }
0222 
0223 QVariant EntityTreeModel::data(const QModelIndex &index, int role) const
0224 {
0225     Q_D(const EntityTreeModel);
0226     if (role == SessionRole) {
0227         return QVariant::fromValue(qobject_cast<QObject *>(d->m_session));
0228     }
0229 
0230     // Ugly, but at least the API is clean.
0231     const auto headerGroup = static_cast<HeaderGroup>((role / static_cast<int>(TerminalUserRole)));
0232 
0233     role %= TerminalUserRole;
0234     if (!index.isValid()) {
0235         if (ColumnCountRole != role) {
0236             return QVariant();
0237         }
0238 
0239         return entityColumnCount(headerGroup);
0240     }
0241 
0242     if (ColumnCountRole == role) {
0243         return entityColumnCount(headerGroup);
0244     }
0245 
0246     const Node *node = reinterpret_cast<Node *>(index.internalPointer());
0247 
0248     if (ParentCollectionRole == role && d->m_collectionFetchStrategy != FetchNoCollections) {
0249         const Collection parentCollection = d->m_collections.value(node->parent);
0250         Q_ASSERT(parentCollection.isValid());
0251 
0252         return QVariant::fromValue(parentCollection);
0253     }
0254 
0255     if (Node::Collection == node->type) {
0256         const Collection collection = d->m_collections.value(node->id);
0257         if (!collection.isValid()) {
0258             return QVariant();
0259         }
0260 
0261         switch (role) {
0262         case MimeTypeRole:
0263             return collection.mimeType();
0264         case RemoteIdRole:
0265             return collection.remoteId();
0266         case CollectionIdRole:
0267             return collection.id();
0268         case ItemIdRole:
0269             // QVariant().toInt() is 0, not -1, so we have to handle the ItemIdRole
0270             // and CollectionIdRole (below) specially
0271             return -1;
0272         case CollectionRole:
0273             return QVariant::fromValue(collection);
0274         case EntityUrlRole:
0275             return collection.url().url();
0276         case UnreadCountRole:
0277             return collection.statistics().unreadCount();
0278         case FetchStateRole:
0279             return d->m_pendingCollectionRetrieveJobs.contains(collection.id()) ? FetchingState : IdleState;
0280         case IsPopulatedRole:
0281             return d->m_populatedCols.contains(collection.id());
0282         case OriginalCollectionNameRole:
0283             return entityData(collection, index.column(), Qt::DisplayRole);
0284         case PendingCutRole:
0285             return d->m_pendingCutCollections.contains(node->id);
0286         case Qt::BackgroundRole:
0287             if (const auto *const attr = collection.attribute<EntityDisplayAttribute>(); attr && attr->backgroundColor().isValid()) {
0288                 return attr->backgroundColor();
0289             }
0290             [[fallthrough]];
0291         default:
0292             return entityData(collection, index.column(), role);
0293         }
0294 
0295     } else if (Node::Item == node->type) {
0296         const Item item = d->m_items.value(node->id);
0297         if (!item.isValid()) {
0298             return QVariant();
0299         }
0300 
0301         switch (role) {
0302         case ParentCollectionRole:
0303             return QVariant::fromValue(item.parentCollection());
0304         case MimeTypeRole:
0305             return item.mimeType();
0306         case RemoteIdRole:
0307             return item.remoteId();
0308         case ItemRole:
0309             return QVariant::fromValue(item);
0310         case ItemIdRole:
0311             return item.id();
0312         case CollectionIdRole:
0313             return -1;
0314         case LoadedPartsRole:
0315             return QVariant::fromValue(item.loadedPayloadParts());
0316         case AvailablePartsRole:
0317             return QVariant::fromValue(item.availablePayloadParts());
0318         case EntityUrlRole:
0319             return item.url(Akonadi::Item::UrlWithMimeType).url();
0320         case PendingCutRole:
0321             return d->m_pendingCutItems.contains(node->id);
0322         case Qt::BackgroundRole:
0323             if (const auto *const attr = item.attribute<EntityDisplayAttribute>(); attr && attr->backgroundColor().isValid()) {
0324                 return attr->backgroundColor();
0325             }
0326             [[fallthrough]];
0327         default:
0328             return entityData(item, index.column(), role);
0329         }
0330     }
0331 
0332     return QVariant();
0333 }
0334 
0335 Qt::ItemFlags EntityTreeModel::flags(const QModelIndex &index) const
0336 {
0337     Q_D(const EntityTreeModel);
0338     // Pass modeltest.
0339     if (!index.isValid()) {
0340         return {};
0341     }
0342 
0343     Qt::ItemFlags flags = QAbstractItemModel::flags(index);
0344 
0345     const Node *node = reinterpret_cast<Node *>(index.internalPointer());
0346 
0347     if (Node::Collection == node->type) {
0348         const Collection collection = d->m_collections.value(node->id);
0349         if (collection.isValid()) {
0350             if (collection == Collection::root()) {
0351                 // Selectable and displayable only.
0352                 return flags;
0353             }
0354 
0355             const int rights = collection.rights();
0356 
0357             if (rights & Collection::CanChangeCollection) {
0358                 if (index.column() == 0) {
0359                     flags |= Qt::ItemIsEditable;
0360                 }
0361                 // Changing the collection includes changing the metadata (child entityordering).
0362                 // Need to allow this by drag and drop.
0363                 flags |= Qt::ItemIsDropEnabled;
0364             }
0365             if (rights & (Collection::CanCreateCollection | Collection::CanCreateItem | Collection::CanLinkItem)) {
0366                 // Can we drop new collections and items into this collection?
0367                 flags |= Qt::ItemIsDropEnabled;
0368             }
0369 
0370             // dragging is always possible, even for read-only objects, but they can only be copied, not moved.
0371             flags |= Qt::ItemIsDragEnabled;
0372         }
0373     } else if (Node::Item == node->type) {
0374         // cut out entities are shown as disabled
0375         // TODO: Not sure this is wanted, it prevents any interaction with them, better
0376         // solution would be to move this to the delegate, as was done for collections.
0377         if (d->m_pendingCutItems.contains(node->id)) {
0378             return Qt::ItemIsSelectable;
0379         }
0380 
0381         // Rights come from the parent collection.
0382 
0383         Collection parentCollection;
0384         if (!index.parent().isValid()) {
0385             parentCollection = d->m_rootCollection;
0386         } else {
0387             const Node *parentNode = reinterpret_cast<Node *>(index.parent().internalPointer());
0388             parentCollection = d->m_collections.value(parentNode->id);
0389         }
0390         if (parentCollection.isValid()) {
0391             const int rights = parentCollection.rights();
0392 
0393             // Can't drop onto items.
0394             if (rights & Collection::CanChangeItem && index.column() == 0) {
0395                 flags |= Qt::ItemIsEditable;
0396             }
0397             // dragging is always possible, even for read-only objects, but they can only be copied, not moved.
0398             flags |= Qt::ItemIsDragEnabled;
0399         }
0400     }
0401 
0402     return flags;
0403 }
0404 
0405 Qt::DropActions EntityTreeModel::supportedDropActions() const
0406 {
0407     return (Qt::CopyAction | Qt::MoveAction | Qt::LinkAction);
0408 }
0409 
0410 QStringList EntityTreeModel::mimeTypes() const
0411 {
0412     // TODO: Should this return the mimetypes that the items provide? Allow dragging a contact from here for example.
0413     return {QStringLiteral("text/uri-list")};
0414 }
0415 
0416 bool EntityTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
0417 {
0418     Q_UNUSED(row)
0419     Q_UNUSED(column)
0420     Q_D(EntityTreeModel);
0421 
0422     // Can't drop onto Collection::root.
0423     if (!parent.isValid()) {
0424         return false;
0425     }
0426 
0427     // TODO Use action and collection rights and return false if necessary
0428 
0429     // if row and column are -1, then the drop was on parent directly.
0430     // data should then be appended on the end of the items of the collections as appropriate.
0431     // That will mean begin insert rows etc.
0432     // Otherwise it was a sibling of the row^th item of parent.
0433     // Needs to be handled when ordering is accounted for.
0434 
0435     // Handle dropping between items as well as on items.
0436     //   if ( row != -1 && column != -1 )
0437     //   {
0438     //   }
0439 
0440     if (action == Qt::IgnoreAction) {
0441         return true;
0442     }
0443 
0444     // Shouldn't do this. Need to be able to drop vcards for example.
0445     //   if ( !data->hasFormat( "text/uri-list" ) )
0446     //       return false;
0447 
0448     Node *node = reinterpret_cast<Node *>(parent.internalId());
0449 
0450     Q_ASSERT(node);
0451 
0452     if (Node::Item == node->type) {
0453         if (!parent.parent().isValid()) {
0454             // The drop is somehow on an item with no parent (shouldn't happen)
0455             // The drop should be considered handled anyway.
0456             qCWarning(AKONADICORE_LOG) << "Dropped onto item with no parent collection";
0457             return true;
0458         }
0459 
0460         // A drop onto an item should be considered as a drop onto its parent collection
0461         node = reinterpret_cast<Node *>(parent.parent().internalId());
0462     }
0463 
0464     if (Node::Collection == node->type) {
0465         const Collection destCollection = d->m_collections.value(node->id);
0466 
0467         // Applications can't create new collections in root. Only resources can.
0468         if (destCollection == Collection::root()) {
0469             // Accept the event so that it doesn't propagate.
0470             return true;
0471         }
0472 
0473         if (data->hasFormat(QStringLiteral("text/uri-list"))) {
0474             MimeTypeChecker mimeChecker;
0475             mimeChecker.setWantedMimeTypes(destCollection.contentMimeTypes());
0476 
0477             const QList<QUrl> urls = data->urls();
0478             for (const QUrl &url : urls) {
0479                 const Collection collection = d->m_collections.value(Collection::fromUrl(url).id());
0480                 if (collection.isValid()) {
0481                     if (collection.parentCollection().id() == destCollection.id() && action != Qt::CopyAction) {
0482                         qCWarning(AKONADICORE_LOG) << "Error: source and destination of move are the same.";
0483                         return false;
0484                     }
0485 
0486                     if (!mimeChecker.isWantedCollection(collection)) {
0487                         qCDebug(AKONADICORE_LOG) << "unwanted collection" << mimeChecker.wantedMimeTypes() << collection.contentMimeTypes();
0488                         return false;
0489                     }
0490 
0491                     QUrlQuery query(url);
0492                     if (query.hasQueryItem(QStringLiteral("name"))) {
0493                         const QString collectionName = query.queryItemValue(QStringLiteral("name"));
0494                         const QStringList collectionNames = d->childCollectionNames(destCollection);
0495 
0496                         if (collectionNames.contains(collectionName)) {
0497                             QMessageBox::critical(
0498                                 nullptr,
0499                                 i18nc("@window:title", "Error"),
0500                                 i18n("The target collection '%1' contains already\na collection with name '%2'.", destCollection.name(), collection.name()));
0501                             return false;
0502                         }
0503                     }
0504                 } else {
0505                     const Item item = d->m_items.value(Item::fromUrl(url).id());
0506                     if (item.isValid()) {
0507                         if (item.parentCollection().id() == destCollection.id() && action != Qt::CopyAction) {
0508                             qCWarning(AKONADICORE_LOG) << "Error: source and destination of move are the same.";
0509                             return false;
0510                         }
0511 
0512                         if (!mimeChecker.isWantedItem(item)) {
0513                             qCDebug(AKONADICORE_LOG) << "unwanted item" << mimeChecker.wantedMimeTypes() << item.mimeType();
0514                             return false;
0515                         }
0516                     }
0517                 }
0518             }
0519 
0520             KJob *job = PasteHelper::pasteUriList(data, destCollection, action, d->m_session);
0521             if (!job) {
0522                 return false;
0523             }
0524 
0525             connect(job, SIGNAL(result(KJob *)), SLOT(pasteJobDone(KJob *)));
0526 
0527             // Accept the event so that it doesn't propagate.
0528             return true;
0529         } else {
0530             //       not a set of uris. Maybe vcards etc. Check if the parent supports them, and maybe do
0531             // fromMimeData for them. Hmm, put it in the same transaction with the above?
0532             // TODO: This should be handled first, not last.
0533         }
0534     }
0535 
0536     return false;
0537 }
0538 
0539 QModelIndex EntityTreeModel::index(int row, int column, const QModelIndex &parent) const
0540 {
0541     Q_D(const EntityTreeModel);
0542 
0543     if (parent.column() > 0) {
0544         return QModelIndex();
0545     }
0546 
0547     // TODO: don't use column count here? Use some d-> func.
0548     if (column >= columnCount() || column < 0) {
0549         return QModelIndex();
0550     }
0551 
0552     QList<Node *> childEntities;
0553 
0554     const Node *parentNode = reinterpret_cast<Node *>(parent.internalPointer());
0555     if (!parentNode || !parent.isValid()) {
0556         if (d->m_showRootCollection) {
0557             childEntities << d->m_childEntities.value(-1);
0558         } else {
0559             childEntities = d->m_childEntities.value(d->m_rootCollection.id());
0560         }
0561     } else if (parentNode->id >= 0) {
0562         childEntities = d->m_childEntities.value(parentNode->id);
0563     }
0564 
0565     const int size = childEntities.size();
0566     if (row < 0 || row >= size) {
0567         return QModelIndex();
0568     }
0569 
0570     Node *node = childEntities.at(row);
0571     return createIndex(row, column, reinterpret_cast<void *>(node));
0572 }
0573 
0574 QModelIndex EntityTreeModel::parent(const QModelIndex &index) const
0575 {
0576     Q_D(const EntityTreeModel);
0577 
0578     if (!index.isValid()) {
0579         return QModelIndex();
0580     }
0581 
0582     if (d->m_collectionFetchStrategy == InvisibleCollectionFetch || d->m_collectionFetchStrategy == FetchNoCollections) {
0583         return QModelIndex();
0584     }
0585 
0586     const Node *node = reinterpret_cast<Node *>(index.internalPointer());
0587 
0588     if (!node) {
0589         return QModelIndex();
0590     }
0591 
0592     const Collection collection = d->m_collections.value(node->parent);
0593 
0594     if (!collection.isValid()) {
0595         return QModelIndex();
0596     }
0597 
0598     if (collection.id() == d->m_rootCollection.id()) {
0599         if (!d->m_showRootCollection) {
0600             return QModelIndex();
0601         } else {
0602             return createIndex(0, 0, reinterpret_cast<void *>(d->m_rootNode));
0603         }
0604     }
0605 
0606     Q_ASSERT(collection.parentCollection().isValid());
0607     const int row = d->indexOf<Node::Collection>(d->m_childEntities.value(collection.parentCollection().id()), collection.id());
0608 
0609     Q_ASSERT(row >= 0);
0610     Node *parentNode = d->m_childEntities.value(collection.parentCollection().id()).at(row);
0611 
0612     return createIndex(row, 0, reinterpret_cast<void *>(parentNode));
0613 }
0614 
0615 int EntityTreeModel::rowCount(const QModelIndex &parent) const
0616 {
0617     Q_D(const EntityTreeModel);
0618 
0619     if (d->m_collectionFetchStrategy == InvisibleCollectionFetch || d->m_collectionFetchStrategy == FetchNoCollections) {
0620         if (parent.isValid()) {
0621             return 0;
0622         } else {
0623             return d->m_items.size();
0624         }
0625     }
0626 
0627     if (!parent.isValid()) {
0628         // If we're showing the root collection then it will be the only child of the root.
0629         if (d->m_showRootCollection) {
0630             return d->m_childEntities.value(-1).size();
0631         }
0632         return d->m_childEntities.value(d->m_rootCollection.id()).size();
0633     }
0634 
0635     if (parent.column() != 0) {
0636         return 0;
0637     }
0638 
0639     const Node *node = reinterpret_cast<Node *>(parent.internalPointer());
0640 
0641     if (!node) {
0642         return 0;
0643     }
0644 
0645     if (Node::Item == node->type) {
0646         return 0;
0647     }
0648 
0649     Q_ASSERT(parent.isValid());
0650     return d->m_childEntities.value(node->id).size();
0651 }
0652 
0653 int EntityTreeModel::entityColumnCount(HeaderGroup headerGroup) const
0654 {
0655     // Not needed in this model.
0656     Q_UNUSED(headerGroup)
0657 
0658     return 1;
0659 }
0660 
0661 QVariant EntityTreeModel::entityHeaderData(int section, Qt::Orientation orientation, int role, HeaderGroup headerGroup) const
0662 {
0663     Q_D(const EntityTreeModel);
0664     // Not needed in this model.
0665     Q_UNUSED(headerGroup)
0666 
0667     if (section == 0 && orientation == Qt::Horizontal && (role == Qt::DisplayRole || role == EntityTreeModel::DisplayNameRole)) {
0668         if (d->m_rootCollection == Collection::root()) {
0669             return i18nc("@title:column Name of a thing", "Name");
0670         }
0671         return d->m_rootCollection.name();
0672     }
0673 
0674     return QAbstractItemModel::headerData(section, orientation, role);
0675 }
0676 
0677 QVariant EntityTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
0678 {
0679     const auto headerGroup = static_cast<HeaderGroup>((role / static_cast<int>(TerminalUserRole)));
0680 
0681     role %= TerminalUserRole;
0682     return entityHeaderData(section, orientation, role, headerGroup);
0683 }
0684 
0685 QMimeData *EntityTreeModel::mimeData(const QModelIndexList &indexes) const
0686 {
0687     Q_D(const EntityTreeModel);
0688 
0689     auto data = new QMimeData();
0690     QList<QUrl> urls;
0691     for (const QModelIndex &index : indexes) {
0692         if (index.column() != 0) {
0693             continue;
0694         }
0695 
0696         if (!index.isValid()) {
0697             continue;
0698         }
0699 
0700         const Node *node = reinterpret_cast<Node *>(index.internalPointer());
0701 
0702         if (Node::Collection == node->type) {
0703             urls << d->m_collections.value(node->id).url(Collection::UrlWithName);
0704         } else if (Node::Item == node->type) {
0705             QUrl url = d->m_items.value(node->id).url(Item::Item::UrlWithMimeType);
0706             QUrlQuery query(url);
0707             query.addQueryItem(QStringLiteral("parent"), QString::number(node->parent));
0708             url.setQuery(query);
0709             urls << url;
0710         } else { // if that happens something went horrible wrong
0711             Q_ASSERT(false);
0712         }
0713     }
0714 
0715     data->setUrls(urls);
0716 
0717     return data;
0718 }
0719 
0720 // Always return false for actions which take place asynchronously, eg via a Job.
0721 bool EntityTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
0722 {
0723     Q_D(EntityTreeModel);
0724 
0725     const Node *node = reinterpret_cast<Node *>(index.internalPointer());
0726 
0727     if (role == PendingCutRole) {
0728         if (index.isValid() && value.toBool()) {
0729             if (Node::Collection == node->type) {
0730                 d->m_pendingCutCollections.append(node->id);
0731             } else if (Node::Item == node->type) {
0732                 d->m_pendingCutItems.append(node->id);
0733             }
0734         } else {
0735             d->m_pendingCutCollections.clear();
0736             d->m_pendingCutItems.clear();
0737         }
0738         return true;
0739     }
0740 
0741     if (index.isValid() && node->type == Node::Collection && (role == CollectionRefRole || role == CollectionDerefRole)) {
0742         const Collection collection = index.data(CollectionRole).value<Collection>();
0743         Q_ASSERT(collection.isValid());
0744 
0745         if (role == CollectionDerefRole) {
0746             d->deref(collection.id());
0747         } else if (role == CollectionRefRole) {
0748             d->ref(collection.id());
0749         }
0750         return true;
0751     }
0752 
0753     if (index.column() == 0 && (role & (Qt::EditRole | ItemRole | CollectionRole))) {
0754         if (Node::Collection == node->type) {
0755             Collection collection = d->m_collections.value(node->id);
0756             if (!collection.isValid() || !value.isValid()) {
0757                 return false;
0758             }
0759 
0760             if (Qt::EditRole == role) {
0761                 collection.setName(value.toString());
0762                 if (collection.hasAttribute<EntityDisplayAttribute>()) {
0763                     auto *displayAttribute = collection.attribute<EntityDisplayAttribute>();
0764                     displayAttribute->setDisplayName(value.toString());
0765                 }
0766             } else if (Qt::BackgroundRole == role) {
0767                 auto color = value.value<QColor>();
0768                 if (!color.isValid()) {
0769                     return false;
0770                 }
0771 
0772                 auto *eda = collection.attribute<EntityDisplayAttribute>(Collection::AddIfMissing);
0773                 eda->setBackgroundColor(color);
0774             } else if (CollectionRole == role) {
0775                 collection = value.value<Collection>();
0776             }
0777 
0778             auto job = new CollectionModifyJob(collection, d->m_session);
0779             connect(job, SIGNAL(result(KJob *)), SLOT(updateJobDone(KJob *)));
0780 
0781             return false;
0782         } else if (Node::Item == node->type) {
0783             Item item = d->m_items.value(node->id);
0784             if (!item.isValid() || !value.isValid()) {
0785                 return false;
0786             }
0787 
0788             if (Qt::EditRole == role) {
0789                 if (item.hasAttribute<EntityDisplayAttribute>()) {
0790                     auto *displayAttribute = item.attribute<EntityDisplayAttribute>(Item::AddIfMissing);
0791                     displayAttribute->setDisplayName(value.toString());
0792                 }
0793             } else if (Qt::BackgroundRole == role) {
0794                 auto color = value.value<QColor>();
0795                 if (!color.isValid()) {
0796                     return false;
0797                 }
0798 
0799                 auto *eda = item.attribute<EntityDisplayAttribute>(Item::AddIfMissing);
0800                 eda->setBackgroundColor(color);
0801             } else if (ItemRole == role) {
0802                 item = value.value<Item>();
0803                 Q_ASSERT(item.id() == node->id);
0804             }
0805 
0806             auto itemModifyJob = new ItemModifyJob(item, d->m_session);
0807             connect(itemModifyJob, SIGNAL(result(KJob *)), SLOT(updateJobDone(KJob *)));
0808 
0809             return false;
0810         }
0811     }
0812 
0813     return QAbstractItemModel::setData(index, value, role);
0814 }
0815 
0816 bool EntityTreeModel::canFetchMore(const QModelIndex &parent) const
0817 {
0818     Q_UNUSED(parent)
0819     return false;
0820 }
0821 
0822 void EntityTreeModel::fetchMore(const QModelIndex &parent)
0823 {
0824     Q_D(EntityTreeModel);
0825 
0826     if (!d->canFetchMore(parent)) {
0827         return;
0828     }
0829 
0830     if (d->m_collectionFetchStrategy == InvisibleCollectionFetch) {
0831         return;
0832     }
0833 
0834     if (d->m_itemPopulation == ImmediatePopulation) {
0835         // Nothing to do. The items are already in the model.
0836         return;
0837     } else if (d->m_itemPopulation == LazyPopulation) {
0838         const Collection collection = parent.data(CollectionRole).value<Collection>();
0839 
0840         if (!collection.isValid()) {
0841             return;
0842         }
0843 
0844         d->fetchItems(collection);
0845     }
0846 }
0847 
0848 bool EntityTreeModel::hasChildren(const QModelIndex &parent) const
0849 {
0850     Q_D(const EntityTreeModel);
0851 
0852     if (d->m_collectionFetchStrategy == InvisibleCollectionFetch || d->m_collectionFetchStrategy == FetchNoCollections) {
0853         return parent.isValid() ? false : !d->m_items.isEmpty();
0854     }
0855 
0856     // TODO: Empty collections right now will return true and get a little + to expand.
0857     // There is probably no way to tell if a collection
0858     // has child items in akonadi without first attempting an itemFetchJob...
0859     // Figure out a way to fix this. (Statistics)
0860     return ((rowCount(parent) > 0) || (d->canFetchMore(parent) && d->m_itemPopulation == LazyPopulation));
0861 }
0862 
0863 bool EntityTreeModel::isCollectionTreeFetched() const
0864 {
0865     Q_D(const EntityTreeModel);
0866     return d->m_collectionTreeFetched;
0867 }
0868 
0869 bool EntityTreeModel::isCollectionPopulated(Collection::Id id) const
0870 {
0871     Q_D(const EntityTreeModel);
0872     return d->m_populatedCols.contains(id);
0873 }
0874 
0875 bool EntityTreeModel::isFullyPopulated() const
0876 {
0877     Q_D(const EntityTreeModel);
0878     return d->m_collectionTreeFetched && d->m_pendingCollectionRetrieveJobs.isEmpty();
0879 }
0880 
0881 QModelIndexList EntityTreeModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const
0882 {
0883     Q_D(const EntityTreeModel);
0884 
0885     if (role == CollectionIdRole || role == CollectionRole) {
0886         Collection::Id id;
0887         if (role == CollectionRole) {
0888             const Collection collection = value.value<Collection>();
0889             id = collection.id();
0890         } else {
0891             id = value.toLongLong();
0892         }
0893 
0894         const Collection collection = d->m_collections.value(id);
0895         if (!collection.isValid()) {
0896             return {};
0897         }
0898 
0899         const QModelIndex collectionIndex = d->indexForCollection(collection);
0900         Q_ASSERT(collectionIndex.isValid());
0901         return {collectionIndex};
0902     } else if (role == ItemIdRole || role == ItemRole) {
0903         Item::Id id;
0904         if (role == ItemRole) {
0905             id = value.value<Item>().id();
0906         } else {
0907             id = value.toLongLong();
0908         }
0909 
0910         const Item item = d->m_items.value(id);
0911         if (!item.isValid()) {
0912             return {};
0913         }
0914         return d->indexesForItem(item);
0915     } else if (role == EntityUrlRole) {
0916         const QUrl url(value.toString());
0917         const Item item = Item::fromUrl(url);
0918 
0919         if (item.isValid()) {
0920             return d->indexesForItem(d->m_items.value(item.id()));
0921         }
0922 
0923         const Collection collection = Collection::fromUrl(url);
0924         if (!collection.isValid()) {
0925             return {};
0926         }
0927         return {d->indexForCollection(collection)};
0928     }
0929 
0930     return QAbstractItemModel::match(start, role, value, hits, flags);
0931 }
0932 
0933 bool EntityTreeModel::insertRows(int /*row*/, int /*count*/, const QModelIndex & /*parent*/)
0934 {
0935     return false;
0936 }
0937 
0938 bool EntityTreeModel::insertColumns(int /*column*/, int /*count*/, const QModelIndex & /*parent*/)
0939 {
0940     return false;
0941 }
0942 
0943 bool EntityTreeModel::removeRows(int /*row*/, int /*count*/, const QModelIndex & /*parent*/)
0944 {
0945     return false;
0946 }
0947 
0948 bool EntityTreeModel::removeColumns(int /*column*/, int /*count*/, const QModelIndex & /*parent*/)
0949 {
0950     return false;
0951 }
0952 
0953 void EntityTreeModel::setItemPopulationStrategy(ItemPopulationStrategy strategy)
0954 {
0955     Q_D(EntityTreeModel);
0956     d->beginResetModel();
0957     d->m_itemPopulation = strategy;
0958 
0959     if (strategy == NoItemPopulation) {
0960         disconnect(d->m_monitor, SIGNAL(itemAdded(Akonadi::Item, Akonadi::Collection)), this, SLOT(monitoredItemAdded(Akonadi::Item, Akonadi::Collection)));
0961         disconnect(d->m_monitor, SIGNAL(itemChanged(Akonadi::Item, QSet<QByteArray>)), this, SLOT(monitoredItemChanged(Akonadi::Item, QSet<QByteArray>)));
0962         disconnect(d->m_monitor, SIGNAL(itemRemoved(Akonadi::Item)), this, SLOT(monitoredItemRemoved(Akonadi::Item)));
0963         disconnect(d->m_monitor,
0964                    SIGNAL(itemMoved(Akonadi::Item, Akonadi::Collection, Akonadi::Collection)),
0965                    this,
0966                    SLOT(monitoredItemMoved(Akonadi::Item, Akonadi::Collection, Akonadi::Collection)));
0967 
0968         disconnect(d->m_monitor, SIGNAL(itemLinked(Akonadi::Item, Akonadi::Collection)), this, SLOT(monitoredItemLinked(Akonadi::Item, Akonadi::Collection)));
0969         disconnect(d->m_monitor,
0970                    SIGNAL(itemUnlinked(Akonadi::Item, Akonadi::Collection)),
0971                    this,
0972                    SLOT(monitoredItemUnlinked(Akonadi::Item, Akonadi::Collection)));
0973     }
0974 
0975     d->m_monitor->d_ptr->useRefCounting = (strategy == LazyPopulation);
0976 
0977     d->endResetModel();
0978 }
0979 
0980 EntityTreeModel::ItemPopulationStrategy EntityTreeModel::itemPopulationStrategy() const
0981 {
0982     Q_D(const EntityTreeModel);
0983     return d->m_itemPopulation;
0984 }
0985 
0986 void EntityTreeModel::setIncludeRootCollection(bool include)
0987 {
0988     Q_D(EntityTreeModel);
0989     d->beginResetModel();
0990     d->m_showRootCollection = include;
0991     d->endResetModel();
0992 }
0993 
0994 bool EntityTreeModel::includeRootCollection() const
0995 {
0996     Q_D(const EntityTreeModel);
0997     return d->m_showRootCollection;
0998 }
0999 
1000 void EntityTreeModel::setRootCollectionDisplayName(const QString &displayName)
1001 {
1002     Q_D(EntityTreeModel);
1003     d->m_rootCollectionDisplayName = displayName;
1004 
1005     // TODO: Emit datachanged if it is being shown.
1006 }
1007 
1008 QString EntityTreeModel::rootCollectionDisplayName() const
1009 {
1010     Q_D(const EntityTreeModel);
1011     return d->m_rootCollectionDisplayName;
1012 }
1013 
1014 void EntityTreeModel::setCollectionFetchStrategy(CollectionFetchStrategy strategy)
1015 {
1016     Q_D(EntityTreeModel);
1017     d->beginResetModel();
1018     d->m_collectionFetchStrategy = strategy;
1019 
1020     if (strategy == FetchNoCollections || strategy == InvisibleCollectionFetch) {
1021         disconnect(d->m_monitor, SIGNAL(collectionChanged(Akonadi::Collection)), this, SLOT(monitoredCollectionChanged(Akonadi::Collection)));
1022         disconnect(d->m_monitor,
1023                    SIGNAL(collectionAdded(Akonadi::Collection, Akonadi::Collection)),
1024                    this,
1025                    SLOT(monitoredCollectionAdded(Akonadi::Collection, Akonadi::Collection)));
1026         disconnect(d->m_monitor, SIGNAL(collectionRemoved(Akonadi::Collection)), this, SLOT(monitoredCollectionRemoved(Akonadi::Collection)));
1027         disconnect(d->m_monitor,
1028                    SIGNAL(collectionMoved(Akonadi::Collection, Akonadi::Collection, Akonadi::Collection)),
1029                    this,
1030                    SLOT(monitoredCollectionMoved(Akonadi::Collection, Akonadi::Collection, Akonadi::Collection)));
1031         d->m_monitor->fetchCollection(false);
1032     } else {
1033         d->m_monitor->fetchCollection(true);
1034     }
1035 
1036     d->endResetModel();
1037 }
1038 
1039 EntityTreeModel::CollectionFetchStrategy EntityTreeModel::collectionFetchStrategy() const
1040 {
1041     Q_D(const EntityTreeModel);
1042     return d->m_collectionFetchStrategy;
1043 }
1044 
1045 static QPair<QList<const QAbstractProxyModel *>, const EntityTreeModel *> proxiesAndModel(const QAbstractItemModel *model)
1046 {
1047     QList<const QAbstractProxyModel *> proxyChain;
1048     const auto *proxy = qobject_cast<const QAbstractProxyModel *>(model);
1049     const QAbstractItemModel *_model = model;
1050     while (proxy) {
1051         proxyChain.prepend(proxy);
1052         _model = proxy->sourceModel();
1053         proxy = qobject_cast<const QAbstractProxyModel *>(_model);
1054     }
1055 
1056     const auto *etm = qobject_cast<const EntityTreeModel *>(_model);
1057     return qMakePair(proxyChain, etm);
1058 }
1059 
1060 static QModelIndex proxiedIndex(const QModelIndex &idx, const QList<const QAbstractProxyModel *> &proxyChain)
1061 {
1062     QModelIndex _idx = idx;
1063     for (const auto *proxy : proxyChain) {
1064         _idx = proxy->mapFromSource(_idx);
1065     }
1066     return _idx;
1067 }
1068 
1069 QModelIndex EntityTreeModel::modelIndexForCollection(const QAbstractItemModel *model, const Collection &collection)
1070 {
1071     const auto &[proxy, etm] = proxiesAndModel(model);
1072     if (!etm) {
1073         qCWarning(AKONADICORE_LOG) << "Model" << model << "is not derived from ETM or a proxy model on top of ETM.";
1074         return {};
1075     }
1076 
1077     QModelIndex idx = etm->d_ptr->indexForCollection(collection);
1078     return proxiedIndex(idx, proxy);
1079 }
1080 
1081 QModelIndexList EntityTreeModel::modelIndexesForItem(const QAbstractItemModel *model, const Item &item)
1082 {
1083     const auto &[proxy, etm] = proxiesAndModel(model);
1084 
1085     if (!etm) {
1086         qCWarning(AKONADICORE_LOG) << "Model" << model << "is not derived from ETM or a proxy model on top of ETM.";
1087         return QModelIndexList();
1088     }
1089 
1090     const QModelIndexList list = etm->d_ptr->indexesForItem(item);
1091     QModelIndexList proxyList;
1092     for (const QModelIndex &idx : list) {
1093         const QModelIndex pIdx = proxiedIndex(idx, proxy);
1094         if (pIdx.isValid()) {
1095             proxyList.push_back(pIdx);
1096         }
1097     }
1098     return proxyList;
1099 }
1100 
1101 Collection EntityTreeModel::updatedCollection(const QAbstractItemModel *model, qint64 collectionId)
1102 {
1103     const auto *proxy = qobject_cast<const QAbstractProxyModel *>(model);
1104     const QAbstractItemModel *_model = model;
1105     while (proxy) {
1106         _model = proxy->sourceModel();
1107         proxy = qobject_cast<const QAbstractProxyModel *>(_model);
1108     }
1109 
1110     const auto *etm = qobject_cast<const EntityTreeModel *>(_model);
1111     if (etm) {
1112         return etm->d_ptr->m_collections.value(collectionId);
1113     } else {
1114         return Collection{collectionId};
1115     }
1116 }
1117 
1118 Collection EntityTreeModel::updatedCollection(const QAbstractItemModel *model, const Collection &collection)
1119 {
1120     return updatedCollection(model, collection.id());
1121 }
1122 
1123 #include "moc_entitytreemodel.cpp"