diff --git a/src/core/models/favoritecollectionsmodel.cpp b/src/core/models/favoritecollectionsmodel.cpp index 9fe0072c8..ca0b5ee55 100644 --- a/src/core/models/favoritecollectionsmodel.cpp +++ b/src/core/models/favoritecollectionsmodel.cpp @@ -1,461 +1,461 @@ /* Copyright (c) 2009 Kevin Ottens 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 "favoritecollectionsmodel.h" #include "akonadicore_debug.h" #include #include #include #include #include #include #include #include "entitytreemodel.h" #include "mimetypechecker.h" #include "pastehelper_p.h" using namespace Akonadi; /** * @internal */ class Q_DECL_HIDDEN FavoriteCollectionsModel::Private { public: Private(const KConfigGroup &group, FavoriteCollectionsModel *parent) : q(parent) , configGroup(group) { } QString labelForCollection(Collection::Id collectionId) const { if (labelMap.contains(collectionId)) { return labelMap[collectionId]; } const QModelIndex collectionIdx = EntityTreeModel::modelIndexForCollection(q->sourceModel(), Collection(collectionId)); QString accountName; const QString nameOfCollection = collectionIdx.data().toString(); QModelIndex idx = collectionIdx.parent(); while (idx != QModelIndex()) { accountName = idx.data(EntityTreeModel::OriginalCollectionNameRole).toString(); idx = idx.parent(); } if (accountName.isEmpty()) { return nameOfCollection; } else { return nameOfCollection + QStringLiteral(" (") + accountName + QLatin1Char(')'); } } void insertIfAvailable(Collection::Id col) { if (collectionIds.contains(col)) { select(col); if (!referencedCollections.contains(col)) { reference(col); } } } void insertIfAvailable(const QModelIndex &idx) { insertIfAvailable(idx.data(EntityTreeModel::CollectionIdRole).value()); } /** * Stuff changed (e.g. new rows inserted into sorted model), reload everything. */ void reload() { //don't clear the selection model here. Otherwise we mess up the users selection as collections get removed and re-inserted. for (const Collection::Id &collectionId : qAsConst(collectionIds)) { insertIfAvailable(collectionId); } //TODO remove what's no longer here } void rowsInserted(const QModelIndex &parent, int begin, int end) { for (int row = begin; row <= end; row++) { const QModelIndex child = parent.child(row, 0); if (!child.isValid()) { continue; } insertIfAvailable(child); const int childRows = q->sourceModel()->rowCount(child); if (childRows > 0) { rowsInserted(child, 0, childRows - 1); } } } void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { for (int row = topLeft.row(); row <= bottomRight.row(); row++) { const QModelIndex idx = topLeft.parent().child(row, 0); insertIfAvailable(idx); } } /** * Selects the index in the internal selection model to make the collection visible in the model */ void select(const Collection::Id &collectionId) { const QModelIndex index = EntityTreeModel::modelIndexForCollection(q->sourceModel(), Collection(collectionId)); if (index.isValid()) { q->selectionModel()->select(index, QItemSelectionModel::Select); } } void deselect(const Collection::Id &collectionId) { const QModelIndex idx = EntityTreeModel::modelIndexForCollection(q->sourceModel(), Collection(collectionId)); if (idx.isValid()) { q->selectionModel()->select(idx, QItemSelectionModel::Deselect); } } void reference(const Collection::Id &collectionId) { if (referencedCollections.contains(collectionId)) { qCWarning(AKONADICORE_LOG) << "already referenced " << collectionId; return; } const QModelIndex index = EntityTreeModel::modelIndexForCollection(q->sourceModel(), Collection(collectionId)); if (index.isValid()) { if (q->sourceModel()->setData(index, QVariant(), EntityTreeModel::CollectionRefRole)) { referencedCollections << collectionId; } else { qCWarning(AKONADICORE_LOG) << "failed to reference collection"; } q->sourceModel()->fetchMore(index); } } void dereference(const Collection::Id &collectionId) { if (!referencedCollections.contains(collectionId)) { qCWarning(AKONADICORE_LOG) << "not referenced " << collectionId; return; } const QModelIndex index = EntityTreeModel::modelIndexForCollection(q->sourceModel(), Collection(collectionId)); if (index.isValid()) { q->sourceModel()->setData(index, QVariant(), EntityTreeModel::CollectionDerefRole); referencedCollections.remove(collectionId); } } void clearReferences() { for (const Collection::Id &collectionId : qAsConst(referencedCollections)) { dereference(collectionId); } } /** * Adds a collection to the favorite collections */ void add(const Collection::Id &collectionId) { if (collectionIds.contains(collectionId)) { qCDebug(AKONADICORE_LOG) << "already in model " << collectionId; return; } collectionIds << collectionId; reference(collectionId); select(collectionId); } void remove(const Collection::Id &collectionId) { collectionIds.removeAll(collectionId); labelMap.remove(collectionId); dereference(collectionId); deselect(collectionId); } void set(const QList &collections) { QList colIds = collectionIds; for (const Collection::Id &col : collections) { const int removed = colIds.removeAll(col); const bool isNewCollection = removed <= 0; if (isNewCollection) { add(col); } } //Remove what's left for (Akonadi::Collection::Id colId : qAsConst(colIds)) { remove(colId); } } void set(const Akonadi::Collection::List &collections) { QList colIds; colIds.reserve(collections.count()); for (const Akonadi::Collection &col : collections) { colIds << col.id(); } set(colIds); } void loadConfig() { const QList collections = configGroup.readEntry("FavoriteCollectionIds", QList()); const QStringList labels = configGroup.readEntry("FavoriteCollectionLabels", QStringList()); const int numberOfLabels(labels.size()); for (int i = 0; i < collections.size(); ++i) { if (i < numberOfLabels) { labelMap[collections[i]] = labels[i]; } add(collections[i]); } } void saveConfig() { QStringList labels; labels.reserve(collectionIds.count()); for (const Collection::Id &collectionId : qAsConst(collectionIds)) { labels << labelForCollection(collectionId); } configGroup.writeEntry("FavoriteCollectionIds", collectionIds); configGroup.writeEntry("FavoriteCollectionLabels", labels); configGroup.config()->sync(); } FavoriteCollectionsModel *const q; QList collectionIds; QSet referencedCollections; QHash labelMap; KConfigGroup configGroup; }; FavoriteCollectionsModel::FavoriteCollectionsModel(QAbstractItemModel *source, const KConfigGroup &group, QObject *parent) : Akonadi::SelectionProxyModel(new QItemSelectionModel(source, parent), parent) , d(new Private(group, this)) { //This should only be a KRecursiveFilterProxyModel, but remains a SelectionProxyModel for backwards compatibility. // We therefore disable what we anyways don't want (the referencing is handled separately). disconnect(this, SIGNAL(rootIndexAdded(QModelIndex)), this, SLOT(rootIndexAdded(QModelIndex))); disconnect(this, SIGNAL(rootIndexAboutToBeRemoved(QModelIndex)), this, SLOT(rootIndexAboutToBeRemoved(QModelIndex))); setSourceModel(source); setFilterBehavior(ExactSelection); d->loadConfig(); //React to various changes in the source model connect(source, SIGNAL(modelReset()), this, SLOT(reload())); connect(source, SIGNAL(layoutChanged()), this, SLOT(reload())); - connect(source, &QAbstractItemModel::rowsInserted, this, &QAbstractItemModel::rowsInserted); - connect(source, &QAbstractItemModel::dataChanged, this, &QAbstractItemModel::dataChanged); + connect(source, &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex &parent, int begin, int end) { d->rowsInserted(parent, begin, end); }); + connect(source, &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &tl, const QModelIndex &br) { d->dataChanged(tl, br); }); } FavoriteCollectionsModel::~FavoriteCollectionsModel() { delete d; } void FavoriteCollectionsModel::setCollections(const Collection::List &collections) { d->set(collections); d->saveConfig(); } void FavoriteCollectionsModel::addCollection(const Collection &collection) { d->add(collection.id()); d->saveConfig(); } void FavoriteCollectionsModel::removeCollection(const Collection &collection) { d->remove(collection.id()); d->saveConfig(); } Akonadi::Collection::List FavoriteCollectionsModel::collections() const { Collection::List cols; cols.reserve(d->collectionIds.count()); for (const Collection::Id &colId : qAsConst(d->collectionIds)) { const QModelIndex idx = EntityTreeModel::modelIndexForCollection(sourceModel(), Collection(colId)); const Collection collection = sourceModel()->data(idx, EntityTreeModel::CollectionRole).value(); cols << collection; } return cols; } QList FavoriteCollectionsModel::collectionIds() const { return d->collectionIds; } void Akonadi::FavoriteCollectionsModel::setFavoriteLabel(const Collection &collection, const QString &label) { Q_ASSERT(d->collectionIds.contains(collection.id())); d->labelMap[collection.id()] = label; d->saveConfig(); const QModelIndex idx = EntityTreeModel::modelIndexForCollection(sourceModel(), collection); if (!idx.isValid()) { return; } const QModelIndex index = mapFromSource(idx); emit dataChanged(index, index); } QVariant Akonadi::FavoriteCollectionsModel::data(const QModelIndex &index, int role) const { if (index.column() == 0 && (role == Qt::DisplayRole || role == Qt::EditRole)) { const QModelIndex sourceIndex = mapToSource(index); const Collection::Id collectionId = sourceModel()->data(sourceIndex, EntityTreeModel::CollectionIdRole).toLongLong(); return d->labelForCollection(collectionId); } else { return KSelectionProxyModel::data(index, role); } } bool FavoriteCollectionsModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (index.isValid() && index.column() == 0 && role == Qt::EditRole) { const QString newLabel = value.toString(); if (newLabel.isEmpty()) { return false; } const QModelIndex sourceIndex = mapToSource(index); const Collection collection = sourceModel()->data(sourceIndex, EntityTreeModel::CollectionRole).value(); setFavoriteLabel(collection, newLabel); return true; } return Akonadi::SelectionProxyModel::setData(index, value, role); } QString Akonadi::FavoriteCollectionsModel::favoriteLabel(const Akonadi::Collection &collection) { if (!collection.isValid()) { return QString(); } return d->labelForCollection(collection.id()); } QVariant FavoriteCollectionsModel::headerData(int section, Qt::Orientation orientation, int role) const { if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) { return i18n("Favorite Folders"); } else { return KSelectionProxyModel::headerData(section, orientation, role); } } bool FavoriteCollectionsModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(action); Q_UNUSED(row); Q_UNUSED(column); if (data->hasFormat(QStringLiteral("text/uri-list"))) { const QList urls = data->urls(); const QModelIndex sourceIndex = mapToSource(parent); const Collection destCollection = sourceModel()->data(sourceIndex, EntityTreeModel::CollectionRole).value(); MimeTypeChecker mimeChecker; mimeChecker.setWantedMimeTypes(destCollection.contentMimeTypes()); for (const QUrl &url : urls) { const Collection col = Collection::fromUrl(url); if (col.isValid()) { addCollection(col); } else { const Item item = Item::fromUrl(url); if (item.isValid()) { if (item.parentCollection().id() == destCollection.id() && action != Qt::CopyAction) { qCDebug(AKONADICORE_LOG) << "Error: source and destination of move are the same."; return false; } #if 0 if (!mimeChecker.isWantedItem(item)) { qCDebug(AKONADICORE_LOG) << "unwanted item" << mimeChecker.wantedMimeTypes() << item.mimeType(); return false; } #endif KJob *job = PasteHelper::pasteUriList(data, destCollection, action); if (!job) { return false; } connect(job, &KJob::result, this, &FavoriteCollectionsModel::pasteJobDone); // Accept the event so that it doesn't propagate. return true; } } } return true; } return false; } QStringList FavoriteCollectionsModel::mimeTypes() const { QStringList mts = Akonadi::SelectionProxyModel::mimeTypes(); if (!mts.contains(QStringLiteral("text/uri-list"))) { mts.append(QStringLiteral("text/uri-list")); } return mts; } Qt::ItemFlags FavoriteCollectionsModel::flags(const QModelIndex &index) const { Qt::ItemFlags fs = Akonadi::SelectionProxyModel::flags(index); if (!index.isValid()) { fs |= Qt::ItemIsDropEnabled; } return fs; } void FavoriteCollectionsModel::pasteJobDone(KJob *job) { if (job->error()) { qCDebug(AKONADICORE_LOG) << job->errorString(); } } #include "moc_favoritecollectionsmodel.cpp" diff --git a/src/core/models/favoritecollectionsmodel.h b/src/core/models/favoritecollectionsmodel.h index 390e35206..edd5f0b04 100644 --- a/src/core/models/favoritecollectionsmodel.h +++ b/src/core/models/favoritecollectionsmodel.h @@ -1,168 +1,166 @@ /* Copyright (c) 2009 Kevin Ottens 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. */ #ifndef AKONADI_FAVORITECOLLECTIONSMODEL_H #define AKONADI_FAVORITECOLLECTIONSMODEL_H #include "akonadicore_export.h" #include "selectionproxymodel.h" #include "collection.h" class KConfigGroup; class KJob; namespace Akonadi { class EntityTreeModel; /** * @short A model that lists a set of favorite collections. * * In some applications you want to provide fast access to a list * of often used collections (e.g. Inboxes from different email accounts * in a mail application). Therefor you can use the FavoriteCollectionsModel * which stores the list of favorite collections in a given configuration * file. * * Example: * * @code * * using namespace Akonadi; * * EntityTreeModel *sourceModel = new EntityTreeModel( ... ); * * const KConfigGroup group = KGlobal::config()->group( "Favorite Collections" ); * * FavoriteCollectionsModel *model = new FavoriteCollectionsModel( sourceModel, group, this ); * * EntityListView *view = new EntityListView( this ); * view->setModel( model ); * * @endcode * * @author Kevin Ottens * @since 4.4 */ //TODO_KDE5: Make this a KRecursiveFilterProxyModel instead of a SelectionProxyModel class AKONADICORE_EXPORT FavoriteCollectionsModel : public Akonadi::SelectionProxyModel { Q_OBJECT public: /** * Creates a new favorite collections model. * * @param model The source model where the favorite collections * come from. * @param group The config group that shall be used to save the * selection of favorite collections. * @param parent The parent object. */ FavoriteCollectionsModel(QAbstractItemModel *model, const KConfigGroup &group, QObject *parent = nullptr); /** * Destroys the favorite collections model. */ virtual ~FavoriteCollectionsModel(); /** * Returns the list of favorite collections. * @deprecated Use collectionIds instead. */ AKONADICORE_DEPRECATED Collection::List collections() const; /** * Returns the list of ids of favorite collections set on the FavoriteCollectionsModel. * * Note that if you want Collections with actual data * you should use something like this instead: * * @code * FavoriteCollectionsModel* favs = getFavsModel(); * Collection::List cols; * const int rowCount = favs->rowCount(); * for (int row = 0; row < rowcount; ++row) { * static const int column = 0; * const QModelIndex index = favs->index(row, column); * const Collection col = index.data(EntityTreeModel::CollectionRole).value(); * cols << col; * } * @endcode * * Note that due to the asynchronous nature of the model, this method returns collection ids * of collections which may not be in the model yet. If you want the ids of the collections * that are actually in the model, use a loop similar to above with the CollectionIdRole. */ QList collectionIds() const; /** * Return associate label for collection */ QString favoriteLabel(const Akonadi::Collection &col); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; QStringList mimeTypes() const override; Qt::ItemFlags flags(const QModelIndex &index) const override; public Q_SLOTS: /** * Sets the @p collections as favorite collections. */ void setCollections(const Collection::List &collections); /** * Adds a @p collection to the list of favorite collections. */ void addCollection(const Collection &collection); /** * Removes a @p collection from the list of favorite collections. */ void removeCollection(const Collection &collection); /** * Sets a custom @p label that will be used when showing the * favorite @p collection. */ void setFavoriteLabel(const Collection &collection, const QString &label); private Q_SLOTS: void pasteJobDone(KJob *job); private: //@cond PRIVATE using KSelectionProxyModel::setSourceModel; class Private; Private *const d; Q_PRIVATE_SLOT(d, void reload()) - Q_PRIVATE_SLOT(d, void rowsInserted(QModelIndex, int, int)) - Q_PRIVATE_SLOT(d, void dataChanged(QModelIndex, QModelIndex)) //@endcond }; } #endif