mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-24 10:52:52 +00:00
1264 lines
39 KiB
C++
1264 lines
39 KiB
C++
/*
|
|
Copyright (c) 2008 Stephen Kelly <steveire@gmail.com>
|
|
|
|
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 <QtCore/QHash>
|
|
#include <QtCore/QMimeData>
|
|
#include <QtCore/QTimer>
|
|
#include <QAbstractProxyModel>
|
|
|
|
#include <KDE/KIcon>
|
|
#include <KDE/KLocalizedString>
|
|
#include <KDE/KMessageBox>
|
|
#include <KDE/KUrl>
|
|
|
|
#include <akonadi/attributefactory.h>
|
|
#include <akonadi/changerecorder.h>
|
|
#include <akonadi/collectionmodifyjob.h>
|
|
#include <akonadi/entitydisplayattribute.h>
|
|
#include <akonadi/transactionsequence.h>
|
|
#include <akonadi/itemmodifyjob.h>
|
|
#include <akonadi/session.h>
|
|
#include "collectionfetchscope.h"
|
|
|
|
#include "collectionutils_p.h"
|
|
|
|
#include "kdebug.h"
|
|
#include "pastehelper_p.h"
|
|
|
|
Q_DECLARE_METATYPE(QSet<QByteArray>)
|
|
|
|
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<Node *> &list, d->m_childEntities) {
|
|
QList<Node *>::const_iterator it = list.constBegin();
|
|
const QList<Node *>::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<EntityDisplayAttribute>() &&
|
|
!item.attribute<EntityDisplayAttribute>()->displayName().isEmpty()) {
|
|
return item.attribute<EntityDisplayAttribute>()->displayName();
|
|
} else {
|
|
if (!item.remoteId().isEmpty()) {
|
|
return item.remoteId();
|
|
}
|
|
return QString(QLatin1String("<") + QString::number(item.id()) + QLatin1String(">"));
|
|
}
|
|
break;
|
|
case Qt::DecorationRole:
|
|
if (item.hasAttribute<EntityDisplayAttribute>() &&
|
|
!item.attribute<EntityDisplayAttribute>()->iconName().isEmpty()) {
|
|
return item.attribute<EntityDisplayAttribute>()->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<EntityDisplayAttribute>() &&
|
|
!collection.attribute<EntityDisplayAttribute>()->iconName().isEmpty()) {
|
|
return collection.attribute<EntityDisplayAttribute>()->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<QObject *>(d->m_session));
|
|
}
|
|
|
|
// Ugly, but at least the API is clean.
|
|
const HeaderGroup headerGroup = static_cast<HeaderGroup>((role / static_cast<int>(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<Node *>(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>()) {
|
|
EntityDisplayAttribute *eda = collection.attribute<EntityDisplayAttribute>();
|
|
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>()) {
|
|
EntityDisplayAttribute *eda = item.attribute<EntityDisplayAttribute>();
|
|
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<Node *>(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<Node *>(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<Node *>(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<Node *>(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<Node *> childEntities;
|
|
|
|
const Node *parentNode = reinterpret_cast<Node *>(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<void *>(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<Node *>(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<void *>(d->m_rootNode));
|
|
}
|
|
}
|
|
|
|
Q_ASSERT(collection.parentCollection().isValid());
|
|
const int row = d->indexOf<Node::Collection>(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<void *>(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<Node *>(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<HeaderGroup>((role / static_cast<int>(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<Node *>(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<Node *>(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<Collection>();
|
|
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>()) {
|
|
EntityDisplayAttribute *displayAttribute = collection.attribute<EntityDisplayAttribute>();
|
|
displayAttribute->setDisplayName(value.toString());
|
|
}
|
|
}
|
|
|
|
if (Qt::BackgroundRole == role) {
|
|
QColor color = value.value<QColor>();
|
|
|
|
if (!color.isValid()) {
|
|
return false;
|
|
}
|
|
|
|
EntityDisplayAttribute *eda = collection.attribute<EntityDisplayAttribute>(Entity::AddIfMissing);
|
|
eda->setBackgroundColor(color);
|
|
}
|
|
|
|
if (CollectionRole == role) {
|
|
collection = value.value<Collection>();
|
|
}
|
|
|
|
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>()) {
|
|
EntityDisplayAttribute *displayAttribute = item.attribute<EntityDisplayAttribute>(Entity::AddIfMissing);
|
|
displayAttribute->setDisplayName(value.toString());
|
|
}
|
|
}
|
|
|
|
if (Qt::BackgroundRole == role) {
|
|
QColor color = value.value<QColor>();
|
|
|
|
if (!color.isValid()) {
|
|
return false;
|
|
}
|
|
|
|
EntityDisplayAttribute *eda = item.attribute<EntityDisplayAttribute>(Entity::AddIfMissing);
|
|
eda->setBackgroundColor(color);
|
|
}
|
|
|
|
if (ItemRole == role) {
|
|
item = value.value<Item>();
|
|
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<Collection>();
|
|
|
|
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<Collection>();
|
|
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<Item>();
|
|
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<Item>();
|
|
|
|
if (!item.isValid()) {
|
|
const Collection collection = idx.data(CollectionRole).value<Collection>();
|
|
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<QByteArray>)),
|
|
this, SLOT(monitoredItemChanged(Akonadi::Item,QSet<QByteArray>)));
|
|
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<QList<const QAbstractProxyModel *>, const EntityTreeModel *> proxiesAndModel(const QAbstractItemModel *model)
|
|
{
|
|
QList<const QAbstractProxyModel *> proxyChain;
|
|
const QAbstractProxyModel *proxy = qobject_cast<const QAbstractProxyModel *>(model);
|
|
const QAbstractItemModel *_model = model;
|
|
while (proxy) {
|
|
proxyChain.prepend(proxy);
|
|
_model = proxy->sourceModel();
|
|
proxy = qobject_cast<const QAbstractProxyModel *>(_model);
|
|
}
|
|
|
|
const EntityTreeModel *etm = qobject_cast<const EntityTreeModel *>(_model);
|
|
return qMakePair(proxyChain, etm);
|
|
}
|
|
|
|
static QModelIndex proxiedIndex(const QModelIndex &idx, QList<const QAbstractProxyModel *> proxyChain)
|
|
{
|
|
QListIterator<const QAbstractProxyModel *> 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<QList<const QAbstractProxyModel *>, 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<QList<const QAbstractProxyModel *>, 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"
|