mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-23 18:32:51 +00:00
460 lines
15 KiB
C++
460 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"
|