mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-23 10:22:50 +00:00
472 lines
12 KiB
C++
472 lines
12 KiB
C++
/*
|
|
Copyright (c) 2006 - 2007 Volker Krause <vkrause@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 "itemmodel.h"
|
|
|
|
#include "itemfetchjob.h"
|
|
#include "collectionfetchjob.h"
|
|
#include "itemfetchscope.h"
|
|
#include "monitor.h"
|
|
#include "pastehelper_p.h"
|
|
#include "session.h"
|
|
|
|
#include <kdebug.h>
|
|
#include <klocalizedstring.h>
|
|
#include <kurl.h>
|
|
|
|
#include <QCoreApplication>
|
|
#include <QtCore/QDebug>
|
|
#include <QtCore/QMimeData>
|
|
|
|
using namespace Akonadi;
|
|
|
|
/**
|
|
* @internal
|
|
*
|
|
* This struct is used for optimization reasons.
|
|
* because it embeds the row.
|
|
*
|
|
* Semantically, we could have used an item instead.
|
|
*/
|
|
struct ItemContainer
|
|
{
|
|
ItemContainer(const Item &i, int r)
|
|
: item(i)
|
|
, row(r)
|
|
{
|
|
}
|
|
Item item;
|
|
int row;
|
|
};
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
class ItemModel::Private
|
|
{
|
|
public:
|
|
Private(ItemModel *parent)
|
|
: mParent(parent)
|
|
, monitor(new Monitor())
|
|
{
|
|
session = new Session(QCoreApplication::instance()->applicationName().toUtf8()
|
|
+ QByteArray("-ItemModel-") + QByteArray::number(qrand()), mParent);
|
|
|
|
monitor->ignoreSession(session);
|
|
|
|
mParent->connect(monitor, SIGNAL(itemChanged(Akonadi::Item,QSet<QByteArray>)),
|
|
mParent, SLOT(itemChanged(Akonadi::Item,QSet<QByteArray>)));
|
|
mParent->connect(monitor, SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)),
|
|
mParent, SLOT(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)));
|
|
mParent->connect(monitor, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)),
|
|
mParent, SLOT(itemAdded(Akonadi::Item)));
|
|
mParent->connect(monitor, SIGNAL(itemRemoved(Akonadi::Item)),
|
|
mParent, SLOT(itemRemoved(Akonadi::Item)));
|
|
mParent->connect(monitor, SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection)),
|
|
mParent, SLOT(itemAdded(Akonadi::Item)));
|
|
mParent->connect(monitor, SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection)),
|
|
mParent, SLOT(itemRemoved(Akonadi::Item)));
|
|
}
|
|
|
|
~Private()
|
|
{
|
|
delete monitor;
|
|
}
|
|
|
|
void listingDone(KJob *job);
|
|
void collectionFetchResult(KJob *job);
|
|
void itemChanged(const Akonadi::Item &item, const QSet<QByteArray> &);
|
|
void itemsAdded(const Akonadi::Item::List &list);
|
|
void itemAdded(const Akonadi::Item &item);
|
|
void itemMoved(const Akonadi::Item &item, const Akonadi::Collection &src, const Akonadi::Collection &dst);
|
|
void itemRemoved(const Akonadi::Item &item);
|
|
int rowForItem(const Akonadi::Item &item);
|
|
bool collectionIsCompatible() const;
|
|
|
|
ItemModel *mParent;
|
|
|
|
QList<ItemContainer *> items;
|
|
QHash<Item, ItemContainer *> itemHash;
|
|
|
|
Collection collection;
|
|
Monitor *monitor;
|
|
Session *session;
|
|
};
|
|
|
|
bool ItemModel::Private::collectionIsCompatible() const
|
|
{
|
|
// in the generic case, we show any collection
|
|
if (mParent->mimeTypes() == QStringList(QLatin1String("text/uri-list"))) {
|
|
return true;
|
|
}
|
|
// if the model's mime types are more specific, limit to those
|
|
// collections that have matching types
|
|
Q_FOREACH (const QString &type, mParent->mimeTypes()) {
|
|
if (collection.contentMimeTypes().contains(type)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ItemModel::Private::listingDone(KJob *job)
|
|
{
|
|
ItemFetchJob *fetch = static_cast<ItemFetchJob *>(job);
|
|
Q_UNUSED(fetch);
|
|
if (job->error()) {
|
|
// TODO
|
|
kWarning() << "Item query failed:" << job->errorString();
|
|
}
|
|
}
|
|
|
|
void ItemModel::Private::collectionFetchResult(KJob *job)
|
|
{
|
|
CollectionFetchJob *fetch = static_cast<CollectionFetchJob *>(job);
|
|
|
|
if (fetch->collections().isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
Q_ASSERT(fetch->collections().count() == 1); // we only listed base
|
|
Collection c = fetch->collections().first();
|
|
// avoid recursion, if this fails for some reason
|
|
if (!c.contentMimeTypes().isEmpty()) {
|
|
mParent->setCollection(c);
|
|
} else {
|
|
kWarning() << "Failed to retrieve the contents mime type of the collection: " << c;
|
|
mParent->setCollection(Collection());
|
|
}
|
|
}
|
|
|
|
int ItemModel::Private::rowForItem(const Akonadi::Item &item)
|
|
{
|
|
ItemContainer *container = itemHash.value(item);
|
|
if (!container) {
|
|
return -1;
|
|
}
|
|
|
|
/* Try to find the item directly;
|
|
|
|
If items have been removed, this first try won't succeed because
|
|
the ItemContainer rows have not been updated (costs too much).
|
|
*/
|
|
if (container->row < items.count()
|
|
&& items.at(container->row) == container) {
|
|
return container->row;
|
|
} else {
|
|
// Slow solution if the fist one has not succeeded
|
|
int row = -1;
|
|
const int numberOfItems(items.size());
|
|
for (int i = 0; i < numberOfItems; ++i) {
|
|
if (items.at(i)->item == item) {
|
|
row = i;
|
|
break;
|
|
}
|
|
}
|
|
return row;
|
|
}
|
|
|
|
}
|
|
|
|
void ItemModel::Private::itemChanged(const Akonadi::Item &item, const QSet<QByteArray> &)
|
|
{
|
|
int row = rowForItem(item);
|
|
if (row < 0) {
|
|
return;
|
|
}
|
|
|
|
items[row]->item = item;
|
|
itemHash.remove(item);
|
|
itemHash[item] = items[row];
|
|
|
|
QModelIndex start = mParent->index(row, 0, QModelIndex());
|
|
QModelIndex end = mParent->index(row, mParent->columnCount(QModelIndex()) - 1 , QModelIndex());
|
|
|
|
mParent->dataChanged(start, end);
|
|
}
|
|
|
|
void ItemModel::Private::itemMoved(const Akonadi::Item &item, const Akonadi::Collection &colSrc, const Akonadi::Collection &colDst)
|
|
{
|
|
if (colSrc == collection && colDst != collection) {
|
|
// item leaving this model
|
|
itemRemoved(item);
|
|
return;
|
|
}
|
|
|
|
if (colDst == collection && colSrc != collection) {
|
|
itemAdded(item);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void ItemModel::Private::itemsAdded(const Akonadi::Item::List &list)
|
|
{
|
|
if (list.isEmpty()) {
|
|
return;
|
|
}
|
|
mParent->beginInsertRows(QModelIndex(), items.count(), items.count() + list.count() - 1);
|
|
foreach (const Item &item, list) {
|
|
ItemContainer *c = new ItemContainer(item, items.count());
|
|
items.append(c);
|
|
itemHash[item] = c;
|
|
}
|
|
mParent->endInsertRows();
|
|
}
|
|
|
|
void ItemModel::Private::itemAdded(const Akonadi::Item &item)
|
|
{
|
|
Item::List l;
|
|
l << item;
|
|
itemsAdded(l);
|
|
}
|
|
|
|
void ItemModel::Private::itemRemoved(const Akonadi::Item &_item)
|
|
{
|
|
int row = rowForItem(_item);
|
|
if (row < 0) {
|
|
return;
|
|
}
|
|
|
|
mParent->beginRemoveRows(QModelIndex(), row, row);
|
|
const Item item = items.at(row)->item;
|
|
Q_ASSERT(item.isValid());
|
|
itemHash.remove(item);
|
|
delete items.takeAt(row);
|
|
mParent->endRemoveRows();
|
|
}
|
|
|
|
ItemModel::ItemModel(QObject *parent)
|
|
: QAbstractTableModel(parent)
|
|
, d(new Private(this))
|
|
{
|
|
}
|
|
|
|
ItemModel::~ItemModel()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
QVariant ItemModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!index.isValid()) {
|
|
return QVariant();
|
|
}
|
|
if (index.row() >= d->items.count()) {
|
|
return QVariant();
|
|
}
|
|
const Item item = d->items.at(index.row())->item;
|
|
if (!item.isValid()) {
|
|
return QVariant();
|
|
}
|
|
|
|
if (role == Qt::DisplayRole) {
|
|
switch (index.column()) {
|
|
case Id:
|
|
return QString::number(item.id());
|
|
case RemoteId:
|
|
return item.remoteId();
|
|
case MimeType:
|
|
return item.mimeType();
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
if (role == IdRole) {
|
|
return item.id();
|
|
}
|
|
|
|
if (role == ItemRole) {
|
|
QVariant var;
|
|
var.setValue(item);
|
|
return var;
|
|
}
|
|
|
|
if (role == MimeTypeRole) {
|
|
return item.mimeType();
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
int ItemModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
if (!parent.isValid()) {
|
|
return d->items.count();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ItemModel::columnCount(const QModelIndex &parent) const
|
|
{
|
|
if (!parent.isValid()) {
|
|
return 3; // keep in sync with Column enum
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
QVariant ItemModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
{
|
|
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
|
|
switch (section) {
|
|
case Id:
|
|
return i18n("Id");
|
|
case RemoteId:
|
|
return i18n("Remote Id");
|
|
case MimeType:
|
|
return i18n("MimeType");
|
|
default:
|
|
return QString();
|
|
}
|
|
}
|
|
return QAbstractTableModel::headerData(section, orientation, role);
|
|
}
|
|
|
|
void ItemModel::setCollection(const Collection &collection)
|
|
{
|
|
kDebug();
|
|
if (d->collection == collection) {
|
|
return;
|
|
}
|
|
|
|
// if we don't know anything about this collection yet, fetch it
|
|
if (collection.isValid() && collection.contentMimeTypes().isEmpty()) {
|
|
CollectionFetchJob *job = new CollectionFetchJob(collection, CollectionFetchJob::Base, this);
|
|
connect(job, SIGNAL(result(KJob*)), this, SLOT(collectionFetchResult(KJob*)));
|
|
return;
|
|
}
|
|
|
|
d->monitor->setCollectionMonitored(d->collection, false);
|
|
|
|
d->collection = collection;
|
|
|
|
d->monitor->setCollectionMonitored(d->collection, true);
|
|
|
|
// the query changed, thus everything we have already is invalid
|
|
qDeleteAll(d->items);
|
|
d->items.clear();
|
|
reset();
|
|
|
|
// stop all running jobs
|
|
d->session->clear();
|
|
|
|
// start listing job
|
|
if (d->collectionIsCompatible()) {
|
|
ItemFetchJob *job = new ItemFetchJob(collection, session());
|
|
job->setFetchScope(d->monitor->itemFetchScope());
|
|
connect(job, SIGNAL(itemsReceived(Akonadi::Item::List)),
|
|
SLOT(itemsAdded(Akonadi::Item::List)));
|
|
connect(job, SIGNAL(result(KJob*)), SLOT(listingDone(KJob*)));
|
|
}
|
|
|
|
emit collectionChanged(collection);
|
|
}
|
|
|
|
void ItemModel::setFetchScope(const ItemFetchScope &fetchScope)
|
|
{
|
|
d->monitor->setItemFetchScope(fetchScope);
|
|
}
|
|
|
|
ItemFetchScope &ItemModel::fetchScope()
|
|
{
|
|
return d->monitor->itemFetchScope();
|
|
}
|
|
|
|
Item ItemModel::itemForIndex(const QModelIndex &index) const
|
|
{
|
|
if (!index.isValid()) {
|
|
return Akonadi::Item();
|
|
}
|
|
|
|
if (index.row() >= d->items.count()) {
|
|
return Akonadi::Item();
|
|
}
|
|
|
|
Item item = d->items.at(index.row())->item;
|
|
if (item.isValid()) {
|
|
return item;
|
|
} else {
|
|
return Akonadi::Item();
|
|
}
|
|
}
|
|
|
|
Qt::ItemFlags ItemModel::flags(const QModelIndex &index) const
|
|
{
|
|
Qt::ItemFlags defaultFlags = QAbstractTableModel::flags(index);
|
|
|
|
if (index.isValid()) {
|
|
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
|
|
} else {
|
|
return Qt::ItemIsDropEnabled | defaultFlags;
|
|
}
|
|
}
|
|
|
|
QStringList ItemModel::mimeTypes() const
|
|
{
|
|
return QStringList() << QLatin1String("text/uri-list");
|
|
}
|
|
|
|
Session *ItemModel::session() const
|
|
{
|
|
return d->session;
|
|
}
|
|
|
|
QMimeData *ItemModel::mimeData(const QModelIndexList &indexes) const
|
|
{
|
|
QMimeData *data = new QMimeData();
|
|
// Add item uri to the mimedata for dropping in external applications
|
|
KUrl::List urls;
|
|
foreach (const QModelIndex &index, indexes) {
|
|
if (index.column() != 0) {
|
|
continue;
|
|
}
|
|
|
|
urls << itemForIndex(index).url(Item::UrlWithMimeType);
|
|
}
|
|
urls.populateMimeData(data);
|
|
|
|
return data;
|
|
}
|
|
|
|
QModelIndex ItemModel::indexForItem(const Akonadi::Item &item, const int column) const
|
|
{
|
|
return index(d->rowForItem(item), column);
|
|
}
|
|
|
|
bool ItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
|
|
{
|
|
Q_UNUSED(row);
|
|
Q_UNUSED(column);
|
|
Q_UNUSED(parent);
|
|
KJob *job = PasteHelper::paste(data, d->collection, action != Qt::MoveAction);
|
|
// TODO: error handling
|
|
return job;
|
|
}
|
|
|
|
Collection ItemModel::collection() const
|
|
{
|
|
return d->collection;
|
|
}
|
|
|
|
Qt::DropActions ItemModel::supportedDropActions() const
|
|
{
|
|
return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction;
|
|
}
|
|
|
|
#include "moc_itemmodel.cpp"
|