/* Copyright (c) 2008 Stephen Kelly This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "entitytreemodel.h" #include "entitytreemodel_p.h" #include "monitor_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "collectionfetchscope.h" #include "collectionutils_p.h" #include "kdebug.h" #include "pastehelper_p.h" Q_DECLARE_METATYPE(QSet) using namespace Akonadi; EntityTreeModel::EntityTreeModel(ChangeRecorder *monitor, QObject *parent) : QAbstractItemModel(parent) , d_ptr(new EntityTreeModelPrivate(this)) { Q_D(EntityTreeModel); d->init(monitor); } EntityTreeModel::EntityTreeModel(ChangeRecorder *monitor, EntityTreeModelPrivate *d, QObject *parent) : QAbstractItemModel(parent) , d_ptr(d) { d->init(monitor); } EntityTreeModel::~EntityTreeModel() { Q_D(EntityTreeModel); foreach (const QList &list, d->m_childEntities) { QList::const_iterator it = list.constBegin(); const QList::const_iterator end = list.constEnd(); for (; it != end; ++it) { delete *it; } } d->m_rootNode = 0; delete d_ptr; } bool EntityTreeModel::includeUnsubscribed() const { return (listFilter() == CollectionFetchScope::NoFilter); } void EntityTreeModel::setIncludeUnsubscribed(bool show) { if (show) { setListFilter(CollectionFetchScope::NoFilter); } else { setListFilter(CollectionFetchScope::Enabled); } } CollectionFetchScope::ListFilter EntityTreeModel::listFilter() const { Q_D(const EntityTreeModel); return d->m_listFilter; } void EntityTreeModel::setListFilter(CollectionFetchScope::ListFilter filter) { Q_D(EntityTreeModel); d->beginResetModel(); d->m_listFilter = filter; d->m_monitor->setAllMonitored(filter == CollectionFetchScope::NoFilter); d->endResetModel(); } void EntityTreeModel::setCollectionsMonitored(const Collection::List &collections) { Q_D(EntityTreeModel); d->beginResetModel(); foreach(const Akonadi::Collection &col, d->m_monitor->collectionsMonitored()) { d->m_monitor->setCollectionMonitored(col, false); } foreach(const Akonadi::Collection &col, collections) { d->m_monitor->setCollectionMonitored(col, true); } d->endResetModel(); } void EntityTreeModel::setCollectionMonitored(const Collection &col, bool monitored) { Q_D(EntityTreeModel); d->m_monitor->setCollectionMonitored(col, monitored); } void EntityTreeModel::setCollectionReferenced(const Akonadi::Collection &col, bool referenced) { Q_D(EntityTreeModel); d->m_monitor->setCollectionMonitored(col, referenced); Akonadi::Collection referencedCollection = col; referencedCollection.setReferenced(referenced); //We have to use the same session as the monitor, so the monitor can fetch the collection afterwards new Akonadi::CollectionModifyJob(referencedCollection, d->m_monitor->session()); } bool EntityTreeModel::systemEntitiesShown() const { Q_D(const EntityTreeModel); return d->m_showSystemEntities; } void EntityTreeModel::setShowSystemEntities(bool show) { Q_D(EntityTreeModel); d->m_showSystemEntities = show; } void EntityTreeModel::clearAndReset() { Q_D(EntityTreeModel); d->beginResetModel(); d->endResetModel(); } int EntityTreeModel::columnCount(const QModelIndex &parent) const { // TODO: Statistics? if (parent.isValid() && parent.column() != 0) { return 0; } return qMax(entityColumnCount(CollectionTreeHeaders), entityColumnCount(ItemListHeaders)); } QVariant EntityTreeModel::entityData(const Item &item, int column, int role) const { if (column == 0) { switch (role) { case Qt::DisplayRole: case Qt::EditRole: if (item.hasAttribute() && !item.attribute()->displayName().isEmpty()) { return item.attribute()->displayName(); } else { if (!item.remoteId().isEmpty()) { return item.remoteId(); } return QString(QLatin1String("<") + QString::number(item.id()) + QLatin1String(">")); } break; case Qt::DecorationRole: if (item.hasAttribute() && !item.attribute()->iconName().isEmpty()) { return item.attribute()->icon(); } break; default: break; } } return QVariant(); } QVariant EntityTreeModel::entityData(const Collection &collection, int column, int role) const { Q_D(const EntityTreeModel); if (column > 0) { return QString(); } if (collection == Collection::root()) { // Only display the root collection. It may not be edited. if (role == Qt::DisplayRole) { return d->m_rootCollectionDisplayName; } if (role == Qt::EditRole) { return QVariant(); } } switch (role) { case Qt::DisplayRole: case Qt::EditRole: if (column == 0) { const QString displayName = collection.displayName(); if (!displayName.isEmpty()) { return displayName; } else { return i18n("Loading..."); } } break; case Qt::DecorationRole: if (collection.hasAttribute() && !collection.attribute()->iconName().isEmpty()) { return collection.attribute()->icon(); } return KIcon(CollectionUtils::defaultIconName(collection)); default: break; } return QVariant(); } QVariant EntityTreeModel::data(const QModelIndex &index, int role) const { Q_D(const EntityTreeModel); if (role == SessionRole) { return QVariant::fromValue(qobject_cast(d->m_session)); } // Ugly, but at least the API is clean. const HeaderGroup headerGroup = static_cast((role / static_cast(TerminalUserRole))); role %= TerminalUserRole; if (!index.isValid()) { if (ColumnCountRole != role) { return QVariant(); } return entityColumnCount(headerGroup); } if (ColumnCountRole == role) { return entityColumnCount(headerGroup); } const Node *node = reinterpret_cast(index.internalPointer()); if (ParentCollectionRole == role && d->m_collectionFetchStrategy != FetchNoCollections) { const Collection parentCollection = d->m_collections.value(node->parent); Q_ASSERT(parentCollection.isValid()); return QVariant::fromValue(parentCollection); } if (Node::Collection == node->type) { const Collection collection = d->m_collections.value(node->id); if (!collection.isValid()) { return QVariant(); } switch (role) { case MimeTypeRole: return collection.mimeType(); break; case RemoteIdRole: return collection.remoteId(); break; case CollectionIdRole: return collection.id(); break; case ItemIdRole: // QVariant().toInt() is 0, not -1, so we have to handle the ItemIdRole // and CollectionIdRole (below) specially return -1; break; case CollectionRole: return QVariant::fromValue(collection); break; case EntityUrlRole: return collection.url().url(); break; case UnreadCountRole: { CollectionStatistics statistics = collection.statistics(); return statistics.unreadCount(); } case FetchStateRole: { return d->m_pendingCollectionRetrieveJobs.contains(collection.id()) ? FetchingState : IdleState; } case CollectionSyncProgressRole: { return QVariant(); // no longer supported } case IsPopulatedRole: { return d->m_populatedCols.contains(collection.id()); } case Qt::BackgroundRole: { if (collection.hasAttribute()) { EntityDisplayAttribute *eda = collection.attribute(); QColor color = eda->backgroundColor(); if (color.isValid()) { return color; } } // fall through. } default: return entityData(collection, index.column(), role); break; } } else if (Node::Item == node->type) { const Item item = d->m_items.value(node->id); if (!item.isValid()) { return QVariant(); } switch (role) { case ParentCollectionRole: return QVariant::fromValue(item.parentCollection()); case MimeTypeRole: return item.mimeType(); break; case RemoteIdRole: return item.remoteId(); break; case ItemRole: return QVariant::fromValue(item); break; case ItemIdRole: return item.id(); break; case CollectionIdRole: return -1; break; case LoadedPartsRole: return QVariant::fromValue(item.loadedPayloadParts()); break; case AvailablePartsRole: return QVariant::fromValue(item.availablePayloadParts()); break; case EntityUrlRole: return item.url(Akonadi::Item::UrlWithMimeType).url(); break; case Qt::BackgroundRole: { if (item.hasAttribute()) { EntityDisplayAttribute *eda = item.attribute(); const QColor color = eda->backgroundColor(); if (color.isValid()) { return color; } } // fall through. } default: return entityData(item, index.column(), role); break; } } return QVariant(); } Qt::ItemFlags EntityTreeModel::flags(const QModelIndex &index) const { Q_D(const EntityTreeModel); // Pass modeltest. if (!index.isValid()) { return 0; } Qt::ItemFlags flags = QAbstractItemModel::flags(index); const Node *node = reinterpret_cast(index.internalPointer()); if (Node::Collection == node->type) { // cut out entities will be shown as inactive if (d->m_pendingCutCollections.contains(node->id)) { return Qt::ItemIsSelectable; } const Collection collection = d->m_collections.value(node->id); if (collection.isValid()) { if (collection == Collection::root()) { // Selectable and displayable only. return flags; } const int rights = collection.rights(); if (rights & Collection::CanChangeCollection) { if (index.column() == 0) { flags |= Qt::ItemIsEditable; } // Changing the collection includes changing the metadata (child entityordering). // Need to allow this by drag and drop. flags |= Qt::ItemIsDropEnabled; } if (rights & (Collection::CanCreateCollection | Collection::CanCreateItem | Collection::CanLinkItem)) { // Can we drop new collections and items into this collection? flags |= Qt::ItemIsDropEnabled; } // dragging is always possible, even for read-only objects, but they can only be copied, not moved. flags |= Qt::ItemIsDragEnabled; } } else if (Node::Item == node->type) { if (d->m_pendingCutItems.contains(node->id)) { return Qt::ItemIsSelectable; } // Rights come from the parent collection. Collection parentCollection; if (!index.parent().isValid()) { parentCollection = d->m_rootCollection; } else { const Node *parentNode = reinterpret_cast(index.parent().internalPointer()); parentCollection = d->m_collections.value(parentNode->id); } if (parentCollection.isValid()) { const int rights = parentCollection.rights(); // Can't drop onto items. if (rights &Collection::CanChangeItem && index.column() == 0) { flags = flags | Qt::ItemIsEditable; } // dragging is always possible, even for read-only objects, but they can only be copied, not moved. flags |= Qt::ItemIsDragEnabled; } } return flags; } Qt::DropActions EntityTreeModel::supportedDropActions() const { return (Qt::CopyAction | Qt::MoveAction | Qt::LinkAction); } QStringList EntityTreeModel::mimeTypes() const { // TODO: Should this return the mimetypes that the items provide? Allow dragging a contact from here for example. return QStringList() << QLatin1String("text/uri-list"); } bool EntityTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); Q_D(EntityTreeModel); // Can't drop onto Collection::root. if (!parent.isValid()) { return false; } // TODO Use action and collection rights and return false if necessary // if row and column are -1, then the drop was on parent directly. // data should then be appended on the end of the items of the collections as appropriate. // That will mean begin insert rows etc. // Otherwise it was a sibling of the row^th item of parent. // Needs to be handled when ordering is accounted for. // Handle dropping between items as well as on items. // if ( row != -1 && column != -1 ) // { // } if (action == Qt::IgnoreAction) { return true; } // Shouldn't do this. Need to be able to drop vcards for example. // if ( !data->hasFormat( "text/uri-list" ) ) // return false; Node *node = reinterpret_cast(parent.internalId()); Q_ASSERT(node); if (Node::Item == node->type) { if (!parent.parent().isValid()) { // The drop is somehow on an item with no parent (shouldn't happen) // The drop should be considered handled anyway. kWarning() << "Dropped onto item with no parent collection"; return true; } // A drop onto an item should be considered as a drop onto its parent collection node = reinterpret_cast(parent.parent().internalId()); } if (Node::Collection == node->type) { const Collection destCollection = d->m_collections.value(node->id); // Applications can't create new collections in root. Only resources can. if (destCollection == Collection::root()) { // Accept the event so that it doesn't propagate. return true; } if (data->hasFormat(QLatin1String("text/uri-list"))) { MimeTypeChecker mimeChecker; mimeChecker.setWantedMimeTypes(destCollection.contentMimeTypes()); const KUrl::List urls = KUrl::List::fromMimeData(data); foreach (const KUrl &url, urls) { const Collection collection = d->m_collections.value(Collection::fromUrl(url).id()); if (collection.isValid()) { if (collection.parentCollection().id() == destCollection.id() && action != Qt::CopyAction) { kDebug() << "Error: source and destination of move are the same."; return false; } if (!mimeChecker.isWantedCollection(collection)) { kDebug() << "unwanted collection" << mimeChecker.wantedMimeTypes() << collection.contentMimeTypes(); return false; } if (url.hasQueryItem(QLatin1String("name"))) { const QString collectionName = url.queryItemValue(QLatin1String("name")); const QStringList collectionNames = d->childCollectionNames(destCollection); if (collectionNames.contains(collectionName)) { KMessageBox::error(0, i18n("The target collection '%1' contains already\na collection with name '%2'.", destCollection.name(), collection.name())); return false; } } } else { const Item item = d->m_items.value(Item::fromUrl(url).id()); if (item.isValid()) { if (item.parentCollection().id() == destCollection.id() && action != Qt::CopyAction) { kDebug() << "Error: source and destination of move are the same."; return false; } if (!mimeChecker.isWantedItem(item)) { kDebug() << "unwanted item" << mimeChecker.wantedMimeTypes() << item.mimeType(); return false; } } } } KJob *job = PasteHelper::pasteUriList(data, destCollection, action, d->m_session); if (!job) { return false; } connect(job, SIGNAL(result(KJob*)), SLOT(pasteJobDone(KJob*))); // Accpet the event so that it doesn't propagate. return true; } else { // not a set of uris. Maybe vcards etc. Check if the parent supports them, and maybe do // fromMimeData for them. Hmm, put it in the same transaction with the above? // TODO: This should be handled first, not last. } } return false; } QModelIndex EntityTreeModel::index(int row, int column, const QModelIndex &parent) const { Q_D(const EntityTreeModel); if (parent.column() > 0) { return QModelIndex(); } //TODO: don't use column count here? Use some d-> func. if (column >= columnCount() || column < 0) { return QModelIndex(); } QList childEntities; const Node *parentNode = reinterpret_cast(parent.internalPointer()); if (!parentNode || !parent.isValid()) { if (d->m_showRootCollection) { childEntities << d->m_childEntities.value(-1); } else { childEntities = d->m_childEntities.value(d->m_rootCollection.id()); } } else { if (parentNode->id >= 0) { childEntities = d->m_childEntities.value(parentNode->id); } } const int size = childEntities.size(); if (row < 0 || row >= size) { return QModelIndex(); } Node *node = childEntities.at(row); return createIndex(row, column, reinterpret_cast(node)); } QModelIndex EntityTreeModel::parent(const QModelIndex &index) const { Q_D(const EntityTreeModel); if (!index.isValid()) { return QModelIndex(); } if (d->m_collectionFetchStrategy == InvisibleCollectionFetch || d->m_collectionFetchStrategy == FetchNoCollections) { return QModelIndex(); } const Node *node = reinterpret_cast(index.internalPointer()); if (!node) { return QModelIndex(); } const Collection collection = d->m_collections.value(node->parent); if (!collection.isValid()) { return QModelIndex(); } if (collection.id() == d->m_rootCollection.id()) { if (!d->m_showRootCollection) { return QModelIndex(); } else { return createIndex(0, 0, reinterpret_cast(d->m_rootNode)); } } Q_ASSERT(collection.parentCollection().isValid()); const int row = d->indexOf(d->m_childEntities.value(collection.parentCollection().id()), collection.id()); Q_ASSERT(row >= 0); Node *parentNode = d->m_childEntities.value(collection.parentCollection().id()).at(row); return createIndex(row, 0, reinterpret_cast(parentNode)); } int EntityTreeModel::rowCount(const QModelIndex &parent) const { Q_D(const EntityTreeModel); if (d->m_collectionFetchStrategy == InvisibleCollectionFetch || d->m_collectionFetchStrategy == FetchNoCollections) { if (parent.isValid()) { return 0; } else { return d->m_items.size(); } } if (!parent.isValid()) { // If we're showing the root collection then it will be the only child of the root. if (d->m_showRootCollection) { return d->m_childEntities.value(-1).size(); } return d->m_childEntities.value(d->m_rootCollection.id()).size(); } if (parent.column() != 0) { return 0; } const Node *node = reinterpret_cast(parent.internalPointer()); if (!node) { return 0; } if (Node::Item == node->type) { return 0; } Q_ASSERT(parent.isValid()); return d->m_childEntities.value(node->id).size(); } int EntityTreeModel::entityColumnCount(HeaderGroup headerGroup) const { // Not needed in this model. Q_UNUSED(headerGroup); return 1; } QVariant EntityTreeModel::entityHeaderData(int section, Qt::Orientation orientation, int role, HeaderGroup headerGroup) const { Q_D(const EntityTreeModel); // Not needed in this model. Q_UNUSED(headerGroup); if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (d->m_rootCollection == Collection::root()) { return i18nc("@title:column Name of a thing", "Name"); } return d->m_rootCollection.name(); } return QAbstractItemModel::headerData(section, orientation, role); } QVariant EntityTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { const HeaderGroup headerGroup = static_cast((role / static_cast(TerminalUserRole))); role %= TerminalUserRole; return entityHeaderData(section, orientation, role, headerGroup); } QMimeData *EntityTreeModel::mimeData(const QModelIndexList &indexes) const { Q_D(const EntityTreeModel); QMimeData *data = new QMimeData(); KUrl::List urls; foreach (const QModelIndex &index, indexes) { if (index.column() != 0) { continue; } if (!index.isValid()) { continue; } const Node *node = reinterpret_cast(index.internalPointer()); if (Node::Collection == node->type) { urls << d->m_collections.value(node->id).url(Collection::UrlWithName); } else if (Node::Item == node->type) { KUrl url = d->m_items.value(node->id).url(Item::Item::UrlWithMimeType); // Encode the "virtual" parent url.addQueryItem(QLatin1String("parent"), QString::number(node->parent)); urls << url; } else { // if that happens something went horrible wrong Q_ASSERT(false); } } urls.populateMimeData(data); return data; } // Always return false for actions which take place asyncronously, eg via a Job. bool EntityTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { Q_D(EntityTreeModel); const Node *node = reinterpret_cast(index.internalPointer()); if (role == PendingCutRole) { if (index.isValid() && value.toBool()) { if (Node::Collection == node->type) { d->m_pendingCutCollections.append(node->id); } if (Node::Item == node->type) { d->m_pendingCutItems.append(node->id); } } else { d->m_pendingCutCollections.clear(); d->m_pendingCutItems.clear(); } return true; } if (index.isValid() && node->type == Node::Collection && (role == CollectionRefRole || role == CollectionDerefRole)) { const Collection collection = index.data(CollectionRole).value(); Q_ASSERT(collection.isValid()); if (role == CollectionDerefRole) { d->deref(collection.id()); } else if (role == CollectionRefRole) { d->ref(collection.id()); } return true; } if (index.column() == 0 && (role &(Qt::EditRole | ItemRole | CollectionRole))) { if (Node::Collection == node->type) { Collection collection = d->m_collections.value(node->id); if (!collection.isValid() || !value.isValid()) { return false; } if (Qt::EditRole == role) { collection.setName(value.toString()); if (collection.hasAttribute()) { EntityDisplayAttribute *displayAttribute = collection.attribute(); displayAttribute->setDisplayName(value.toString()); } } if (Qt::BackgroundRole == role) { QColor color = value.value(); if (!color.isValid()) { return false; } EntityDisplayAttribute *eda = collection.attribute(Entity::AddIfMissing); eda->setBackgroundColor(color); } if (CollectionRole == role) { collection = value.value(); } CollectionModifyJob *job = new CollectionModifyJob(collection, d->m_session); connect(job, SIGNAL(result(KJob*)), SLOT(updateJobDone(KJob*))); return false; } else if (Node::Item == node->type) { Item item = d->m_items.value(node->id); if (!item.isValid() || !value.isValid()) { return false; } if (Qt::EditRole == role) { if (item.hasAttribute()) { EntityDisplayAttribute *displayAttribute = item.attribute(Entity::AddIfMissing); displayAttribute->setDisplayName(value.toString()); } } if (Qt::BackgroundRole == role) { QColor color = value.value(); if (!color.isValid()) { return false; } EntityDisplayAttribute *eda = item.attribute(Entity::AddIfMissing); eda->setBackgroundColor(color); } if (ItemRole == role) { item = value.value(); Q_ASSERT(item.id() == node->id); } ItemModifyJob *itemModifyJob = new ItemModifyJob(item, d->m_session); connect(itemModifyJob, SIGNAL(result(KJob*)), SLOT(updateJobDone(KJob*))); return false; } } return QAbstractItemModel::setData(index, value, role); } bool EntityTreeModel::canFetchMore(const QModelIndex &parent) const { Q_UNUSED(parent) return false; } void EntityTreeModel::fetchMore(const QModelIndex &parent) { Q_D(EntityTreeModel); if (!d->canFetchMore(parent)) { return; } if (d->m_collectionFetchStrategy == InvisibleCollectionFetch) { return; } if (d->m_itemPopulation == ImmediatePopulation) { // Nothing to do. The items are already in the model. return; } else if (d->m_itemPopulation == LazyPopulation) { const Collection collection = parent.data(CollectionRole).value(); if (!collection.isValid()) { return; } d->fetchItems(collection); } } bool EntityTreeModel::hasChildren(const QModelIndex &parent) const { Q_D(const EntityTreeModel); if (d->m_collectionFetchStrategy == InvisibleCollectionFetch || d->m_collectionFetchStrategy == FetchNoCollections) { return parent.isValid() ? false : !d->m_items.isEmpty(); } // TODO: Empty collections right now will return true and get a little + to expand. // There is probably no way to tell if a collection // has child items in akonadi without first attempting an itemFetchJob... // Figure out a way to fix this. (Statistics) return ((rowCount(parent) > 0) || (canFetchMore(parent) && d->m_itemPopulation == LazyPopulation)); } bool EntityTreeModel::isCollectionTreeFetched() const { Q_D(const EntityTreeModel); return d->m_collectionTreeFetched; } bool EntityTreeModel::isCollectionPopulated(Collection::Id id) const { Q_D(const EntityTreeModel); return d->m_populatedCols.contains(id); } bool EntityTreeModel::isFullyPopulated() const { Q_D(const EntityTreeModel); return d->m_collectionTreeFetched && d->m_pendingCollectionRetrieveJobs.isEmpty(); } bool EntityTreeModel::entityMatch(const Item &item, const QVariant &value, Qt::MatchFlags flags) const { Q_UNUSED(item); Q_UNUSED(value); Q_UNUSED(flags); return false; } bool EntityTreeModel::entityMatch(const Collection &collection, const QVariant &value, Qt::MatchFlags flags) const { Q_UNUSED(collection); Q_UNUSED(value); Q_UNUSED(flags); return false; } QModelIndexList EntityTreeModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const { Q_D(const EntityTreeModel); if (role == CollectionIdRole || role == CollectionRole) { Collection::Id id; if (role == CollectionRole) { const Collection collection = value.value(); id = collection.id(); } else { id = value.toLongLong(); } QModelIndexList list; const Collection collection = d->m_collections.value(id); if (!collection.isValid()) { return list; } const QModelIndex collectionIndex = d->indexForCollection(collection); Q_ASSERT(collectionIndex.isValid()); list << collectionIndex; return list; } if (role == ItemIdRole || role == ItemRole) { Item::Id id; if (role == ItemRole) { const Item item = value.value(); id = item.id(); } else { id = value.toLongLong(); } QModelIndexList list; const Item item = d->m_items.value(id); if (!item.isValid()) { return list; } return d->indexesForItem(item); } if (role == EntityUrlRole) { const KUrl url(value.toString()); const Item item = Item::fromUrl(url); if (item.isValid()) { return d->indexesForItem(d->m_items.value(item.id())); } const Collection collection = Collection::fromUrl(url); QModelIndexList list; if (collection.isValid()) { list << d->indexForCollection(collection); } return list; } if (role != AmazingCompletionRole) { return QAbstractItemModel::match(start, role, value, hits, flags); } // Try to match names, and email addresses. QModelIndexList list; if (role < 0 || !start.isValid() || !value.isValid()) { return list; } const int column = 0; int row = start.row(); const QModelIndex parentIndex = start.parent(); const int parentRowCount = rowCount(parentIndex); while (row < parentRowCount && (hits == -1 || list.size() < hits)) { const QModelIndex idx = index(row, column, parentIndex); const Item item = idx.data(ItemRole).value(); if (!item.isValid()) { const Collection collection = idx.data(CollectionRole).value(); if (!collection.isValid()) { continue; } if (entityMatch(collection, value, flags)) { list << idx; } } else { if (entityMatch(item, value, flags)) { list << idx; } } ++row; } return list; } bool EntityTreeModel::insertRows(int, int, const QModelIndex &) { return false; } bool EntityTreeModel::insertColumns(int, int, const QModelIndex &) { return false; } bool EntityTreeModel::removeRows(int, int, const QModelIndex &) { return false; } bool EntityTreeModel::removeColumns(int, int, const QModelIndex &) { return false; } void EntityTreeModel::setItemPopulationStrategy(ItemPopulationStrategy strategy) { Q_D(EntityTreeModel); d->beginResetModel(); d->m_itemPopulation = strategy; if (strategy == NoItemPopulation) { disconnect(d->m_monitor, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), this, SLOT(monitoredItemAdded(Akonadi::Item,Akonadi::Collection))); disconnect(d->m_monitor, SIGNAL(itemChanged(Akonadi::Item,QSet)), this, SLOT(monitoredItemChanged(Akonadi::Item,QSet))); disconnect(d->m_monitor, SIGNAL(itemRemoved(Akonadi::Item)), this, SLOT(monitoredItemRemoved(Akonadi::Item))); disconnect(d->m_monitor, SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)), this, SLOT(monitoredItemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))); disconnect(d->m_monitor, SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection)), this, SLOT(monitoredItemLinked(Akonadi::Item,Akonadi::Collection))); disconnect(d->m_monitor, SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection)), this, SLOT(monitoredItemUnlinked(Akonadi::Item,Akonadi::Collection))); } d->m_monitor->d_ptr->useRefCounting = (strategy == LazyPopulation); d->endResetModel(); } EntityTreeModel::ItemPopulationStrategy EntityTreeModel::itemPopulationStrategy() const { Q_D(const EntityTreeModel); return d->m_itemPopulation; } void EntityTreeModel::setIncludeRootCollection(bool include) { Q_D(EntityTreeModel); d->beginResetModel(); d->m_showRootCollection = include; d->endResetModel(); } bool EntityTreeModel::includeRootCollection() const { Q_D(const EntityTreeModel); return d->m_showRootCollection; } void EntityTreeModel::setRootCollectionDisplayName(const QString &displayName) { Q_D(EntityTreeModel); d->m_rootCollectionDisplayName = displayName; // TODO: Emit datachanged if it is being shown. } QString EntityTreeModel::rootCollectionDisplayName() const { Q_D(const EntityTreeModel); return d->m_rootCollectionDisplayName; } void EntityTreeModel::setCollectionFetchStrategy(CollectionFetchStrategy strategy) { Q_D(EntityTreeModel); d->beginResetModel(); d->m_collectionFetchStrategy = strategy; if (strategy == FetchNoCollections || strategy == InvisibleCollectionFetch) { disconnect(d->m_monitor, SIGNAL(collectionChanged(Akonadi::Collection)), this, SLOT(monitoredCollectionChanged(Akonadi::Collection))); disconnect(d->m_monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)), this, SLOT(monitoredCollectionAdded(Akonadi::Collection,Akonadi::Collection))); disconnect(d->m_monitor, SIGNAL(collectionRemoved(Akonadi::Collection)), this, SLOT(monitoredCollectionRemoved(Akonadi::Collection))); disconnect(d->m_monitor, SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection)), this, SLOT(monitoredCollectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection))); d->m_monitor->fetchCollection(false); } else { d->m_monitor->fetchCollection(true); } d->endResetModel(); } EntityTreeModel::CollectionFetchStrategy EntityTreeModel::collectionFetchStrategy() const { Q_D(const EntityTreeModel); return d->m_collectionFetchStrategy; } static QPair, const EntityTreeModel *> proxiesAndModel(const QAbstractItemModel *model) { QList proxyChain; const QAbstractProxyModel *proxy = qobject_cast(model); const QAbstractItemModel *_model = model; while (proxy) { proxyChain.prepend(proxy); _model = proxy->sourceModel(); proxy = qobject_cast(_model); } const EntityTreeModel *etm = qobject_cast(_model); return qMakePair(proxyChain, etm); } static QModelIndex proxiedIndex(const QModelIndex &idx, QList proxyChain) { QListIterator it(proxyChain); QModelIndex _idx = idx; while (it.hasNext()) { _idx = it.next()->mapFromSource(_idx); } return _idx; } QModelIndex EntityTreeModel::modelIndexForCollection(const QAbstractItemModel *model, const Collection &collection) { QPair, const EntityTreeModel *> pair = proxiesAndModel(model); Q_ASSERT(pair.second); QModelIndex idx = pair.second->d_ptr->indexForCollection(collection); return proxiedIndex(idx, pair.first); } QModelIndexList EntityTreeModel::modelIndexesForItem(const QAbstractItemModel *model, const Item &item) { QPair, const EntityTreeModel *> pair = proxiesAndModel(model); if (!pair.second) { kWarning() << "Couldn't find an EntityTreeModel"; return QModelIndexList(); } QModelIndexList list = pair.second->d_ptr->indexesForItem(item); QModelIndexList proxyList; foreach (const QModelIndex &idx, list) { const QModelIndex pIdx = proxiedIndex(idx, pair.first); if (pIdx.isValid()) { proxyList << pIdx; } } return proxyList; } #include "moc_entitytreemodel.cpp"