File indexing completed on 2024-11-10 04:40:34
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"