entitytreemodel.cpp 37.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/*
    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"
22
#include "akonadicore_debug.h"
23 24
#include "monitor_p.h"

Daniel Vrátil's avatar
Daniel Vrátil committed
25 26
#include <QHash>
#include <QMimeData>
27
#include <QAbstractProxyModel>
28
#include <QMessageBox>
29

Laurent Montel's avatar
Laurent Montel committed
30
#include <KLocalizedString>
Laurent Montel's avatar
Laurent Montel committed
31
#include <QUrl>
32
#include <QUrlQuery>
33

Daniel Vrátil's avatar
Daniel Vrátil committed
34
#include "attributefactory.h"
35
#include "monitor.h"
Daniel Vrátil's avatar
Daniel Vrátil committed
36 37 38 39 40
#include "collectionmodifyjob.h"
#include "entitydisplayattribute.h"
#include "transactionsequence.h"
#include "itemmodifyjob.h"
#include "session.h"
41
#include "collectionfetchscope.h"
42

43

44
#include "collectionutils.h"
45

46
#include "pastehelper_p.h"
47

Guy Maurel's avatar
Guy Maurel committed
48
Q_DECLARE_METATYPE(QSet<QByteArray>)
49

50 51
using namespace Akonadi;

52
EntityTreeModel::EntityTreeModel(Monitor *monitor, QObject *parent)
Guy Maurel's avatar
Guy Maurel committed
53 54
    : QAbstractItemModel(parent)
    , d_ptr(new EntityTreeModelPrivate(this))
55
{
Guy Maurel's avatar
Guy Maurel committed
56 57
    Q_D(EntityTreeModel);
    d->init(monitor);
58
}
59

60
EntityTreeModel::EntityTreeModel(Monitor *monitor, EntityTreeModelPrivate *d, QObject *parent)
Guy Maurel's avatar
Guy Maurel committed
61 62
    : QAbstractItemModel(parent)
    , d_ptr(d)
63
{
Guy Maurel's avatar
Guy Maurel committed
64
    d->init(monitor);
65 66 67 68
}

EntityTreeModel::~EntityTreeModel()
{
Guy Maurel's avatar
Guy Maurel committed
69
    Q_D(EntityTreeModel);
70

Laurent Montel's avatar
Laurent Montel committed
71
    for (const QList<Node *> &list : qAsConst(d->m_childEntities)) {
Guy Maurel's avatar
Guy Maurel committed
72 73 74 75 76
        QList<Node *>::const_iterator it = list.constBegin();
        const QList<Node *>::const_iterator end = list.constEnd();
        for (; it != end; ++it) {
            delete *it;
        }
77
    }
78

Laurent Montel's avatar
Laurent Montel committed
79
    d->m_rootNode = nullptr;
80

Guy Maurel's avatar
Guy Maurel committed
81
    delete d_ptr;
82 83
}

84 85 86 87 88 89 90
CollectionFetchScope::ListFilter EntityTreeModel::listFilter() const
{
    Q_D(const EntityTreeModel);
    return d->m_listFilter;
}

void EntityTreeModel::setListFilter(CollectionFetchScope::ListFilter filter)
91
{
Guy Maurel's avatar
Guy Maurel committed
92 93
    Q_D(EntityTreeModel);
    d->beginResetModel();
94 95
    d->m_listFilter = filter;
    d->m_monitor->setAllMonitored(filter == CollectionFetchScope::NoFilter);
Guy Maurel's avatar
Guy Maurel committed
96
    d->endResetModel();
97 98
}

99 100 101 102
void EntityTreeModel::setCollectionsMonitored(const Collection::List &collections)
{
    Q_D(EntityTreeModel);
    d->beginResetModel();
Laurent Montel's avatar
Laurent Montel committed
103 104
    const Akonadi::Collection::List lstCols = d->m_monitor->collectionsMonitored();
    for (const Akonadi::Collection &col : lstCols) {
105 106
        d->m_monitor->setCollectionMonitored(col, false);
    }
Laurent Montel's avatar
Laurent Montel committed
107
    for (const Akonadi::Collection &col : collections) {
108 109 110 111 112 113 114 115 116 117 118
        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);
}

119 120 121 122 123 124 125 126 127
void EntityTreeModel::setCollectionReferenced(const Akonadi::Collection &col, bool referenced)
{
    Q_D(EntityTreeModel);
    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());
}

128
bool EntityTreeModel::systemEntitiesShown() const
129
{
Guy Maurel's avatar
Guy Maurel committed
130 131
    Q_D(const EntityTreeModel);
    return d->m_showSystemEntities;
132 133
}

Guy Maurel's avatar
Guy Maurel committed
134
void EntityTreeModel::setShowSystemEntities(bool show)
135
{
Guy Maurel's avatar
Guy Maurel committed
136 137
    Q_D(EntityTreeModel);
    d->m_showSystemEntities = show;
138 139
}

140 141
void EntityTreeModel::clearAndReset()
{
Guy Maurel's avatar
Guy Maurel committed
142 143 144
    Q_D(EntityTreeModel);
    d->beginResetModel();
    d->endResetModel();
145 146
}

147 148 149 150 151 152 153 154 155
QHash<int, QByteArray> EntityTreeModel::roleNames() const
{
    QHash<int, QByteArray> names = QAbstractItemModel::roleNames();
    names.insert(EntityTreeModel::UnreadCountRole, "unreadCount");
    names.insert(EntityTreeModel::FetchStateRole, "fetchState");
    names.insert(EntityTreeModel::ItemIdRole, "itemId");
    return names;
}

Guy Maurel's avatar
Guy Maurel committed
156
int EntityTreeModel::columnCount(const QModelIndex &parent) const
157 158
{
// TODO: Statistics?
Guy Maurel's avatar
Guy Maurel committed
159
    if (parent.isValid() &&
Laurent Montel's avatar
Laurent Montel committed
160
            parent.column() != 0) {
Guy Maurel's avatar
Guy Maurel committed
161 162
        return 0;
    }
163

Guy Maurel's avatar
Guy Maurel committed
164
    return qMax(entityColumnCount(CollectionTreeHeaders), entityColumnCount(ItemListHeaders));
165 166
}

Guy Maurel's avatar
Guy Maurel committed
167
QVariant EntityTreeModel::entityData(const Item &item, int column, int role) const
168
{
169 170
    Q_D(const EntityTreeModel);

Guy Maurel's avatar
Guy Maurel committed
171 172 173 174 175
    if (column == 0) {
        switch (role) {
        case Qt::DisplayRole:
        case Qt::EditRole:
            if (item.hasAttribute<EntityDisplayAttribute>() &&
Laurent Montel's avatar
Laurent Montel committed
176
                    !item.attribute<EntityDisplayAttribute>()->displayName().isEmpty()) {
Guy Maurel's avatar
Guy Maurel committed
177 178 179 180 181
                return item.attribute<EntityDisplayAttribute>()->displayName();
            } else {
                if (!item.remoteId().isEmpty()) {
                    return item.remoteId();
                }
182
                return QString(QStringLiteral("<") + QString::number(item.id()) + QStringLiteral(">"));
Guy Maurel's avatar
Guy Maurel committed
183 184 185 186
            }
            break;
        case Qt::DecorationRole:
            if (item.hasAttribute<EntityDisplayAttribute>() &&
Laurent Montel's avatar
Laurent Montel committed
187
                    !item.attribute<EntityDisplayAttribute>()->iconName().isEmpty()) {
188
                return d->iconForName(item.attribute<EntityDisplayAttribute>()->iconName());
Guy Maurel's avatar
Guy Maurel committed
189 190 191 192
            }
            break;
        default:
            break;
193
        }
194 195
    }

Guy Maurel's avatar
Guy Maurel committed
196
    return QVariant();
197 198
}

Guy Maurel's avatar
Guy Maurel committed
199
QVariant EntityTreeModel::entityData(const Collection &collection, int column, int role) const
200
{
Guy Maurel's avatar
Guy Maurel committed
201
    Q_D(const EntityTreeModel);
202

Guy Maurel's avatar
Guy Maurel committed
203 204
    if (column > 0) {
        return QString();
205
    }
206

Guy Maurel's avatar
Guy Maurel committed
207 208 209 210 211 212 213 214 215
    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();
        }
216
    }
217

Guy Maurel's avatar
Guy Maurel committed
218
    switch (role) {
219 220
    case Qt::DisplayRole:
    case Qt::EditRole:
Guy Maurel's avatar
Guy Maurel committed
221 222 223 224 225 226 227 228 229
        if (column == 0) {
            const QString displayName = collection.displayName();
            if (!displayName.isEmpty()) {
                return displayName;
            } else {
                return i18n("Loading...");
            }
        }
        break;
230
    case Qt::DecorationRole:
Guy Maurel's avatar
Guy Maurel committed
231
        if (collection.hasAttribute<EntityDisplayAttribute>() &&
Laurent Montel's avatar
Laurent Montel committed
232
                !collection.attribute<EntityDisplayAttribute>()->iconName().isEmpty()) {
233
            return d->iconForName(collection.attribute<EntityDisplayAttribute>()->iconName());
Guy Maurel's avatar
Guy Maurel committed
234
        }
235
        return d->iconForName(CollectionUtils::defaultIconName(collection));
236
    default:
Guy Maurel's avatar
Guy Maurel committed
237 238
        break;
    }
239

Guy Maurel's avatar
Guy Maurel committed
240
    return QVariant();
241 242
}

Guy Maurel's avatar
Guy Maurel committed
243
QVariant EntityTreeModel::data(const QModelIndex &index, int role) const
244
{
Guy Maurel's avatar
Guy Maurel committed
245 246 247
    Q_D(const EntityTreeModel);
    if (role == SessionRole) {
        return QVariant::fromValue(qobject_cast<QObject *>(d->m_session));
248
    }
Tobias Koenig's avatar
Tobias Koenig committed
249

Guy Maurel's avatar
Guy Maurel committed
250 251
    // Ugly, but at least the API is clean.
    const HeaderGroup headerGroup = static_cast<HeaderGroup>((role / static_cast<int>(TerminalUserRole)));
252

Guy Maurel's avatar
Guy Maurel committed
253 254 255 256 257
    role %= TerminalUserRole;
    if (!index.isValid()) {
        if (ColumnCountRole != role) {
            return QVariant();
        }
258

Guy Maurel's avatar
Guy Maurel committed
259 260
        return entityColumnCount(headerGroup);
    }
261

Guy Maurel's avatar
Guy Maurel committed
262 263 264
    if (ColumnCountRole == role) {
        return entityColumnCount(headerGroup);
    }
265

Guy Maurel's avatar
Guy Maurel committed
266
    const Node *node = reinterpret_cast<Node *>(index.internalPointer());
267

Guy Maurel's avatar
Guy Maurel committed
268
    if (ParentCollectionRole == role &&
Laurent Montel's avatar
Laurent Montel committed
269
            d->m_collectionFetchStrategy != FetchNoCollections) {
Guy Maurel's avatar
Guy Maurel committed
270 271
        const Collection parentCollection = d->m_collections.value(node->parent);
        Q_ASSERT(parentCollection.isValid());
272

Guy Maurel's avatar
Guy Maurel committed
273
        return QVariant::fromValue(parentCollection);
274
    }
275

Guy Maurel's avatar
Guy Maurel committed
276 277 278 279 280 281
    if (Node::Collection == node->type) {

        const Collection collection = d->m_collections.value(node->id);

        if (!collection.isValid()) {
            return QVariant();
282
        }
283

Guy Maurel's avatar
Guy Maurel committed
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
        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 IsPopulatedRole: {
            return d->m_populatedCols.contains(collection.id());
        }
315 316 317
        case OriginalCollectionNameRole: {
            return entityData(collection, index.column(), Qt::DisplayRole);
        }
Guy Maurel's avatar
Guy Maurel committed
318 319 320 321 322 323 324 325 326
        case Qt::BackgroundRole: {
            if (collection.hasAttribute<EntityDisplayAttribute>()) {
                EntityDisplayAttribute *eda = collection.attribute<EntityDisplayAttribute>();
                QColor color = eda->backgroundColor();
                if (color.isValid()) {
                    return color;
                }
            }
            // fall through.
Laurent Montel's avatar
Laurent Montel committed
327 328 329
#if QT_VERSION >= QT_VERSION_CHECK(5,8,0)
        Q_FALLTHROUGH();
#endif
Guy Maurel's avatar
Guy Maurel committed
330 331 332 333 334
        }
        default:
            return entityData(collection, index.column(), role);
            break;
        }
335

Guy Maurel's avatar
Guy Maurel committed
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
    } 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.
Laurent Montel's avatar
Laurent Montel committed
378 379 380
#if QT_VERSION >= QT_VERSION_CHECK(5,8,0)
        Q_FALLTHROUGH();
#endif
Guy Maurel's avatar
Guy Maurel committed
381 382 383 384
        }
        default:
            return entityData(item, index.column(), role);
            break;
385
        }
386
    }
Tobias Koenig's avatar
Tobias Koenig committed
387

Guy Maurel's avatar
Guy Maurel committed
388
    return QVariant();
389 390
}

Guy Maurel's avatar
Guy Maurel committed
391
Qt::ItemFlags EntityTreeModel::flags(const QModelIndex &index) const
392
{
Guy Maurel's avatar
Guy Maurel committed
393 394 395
    Q_D(const EntityTreeModel);
    // Pass modeltest.
    if (!index.isValid()) {
David Faure's avatar
David Faure committed
396
        return {};
Guy Maurel's avatar
Guy Maurel committed
397
    }
398

Guy Maurel's avatar
Guy Maurel committed
399
    Qt::ItemFlags flags = QAbstractItemModel::flags(index);
400

Guy Maurel's avatar
Guy Maurel committed
401
    const Node *node = reinterpret_cast<Node *>(index.internalPointer());
402

Guy Maurel's avatar
Guy Maurel committed
403 404 405 406 407
    if (Node::Collection == node->type) {
        // cut out entities will be shown as inactive
        if (d->m_pendingCutCollections.contains(node->id)) {
            return Qt::ItemIsSelectable;
        }
408

Guy Maurel's avatar
Guy Maurel committed
409 410
        const Collection collection = d->m_collections.value(node->id);
        if (collection.isValid()) {
411

Guy Maurel's avatar
Guy Maurel committed
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
            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;
            }
431

Guy Maurel's avatar
Guy Maurel committed
432 433
            // dragging is always possible, even for read-only objects, but they can only be copied, not moved.
            flags |= Qt::ItemIsDragEnabled;
434

435
        }
Guy Maurel's avatar
Guy Maurel committed
436 437 438 439
    } else if (Node::Item == node->type) {
        if (d->m_pendingCutItems.contains(node->id)) {
            return Qt::ItemIsSelectable;
        }
440

Guy Maurel's avatar
Guy Maurel committed
441
        // Rights come from the parent collection.
442

Guy Maurel's avatar
Guy Maurel committed
443 444 445 446 447
        Collection parentCollection;
        if (!index.parent().isValid()) {
            parentCollection = d->m_rootCollection;
        } else {
            const Node *parentNode = reinterpret_cast<Node *>(index.parent().internalPointer());
448

Guy Maurel's avatar
Guy Maurel committed
449 450 451 452
            parentCollection = d->m_collections.value(parentNode->id);
        }
        if (parentCollection.isValid()) {
            const int rights = parentCollection.rights();
453

Guy Maurel's avatar
Guy Maurel committed
454
            // Can't drop onto items.
Laurent Montel's avatar
Laurent Montel committed
455
            if (rights & Collection::CanChangeItem && index.column() == 0) {
David Faure's avatar
David Faure committed
456
                flags |= Qt::ItemIsEditable;
Guy Maurel's avatar
Guy Maurel committed
457 458 459 460
            }
            // dragging is always possible, even for read-only objects, but they can only be copied, not moved.
            flags |= Qt::ItemIsDragEnabled;
        }
461 462
    }

Guy Maurel's avatar
Guy Maurel committed
463
    return flags;
464 465 466 467
}

Qt::DropActions EntityTreeModel::supportedDropActions() const
{
Guy Maurel's avatar
Guy Maurel committed
468
    return (Qt::CopyAction | Qt::MoveAction | Qt::LinkAction);
469 470 471 472
}

QStringList EntityTreeModel::mimeTypes() const
{
Guy Maurel's avatar
Guy Maurel committed
473
    // TODO: Should this return the mimetypes that the items provide? Allow dragging a contact from here for example.
474
    return QStringList() << QStringLiteral("text/uri-list");
475 476
}

Guy Maurel's avatar
Guy Maurel committed
477
bool EntityTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
478
{
Guy Maurel's avatar
Guy Maurel committed
479 480 481
    Q_UNUSED(row);
    Q_UNUSED(column);
    Q_D(EntityTreeModel);
482

Guy Maurel's avatar
Guy Maurel committed
483 484 485 486
    // Can't drop onto Collection::root.
    if (!parent.isValid()) {
        return false;
    }
Stephen Kelly's avatar
Stephen Kelly committed
487

Guy Maurel's avatar
Guy Maurel committed
488
    // TODO Use action and collection rights and return false if necessary
489

Guy Maurel's avatar
Guy Maurel committed
490 491 492 493 494
    // 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.
Stephen Kelly's avatar
Stephen Kelly committed
495

Guy Maurel's avatar
Guy Maurel committed
496
    // Handle dropping between items as well as on items.
Tobias Koenig's avatar
Tobias Koenig committed
497
//   if ( row != -1 && column != -1 )
Stephen Kelly's avatar
Stephen Kelly committed
498 499 500
//   {
//   }

Guy Maurel's avatar
Guy Maurel committed
501 502 503
    if (action == Qt::IgnoreAction) {
        return true;
    }
504 505

// Shouldn't do this. Need to be able to drop vcards for example.
Tobias Koenig's avatar
Tobias Koenig committed
506
//   if ( !data->hasFormat( "text/uri-list" ) )
507 508
//       return false;

Guy Maurel's avatar
Guy Maurel committed
509
    Node *node = reinterpret_cast<Node *>(parent.internalId());
Stephen Kelly's avatar
Stephen Kelly committed
510

Guy Maurel's avatar
Guy Maurel committed
511
    Q_ASSERT(node);
512

Guy Maurel's avatar
Guy Maurel committed
513 514 515 516
    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.
517
            qCWarning(AKONADICORE_LOG) << "Dropped onto item with no parent collection";
Guy Maurel's avatar
Guy Maurel committed
518 519
            return true;
        }
520

Guy Maurel's avatar
Guy Maurel committed
521 522
        // A drop onto an item should be considered as a drop onto its parent collection
        node = reinterpret_cast<Node *>(parent.parent().internalId());
523
    }
524

Guy Maurel's avatar
Guy Maurel committed
525 526
    if (Node::Collection == node->type) {
        const Collection destCollection = d->m_collections.value(node->id);
527

Guy Maurel's avatar
Guy Maurel committed
528 529 530 531 532
        // 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;
        }
533

534
        if (data->hasFormat(QStringLiteral("text/uri-list"))) {
Guy Maurel's avatar
Guy Maurel committed
535 536 537 538

            MimeTypeChecker mimeChecker;
            mimeChecker.setWantedMimeTypes(destCollection.contentMimeTypes());

Laurent Montel's avatar
Laurent Montel committed
539
            const QList<QUrl> urls = data->urls();
Laurent Montel's avatar
Laurent Montel committed
540
            for (const QUrl &url : urls) {
Guy Maurel's avatar
Guy Maurel committed
541 542 543
                const Collection collection = d->m_collections.value(Collection::fromUrl(url).id());
                if (collection.isValid()) {
                    if (collection.parentCollection().id() == destCollection.id() &&
Laurent Montel's avatar
Laurent Montel committed
544
                            action != Qt::CopyAction) {
545
                        qCWarning(AKONADICORE_LOG) << "Error: source and destination of move are the same.";
Guy Maurel's avatar
Guy Maurel committed
546 547 548 549
                        return false;
                    }

                    if (!mimeChecker.isWantedCollection(collection)) {
550
                        qCDebug(AKONADICORE_LOG) << "unwanted collection" << mimeChecker.wantedMimeTypes() << collection.contentMimeTypes();
Guy Maurel's avatar
Guy Maurel committed
551 552 553
                        return false;
                    }

554 555 556
                    QUrlQuery query(url);
                    if (query.hasQueryItem(QStringLiteral("name"))) {
                        const QString collectionName = query.queryItemValue(QStringLiteral("name"));
Guy Maurel's avatar
Guy Maurel committed
557 558 559
                        const QStringList collectionNames = d->childCollectionNames(destCollection);

                        if (collectionNames.contains(collectionName)) {
Laurent Montel's avatar
Laurent Montel committed
560
                            QMessageBox::critical(nullptr, i18n("Error"),
561
                                                  i18n("The target collection '%1' contains already\na collection with name '%2'.",
Guy Maurel's avatar
Guy Maurel committed
562 563 564 565 566 567 568 569
                                                       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) {
570
                            qCWarning(AKONADICORE_LOG) << "Error: source and destination of move are the same.";
Guy Maurel's avatar
Guy Maurel committed
571 572 573 574
                            return false;
                        }

                        if (!mimeChecker.isWantedItem(item)) {
575
                            qCDebug(AKONADICORE_LOG) << "unwanted item" << mimeChecker.wantedMimeTypes() << item.mimeType();
Guy Maurel's avatar
Guy Maurel committed
576 577 578 579
                            return false;
                        }
                    }
                }
580 581
            }

Guy Maurel's avatar
Guy Maurel committed
582 583 584
            KJob *job = PasteHelper::pasteUriList(data, destCollection, action, d->m_session);
            if (!job) {
                return false;
Stephen Kelly's avatar
Stephen Kelly committed
585 586
            }

Guy Maurel's avatar
Guy Maurel committed
587
            connect(job, SIGNAL(result(KJob*)), SLOT(pasteJobDone(KJob*)));
Stephen Kelly's avatar
Stephen Kelly committed
588

Guy Maurel's avatar
Guy Maurel committed
589 590 591
            // Accpet the event so that it doesn't propagate.
            return true;
        } else {
592
//       not a set of uris. Maybe vcards etc. Check if the parent supports them, and maybe do
Guy Maurel's avatar
Guy Maurel committed
593 594 595
            // fromMimeData for them. Hmm, put it in the same transaction with the above?
            // TODO: This should be handled first, not last.
        }
596 597
    }

Guy Maurel's avatar
Guy Maurel committed
598
    return false;
599 600
}

Guy Maurel's avatar
Guy Maurel committed
601
QModelIndex EntityTreeModel::index(int row, int column, const QModelIndex &parent) const
602 603
{

Guy Maurel's avatar
Guy Maurel committed
604
    Q_D(const EntityTreeModel);
605

Guy Maurel's avatar
Guy Maurel committed
606 607 608
    if (parent.column() > 0) {
        return QModelIndex();
    }
Stephen Kelly's avatar
Stephen Kelly committed
609

Guy Maurel's avatar
Guy Maurel committed
610 611
    //TODO: don't use column count here? Use some d-> func.
    if (column >= columnCount() ||
Laurent Montel's avatar
Laurent Montel committed
612
            column < 0) {
Guy Maurel's avatar
Guy Maurel committed
613 614
        return QModelIndex();
    }
615

Guy Maurel's avatar
Guy Maurel committed
616
    QList<Node *> childEntities;
617

Guy Maurel's avatar
Guy Maurel committed
618
    const Node *parentNode = reinterpret_cast<Node *>(parent.internalPointer());
619

Guy Maurel's avatar
Guy Maurel committed
620 621 622 623 624 625
    if (!parentNode || !parent.isValid()) {
        if (d->m_showRootCollection) {
            childEntities << d->m_childEntities.value(-1);
        } else {
            childEntities = d->m_childEntities.value(d->m_rootCollection.id());
        }
626
    } else {
Guy Maurel's avatar
Guy Maurel committed
627 628 629
        if (parentNode->id >= 0) {
            childEntities = d->m_childEntities.value(parentNode->id);
        }
630
    }
631

Guy Maurel's avatar
Guy Maurel committed
632 633 634 635
    const int size = childEntities.size();
    if (row < 0 || row >= size) {
        return QModelIndex();
    }
636

Guy Maurel's avatar
Guy Maurel committed
637
    Node *node = childEntities.at(row);
638

Guy Maurel's avatar
Guy Maurel committed
639
    return createIndex(row, column, reinterpret_cast<void *>(node));
640 641
}

Guy Maurel's avatar
Guy Maurel committed
642
QModelIndex EntityTreeModel::parent(const QModelIndex &index) const
643
{
Guy Maurel's avatar
Guy Maurel committed
644
    Q_D(const EntityTreeModel);
645

Guy Maurel's avatar
Guy Maurel committed
646 647 648
    if (!index.isValid()) {
        return QModelIndex();
    }
649

Guy Maurel's avatar
Guy Maurel committed
650
    if (d->m_collectionFetchStrategy == InvisibleCollectionFetch ||
Laurent Montel's avatar
Laurent Montel committed
651
            d->m_collectionFetchStrategy == FetchNoCollections) {
Guy Maurel's avatar
Guy Maurel committed
652 653
        return QModelIndex();
    }
654

Guy Maurel's avatar
Guy Maurel committed
655
    const Node *node = reinterpret_cast<Node *>(index.internalPointer());
656

Guy Maurel's avatar
Guy Maurel committed
657 658 659
    if (!node) {
        return QModelIndex();
    }
660

Guy Maurel's avatar
Guy Maurel committed
661
    const Collection collection = d->m_collections.value(node->parent);
662

Guy Maurel's avatar
Guy Maurel committed
663 664 665
    if (!collection.isValid()) {
        return QModelIndex();
    }
666

Guy Maurel's avatar
Guy Maurel committed
667 668 669 670 671 672
    if (collection.id() == d->m_rootCollection.id()) {
        if (!d->m_showRootCollection) {
            return QModelIndex();
        } else {
            return createIndex(0, 0, reinterpret_cast<void *>(d->m_rootNode));
        }
673
    }
674

Guy Maurel's avatar
Guy Maurel committed
675 676
    Q_ASSERT(collection.parentCollection().isValid());
    const int row = d->indexOf<Node::Collection>(d->m_childEntities.value(collection.parentCollection().id()), collection.id());
677

Guy Maurel's avatar
Guy Maurel committed
678 679
    Q_ASSERT(row >= 0);
    Node *parentNode = d->m_childEntities.value(collection.parentCollection().id()).at(row);
680

Guy Maurel's avatar
Guy Maurel committed
681
    return createIndex(row, 0, reinterpret_cast<void *>(parentNode));
682 683
}

Guy Maurel's avatar
Guy Maurel committed
684
int EntityTreeModel::rowCount(const QModelIndex &parent) const
685
{
Guy Maurel's avatar
Guy Maurel committed
686
    Q_D(const EntityTreeModel);
687

Guy Maurel's avatar
Guy Maurel committed
688
    if (d->m_collectionFetchStrategy == InvisibleCollectionFetch ||
Laurent Montel's avatar
Laurent Montel committed
689
            d->m_collectionFetchStrategy == FetchNoCollections) {
Guy Maurel's avatar
Guy Maurel committed
690 691 692 693 694
        if (parent.isValid()) {
            return 0;
        } else {
            return d->m_items.size();
        }
695
    }
696

Guy Maurel's avatar
Guy Maurel committed
697 698 699 700 701 702
    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();
703
    }
704

Guy Maurel's avatar
Guy Maurel committed
705 706 707
    if (parent.column() != 0) {
        return 0;
    }
708

Guy Maurel's avatar
Guy Maurel committed
709
    const Node *node = reinterpret_cast<Node *>(parent.internalPointer());
710

Guy Maurel's avatar
Guy Maurel committed
711 712 713
    if (!node) {
        return 0;
    }
714

Guy Maurel's avatar
Guy Maurel committed
715 716 717
    if (Node::Item == node->type) {
        return 0;
    }
718

Guy Maurel's avatar
Guy Maurel committed
719 720
    Q_ASSERT(parent.isValid());
    return d->m_childEntities.value(node->id).size();
721 722
}

Guy Maurel's avatar
Guy Maurel committed
723
int EntityTreeModel::entityColumnCount(HeaderGroup headerGroup) const
724
{
Guy Maurel's avatar
Guy Maurel committed
725 726
    // Not needed in this model.
    Q_UNUSED(headerGroup);
727

Guy Maurel's avatar
Guy Maurel committed
728
    return 1;
729 730
}

Guy Maurel's avatar
Guy Maurel committed
731
QVariant EntityTreeModel::entityHeaderData(int section, Qt::Orientation orientation, int role, HeaderGroup headerGroup) const
732
{
Guy Maurel's avatar
Guy Maurel committed
733 734 735 736 737
    Q_D(const EntityTreeModel);
    // Not needed in this model.
    Q_UNUSED(headerGroup);

    if (section == 0 &&
Laurent Montel's avatar
Laurent Montel committed
738 739
            orientation == Qt::Horizontal &&
            role == Qt::DisplayRole) {
Guy Maurel's avatar
Guy Maurel committed
740 741 742 743
        if (d->m_rootCollection == Collection::root()) {
            return i18nc("@title:column Name of a thing", "Name");
        }
        return d->m_rootCollection.name();
744
    }
745

Guy Maurel's avatar
Guy Maurel committed
746
    return QAbstractItemModel::headerData(section, orientation, role);
747 748
}

Guy Maurel's avatar
Guy Maurel committed
749
QVariant EntityTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
750
{
Guy Maurel's avatar
Guy Maurel committed
751
    const HeaderGroup headerGroup = static_cast<HeaderGroup>((role / static_cast<int>(TerminalUserRole)));
752

Guy Maurel's avatar
Guy Maurel committed
753 754
    role %= TerminalUserRole;
    return entityHeaderData(section, orientation, role, headerGroup);
755 756
}

Guy Maurel's avatar
Guy Maurel committed
757
QMimeData *EntityTreeModel::mimeData(const QModelIndexList &indexes) const
758
{
Guy Maurel's avatar
Guy Maurel committed
759
    Q_D(const EntityTreeModel);
760

Guy Maurel's avatar
Guy Maurel committed
761
    QMimeData *data = new QMimeData();
Laurent Montel's avatar
Laurent Montel committed
762
    QList<QUrl> urls;
Laurent Montel's avatar
Laurent Montel committed
763
    for (const QModelIndex &index : indexes) {
Guy Maurel's avatar
Guy Maurel committed
764 765 766
        if (index.column() != 0) {
            continue;
        }
767

Guy Maurel's avatar
Guy Maurel committed
768 769 770
        if (!index.isValid()) {
            continue;
        }
771

Guy Maurel's avatar
Guy Maurel committed
772
        const Node *node = reinterpret_cast<Node *>(index.internalPointer());
773

Guy Maurel's avatar
Guy Maurel committed
774 775 776
        if (Node::Collection == node->type) {
            urls << d->m_collections.value(node->id).url(Collection::UrlWithName);
        } else if (Node::Item == node->type) {
Laurent Montel's avatar
Laurent Montel committed
777
            QUrl url = d->m_items.value(node->id).url(Item::Item::UrlWithMimeType);
778
            QUrlQuery query(url);
779 780
            query.addQueryItem(QStringLiteral("parent"), QString::number(node->parent));
            url.setQuery(query);
781
            urls << url;
Guy Maurel's avatar
Guy Maurel committed
782 783 784
        } else { // if that happens something went horrible wrong
            Q_ASSERT(false);
        }
785
    }
786

Laurent Montel's avatar
Laurent Montel committed
787
    data->setUrls(urls);
788

Guy Maurel's avatar
Guy Maurel committed
789
    return data;
790 791 792
}

// Always return false for actions which take place asyncronously, eg via a Job.
Guy Maurel's avatar
Guy Maurel committed
793
bool EntityTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
794
{
Guy Maurel's avatar
Guy Maurel committed
795
    Q_D(EntityTreeModel);
796

Guy Maurel's avatar
Guy Maurel committed
797
    const Node *node = reinterpret_cast<Node *>(index.internalPointer());
798

Guy Maurel's avatar
Guy Maurel committed
799 800 801 802 803
    if (role == PendingCutRole) {
        if (index.isValid() && value.toBool()) {
            if (Node::Collection == node->type) {
                d->m_pendingCutCollections.append(node->id);
            }
804

Guy Maurel's avatar
Guy Maurel committed
805 806 807 808 809 810 811 812
            if (Node::Item == node->type) {
                d->m_pendingCutItems.append(node->id);
            }
        } else {
            d->m_pendingCutCollections.clear();
            d->m_pendingCutItems.clear();
        }
        return true;
813
    }
Guy Maurel's avatar
Guy Maurel committed
814 815

    if (index.isValid() &&
Laurent Montel's avatar
Laurent Montel committed
816 817 818
            node->type == Node::Collection &&
            (role == CollectionRefRole ||
             role == CollectionDerefRole)) {
Guy Maurel's avatar
Guy Maurel committed
819 820 821 822 823 824 825 826 827
        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;
828
    }
829

Guy Maurel's avatar
Guy Maurel committed
830
    if (index.column() == 0 &&
Laurent Montel's avatar
Laurent Montel committed
831
            (role & (Qt::EditRole | ItemRole | CollectionRole))) {
Guy Maurel's avatar
Guy Maurel committed
832
        if (Node::Collection == node->type) {
833

Guy Maurel's avatar
Guy Maurel committed
834
            Collection collection = d->m_collections.value(node->id);
835

Guy Maurel's avatar
Guy Maurel committed
836 837 838
            if (!collection.isValid() || !value.isValid()) {
                return false;
            }
839

Guy Maurel's avatar
Guy Maurel committed
840 841
            if (Qt::EditRole == role) {
                collection.setName(value.toString());
842

Guy Maurel's avatar
Guy Maurel committed
843 844 845 846 847
                if (collection.hasAttribute<EntityDisplayAttribute>()) {
                    EntityDisplayAttribute *displayAttribute = collection.attribute<EntityDisplayAttribute>();
                    displayAttribute->setDisplayName(value.toString());
                }
            }
848

Guy Maurel's avatar
Guy Maurel committed
849 850
            if (Qt::BackgroundRole == role) {
                QColor color = value.value<QColor>();
851

Guy Maurel's avatar
Guy Maurel committed
852 853 854
                if (!color.isValid()) {
                    return false;
                }
855

856
                EntityDisplayAttribute *eda = collection.attribute<EntityDisplayAttribute>(Collection::AddIfMissing);
Guy Maurel's avatar
Guy Maurel committed
857 858
                eda->setBackgroundColor(color);
            }
859

Guy Maurel's avatar
Guy Maurel committed
860 861 862
            if (CollectionRole == role) {
                collection = value.value<Collection>();
            }
863

Guy Maurel's avatar
Guy Maurel committed
864 865 866
            CollectionModifyJob *job = new CollectionModifyJob(collection, d->m_session);
            connect(job, SIGNAL(result(KJob*)),
                    SLOT(updateJobDone(KJob*)));
867

Guy Maurel's avatar
Guy Maurel committed
868 869
            return false;
        } else if (Node::Item == node->type) {
870

Guy Maurel's avatar
Guy Maurel committed