kde-playground/kdepimlibs/akonadi/favoritecollectionsmodel.cpp
2015-04-14 21:49:29 +00:00

459 lines
15 KiB
C++

/*
Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
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 <QItemSelectionModel>
#include <QtCore/QMimeData>
#include <kconfiggroup.h>
#include <klocale.h>
#include <klocalizedstring.h>
#include <KJob>
#include <KUrl>
#include "entitytreemodel.h"
#include "mimetypechecker.h"
#include "pastehelper_p.h"
using namespace Akonadi;
/**
* @internal
*/
class 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().toString();
idx = idx.parent();
}
if (accountName.isEmpty()) {
return nameOfCollection;
} else {
return nameOfCollection + QLatin1String(" (") + 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<Collection::Id>());
}
/**
* 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.
foreach (const Collection::Id &collectionId, 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)) {
kWarning() << "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 {
kWarning() << "failed to reference collection";
}
q->sourceModel()->fetchMore(index);
}
}
void dereference(const Collection::Id &collectionId)
{
if (!referencedCollections.contains(collectionId)) {
kWarning() << "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()
{
foreach (const Collection::Id &collectionId, referencedCollections) {
dereference(collectionId);
}
}
/**
* Adds a collection to the favorite collections
*/
void add(const Collection::Id &collectionId)
{
if (collectionIds.contains(collectionId)) {
kDebug() << "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<Collection::Id> &collections)
{
QList<Collection::Id> colIds = collectionIds;
foreach (const Collection::Id &col, collections) {
const int removed = colIds.removeAll(col);
const bool isNewCollection = removed <= 0;
if (isNewCollection) {
add(col);
}
}
//Remove what's left
foreach (const Akonadi::Collection::Id &colId, colIds) {
remove(colId);
}
}
void set(const Akonadi::Collection::List &collections)
{
QList<Akonadi::Collection::Id> colIds;
foreach (const Akonadi::Collection &col, collections) {
colIds << col.id();
}
set(colIds);
}
void loadConfig()
{
const QList<Collection::Id> collections = configGroup.readEntry("FavoriteCollectionIds", QList<qint64>());
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;
foreach (const Collection::Id &collectionId, collectionIds) {
labels << labelForCollection(collectionId);
}
configGroup.writeEntry("FavoriteCollectionIds", collectionIds);
configGroup.writeEntry("FavoriteCollectionLabels", labels);
configGroup.config()->sync();
}
FavoriteCollectionsModel *const q;
QList<Collection::Id> collectionIds;
QSet<Collection::Id> referencedCollections;
QHash<qint64, QString> 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 compatiblity.
// 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, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)));
connect(source, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex)));
}
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;
foreach (const Collection::Id &colId, d->collectionIds) {
const QModelIndex idx = EntityTreeModel::modelIndexForCollection(sourceModel(), Collection(colId));
const Collection collection = sourceModel()->data(idx, EntityTreeModel::CollectionRole).value<Collection>();
cols << collection;
}
return cols;
}
QList<Collection::Id> 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<Collection>();
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(QLatin1String("text/uri-list"))) {
const KUrl::List urls = KUrl::List::fromMimeData(data);
const QModelIndex sourceIndex = mapToSource(parent);
const Collection destCollection = sourceModel()->data(sourceIndex, EntityTreeModel::CollectionRole).value<Collection>();
MimeTypeChecker mimeChecker;
mimeChecker.setWantedMimeTypes(destCollection.contentMimeTypes());
foreach (const KUrl &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) {
kDebug() << "Error: source and destination of move are the same.";
return false;
}
#if 0
if (!mimeChecker.isWantedItem(item)) {
kDebug() << "unwanted item" << mimeChecker.wantedMimeTypes() << item.mimeType();
return false;
}
#endif
KJob *job = PasteHelper::pasteUriList(data, destCollection, action);
if (!job) {
return false;
}
connect(job, SIGNAL(result(KJob*)), SLOT(pasteJobDone(KJob*)));
// 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(QLatin1String("text/uri-list"))) {
mts.append(QLatin1String("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()) {
kDebug() << job->errorString();
}
}
#include "moc_favoritecollectionsmodel.cpp"