1/*
2 Copyright (c) 2008 Stephen Kelly <steveire@gmail.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#include "entitytreemodel.h"
21#include "entitytreemodel_p.h"
22
23#include "monitor_p.h"
24
25#include <QtCore/QHash>
26#include <QtCore/QMimeData>
27#include <QtCore/QTimer>
28#include <QAbstractProxyModel>
29
30#include <KDE/KIcon>
31#include <KDE/KLocalizedString>
32#include <KDE/KMessageBox>
33#include <KDE/KUrl>
34
35#include <akonadi/attributefactory.h>
36#include <akonadi/changerecorder.h>
37#include <akonadi/collectionmodifyjob.h>
38#include <akonadi/entitydisplayattribute.h>
39#include <akonadi/transactionsequence.h>
40#include <akonadi/itemmodifyjob.h>
41#include <akonadi/session.h>
42#include "collectionfetchscope.h"
43
44#include "collectionutils_p.h"
45
46#include "kdebug.h"
47#include "pastehelper_p.h"
48
49Q_DECLARE_METATYPE(QSet<QByteArray>)
50
51using namespace Akonadi;
52
53EntityTreeModel::EntityTreeModel(ChangeRecorder *monitor, QObject *parent)
54 : QAbstractItemModel(parent)
55 , d_ptr(new EntityTreeModelPrivate(this))
56{
57 Q_D(EntityTreeModel);
58 d->init(monitor);
59}
60
61EntityTreeModel::EntityTreeModel(ChangeRecorder *monitor, EntityTreeModelPrivate *d, QObject *parent)
62 : QAbstractItemModel(parent)
63 , d_ptr(d)
64{
65 d->init(monitor);
66}
67
68EntityTreeModel::~EntityTreeModel()
69{
70 Q_D(EntityTreeModel);
71
72 foreach (const QList<Node *> &list, d->m_childEntities) {
73 QList<Node *>::const_iterator it = list.constBegin();
74 const QList<Node *>::const_iterator end = list.constEnd();
75 for (; it != end; ++it) {
76 delete *it;
77 }
78 }
79
80 d->m_rootNode = 0;
81
82 delete d_ptr;
83}
84
85bool EntityTreeModel::includeUnsubscribed() const
86{
87 return (listFilter() == CollectionFetchScope::NoFilter);
88}
89
90void EntityTreeModel::setIncludeUnsubscribed(bool show)
91{
92 if (show) {
93 setListFilter(CollectionFetchScope::NoFilter);
94 } else {
95 setListFilter(CollectionFetchScope::Enabled);
96 }
97}
98
99CollectionFetchScope::ListFilter EntityTreeModel::listFilter() const
100{
101 Q_D(const EntityTreeModel);
102 return d->m_listFilter;
103}
104
105void EntityTreeModel::setListFilter(CollectionFetchScope::ListFilter filter)
106{
107 Q_D(EntityTreeModel);
108 d->beginResetModel();
109 d->m_listFilter = filter;
110 d->m_monitor->setAllMonitored(filter == CollectionFetchScope::NoFilter);
111 d->endResetModel();
112}
113
114void EntityTreeModel::setCollectionsMonitored(const Collection::List &collections)
115{
116 Q_D(EntityTreeModel);
117 d->beginResetModel();
118 foreach(const Akonadi::Collection &col, d->m_monitor->collectionsMonitored()) {
119 d->m_monitor->setCollectionMonitored(col, false);
120 }
121 foreach(const Akonadi::Collection &col, collections) {
122 d->m_monitor->setCollectionMonitored(col, true);
123 }
124 d->endResetModel();
125}
126
127void EntityTreeModel::setCollectionMonitored(const Collection &col, bool monitored)
128{
129 Q_D(EntityTreeModel);
130 d->m_monitor->setCollectionMonitored(col, monitored);
131}
132
133void EntityTreeModel::setCollectionReferenced(const Akonadi::Collection &col, bool referenced)
134{
135 Q_D(EntityTreeModel);
136 d->m_monitor->setCollectionMonitored(col, referenced);
137 Akonadi::Collection referencedCollection = col;
138 referencedCollection.setReferenced(referenced);
139 //We have to use the same session as the monitor, so the monitor can fetch the collection afterwards
140 new Akonadi::CollectionModifyJob(referencedCollection, d->m_monitor->session());
141}
142
143bool EntityTreeModel::systemEntitiesShown() const
144{
145 Q_D(const EntityTreeModel);
146 return d->m_showSystemEntities;
147}
148
149void EntityTreeModel::setShowSystemEntities(bool show)
150{
151 Q_D(EntityTreeModel);
152 d->m_showSystemEntities = show;
153}
154
155void EntityTreeModel::clearAndReset()
156{
157 Q_D(EntityTreeModel);
158 d->beginResetModel();
159 d->endResetModel();
160}
161
162int EntityTreeModel::columnCount(const QModelIndex &parent) const
163{
164// TODO: Statistics?
165 if (parent.isValid() &&
166 parent.column() != 0) {
167 return 0;
168 }
169
170 return qMax(entityColumnCount(CollectionTreeHeaders), entityColumnCount(ItemListHeaders));
171}
172
173QVariant EntityTreeModel::entityData(const Item &item, int column, int role) const
174{
175 if (column == 0) {
176 switch (role) {
177 case Qt::DisplayRole:
178 case Qt::EditRole:
179 if (item.hasAttribute<EntityDisplayAttribute>() &&
180 !item.attribute<EntityDisplayAttribute>()->displayName().isEmpty()) {
181 return item.attribute<EntityDisplayAttribute>()->displayName();
182 } else {
183 if (!item.remoteId().isEmpty()) {
184 return item.remoteId();
185 }
186 return QString(QLatin1String("<") + QString::number(item.id()) + QLatin1String(">"));
187 }
188 break;
189 case Qt::DecorationRole:
190 if (item.hasAttribute<EntityDisplayAttribute>() &&
191 !item.attribute<EntityDisplayAttribute>()->iconName().isEmpty()) {
192 return item.attribute<EntityDisplayAttribute>()->icon();
193 }
194 break;
195 default:
196 break;
197 }
198 }
199
200 return QVariant();
201}
202
203QVariant EntityTreeModel::entityData(const Collection &collection, int column, int role) const
204{
205 Q_D(const EntityTreeModel);
206
207 if (column > 0) {
208 return QString();
209 }
210
211 if (collection == Collection::root()) {
212 // Only display the root collection. It may not be edited.
213 if (role == Qt::DisplayRole) {
214 return d->m_rootCollectionDisplayName;
215 }
216
217 if (role == Qt::EditRole) {
218 return QVariant();
219 }
220 }
221
222 switch (role) {
223 case Qt::DisplayRole:
224 case Qt::EditRole:
225 if (column == 0) {
226 const QString displayName = collection.displayName();
227 if (!displayName.isEmpty()) {
228 return displayName;
229 } else {
230 return i18n("Loading...");
231 }
232 }
233 break;
234 case Qt::DecorationRole:
235 if (collection.hasAttribute<EntityDisplayAttribute>() &&
236 !collection.attribute<EntityDisplayAttribute>()->iconName().isEmpty()) {
237 return collection.attribute<EntityDisplayAttribute>()->icon();
238 }
239 return KIcon(CollectionUtils::defaultIconName(collection));
240 default:
241 break;
242 }
243
244 return QVariant();
245}
246
247QVariant EntityTreeModel::data(const QModelIndex &index, int role) const
248{
249 Q_D(const EntityTreeModel);
250 if (role == SessionRole) {
251 return QVariant::fromValue(qobject_cast<QObject *>(d->m_session));
252 }
253
254 // Ugly, but at least the API is clean.
255 const HeaderGroup headerGroup = static_cast<HeaderGroup>((role / static_cast<int>(TerminalUserRole)));
256
257 role %= TerminalUserRole;
258 if (!index.isValid()) {
259 if (ColumnCountRole != role) {
260 return QVariant();
261 }
262
263 return entityColumnCount(headerGroup);
264 }
265
266 if (ColumnCountRole == role) {
267 return entityColumnCount(headerGroup);
268 }
269
270 const Node *node = reinterpret_cast<Node *>(index.internalPointer());
271
272 if (ParentCollectionRole == role &&
273 d->m_collectionFetchStrategy != FetchNoCollections) {
274 const Collection parentCollection = d->m_collections.value(node->parent);
275 Q_ASSERT(parentCollection.isValid());
276
277 return QVariant::fromValue(parentCollection);
278 }
279
280 if (Node::Collection == node->type) {
281
282 const Collection collection = d->m_collections.value(node->id);
283
284 if (!collection.isValid()) {
285 return QVariant();
286 }
287
288 switch (role) {
289 case MimeTypeRole:
290 return collection.mimeType();
291 break;
292 case RemoteIdRole:
293 return collection.remoteId();
294 break;
295 case CollectionIdRole:
296 return collection.id();
297 break;
298 case ItemIdRole:
299 // QVariant().toInt() is 0, not -1, so we have to handle the ItemIdRole
300 // and CollectionIdRole (below) specially
301 return -1;
302 break;
303 case CollectionRole:
304 return QVariant::fromValue(collection);
305 break;
306 case EntityUrlRole:
307 return collection.url().url();
308 break;
309 case UnreadCountRole: {
310 CollectionStatistics statistics = collection.statistics();
311 return statistics.unreadCount();
312 }
313 case FetchStateRole: {
314 return d->m_pendingCollectionRetrieveJobs.contains(collection.id()) ? FetchingState : IdleState;
315 }
316 case CollectionSyncProgressRole: {
317 return d->m_collectionSyncProgress.value(collection.id());
318 }
319 case IsPopulatedRole: {
320 return d->m_populatedCols.contains(collection.id());
321 }
322 case Qt::BackgroundRole: {
323 if (collection.hasAttribute<EntityDisplayAttribute>()) {
324 EntityDisplayAttribute *eda = collection.attribute<EntityDisplayAttribute>();
325 QColor color = eda->backgroundColor();
326 if (color.isValid()) {
327 return color;
328 }
329 }
330 // fall through.
331 }
332 default:
333 return entityData(collection, index.column(), role);
334 break;
335 }
336
337 } else if (Node::Item == node->type) {
338 const Item item = d->m_items.value(node->id);
339 if (!item.isValid()) {
340 return QVariant();
341 }
342
343 switch (role) {
344 case ParentCollectionRole:
345 return QVariant::fromValue(item.parentCollection());
346 case MimeTypeRole:
347 return item.mimeType();
348 break;
349 case RemoteIdRole:
350 return item.remoteId();
351 break;
352 case ItemRole:
353 return QVariant::fromValue(item);
354 break;
355 case ItemIdRole:
356 return item.id();
357 break;
358 case CollectionIdRole:
359 return -1;
360 break;
361 case LoadedPartsRole:
362 return QVariant::fromValue(item.loadedPayloadParts());
363 break;
364 case AvailablePartsRole:
365 return QVariant::fromValue(item.availablePayloadParts());
366 break;
367 case EntityUrlRole:
368 return item.url(Akonadi::Item::UrlWithMimeType).url();
369 break;
370 case Qt::BackgroundRole: {
371 if (item.hasAttribute<EntityDisplayAttribute>()) {
372 EntityDisplayAttribute *eda = item.attribute<EntityDisplayAttribute>();
373 const QColor color = eda->backgroundColor();
374 if (color.isValid()) {
375 return color;
376 }
377 }
378 // fall through.
379 }
380 default:
381 return entityData(item, index.column(), role);
382 break;
383 }
384 }
385
386 return QVariant();
387}
388
389Qt::ItemFlags EntityTreeModel::flags(const QModelIndex &index) const
390{
391 Q_D(const EntityTreeModel);
392 // Pass modeltest.
393 if (!index.isValid()) {
394 return 0;
395 }
396
397 Qt::ItemFlags flags = QAbstractItemModel::flags(index);
398
399 const Node *node = reinterpret_cast<Node *>(index.internalPointer());
400
401 if (Node::Collection == node->type) {
402 // cut out entities will be shown as inactive
403 if (d->m_pendingCutCollections.contains(node->id)) {
404 return Qt::ItemIsSelectable;
405 }
406
407 const Collection collection = d->m_collections.value(node->id);
408 if (collection.isValid()) {
409
410 if (collection == Collection::root()) {
411 // Selectable and displayable only.
412 return flags;
413 }
414
415 const int rights = collection.rights();
416
417 if (rights & Collection::CanChangeCollection) {
418 if (index.column() == 0) {
419 flags |= Qt::ItemIsEditable;
420 }
421 // Changing the collection includes changing the metadata (child entityordering).
422 // Need to allow this by drag and drop.
423 flags |= Qt::ItemIsDropEnabled;
424 }
425 if (rights & (Collection::CanCreateCollection | Collection::CanCreateItem | Collection::CanLinkItem)) {
426 // Can we drop new collections and items into this collection?
427 flags |= Qt::ItemIsDropEnabled;
428 }
429
430 // dragging is always possible, even for read-only objects, but they can only be copied, not moved.
431 flags |= Qt::ItemIsDragEnabled;
432
433 }
434 } else if (Node::Item == node->type) {
435 if (d->m_pendingCutItems.contains(node->id)) {
436 return Qt::ItemIsSelectable;
437 }
438
439 // Rights come from the parent collection.
440
441 Collection parentCollection;
442 if (!index.parent().isValid()) {
443 parentCollection = d->m_rootCollection;
444 } else {
445 const Node *parentNode = reinterpret_cast<Node *>(index.parent().internalPointer());
446
447 parentCollection = d->m_collections.value(parentNode->id);
448 }
449 if (parentCollection.isValid()) {
450 const int rights = parentCollection.rights();
451
452 // Can't drop onto items.
453 if (rights &Collection::CanChangeItem && index.column() == 0) {
454 flags = flags | Qt::ItemIsEditable;
455 }
456 // dragging is always possible, even for read-only objects, but they can only be copied, not moved.
457 flags |= Qt::ItemIsDragEnabled;
458 }
459 }
460
461 return flags;
462}
463
464Qt::DropActions EntityTreeModel::supportedDropActions() const
465{
466 return (Qt::CopyAction | Qt::MoveAction | Qt::LinkAction);
467}
468
469QStringList EntityTreeModel::mimeTypes() const
470{
471 // TODO: Should this return the mimetypes that the items provide? Allow dragging a contact from here for example.
472 return QStringList() << QLatin1String("text/uri-list");
473}
474
475bool EntityTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
476{
477 Q_UNUSED(row);
478 Q_UNUSED(column);
479 Q_D(EntityTreeModel);
480
481 // Can't drop onto Collection::root.
482 if (!parent.isValid()) {
483 return false;
484 }
485
486 // TODO Use action and collection rights and return false if necessary
487
488 // if row and column are -1, then the drop was on parent directly.
489 // data should then be appended on the end of the items of the collections as appropriate.
490 // That will mean begin insert rows etc.
491 // Otherwise it was a sibling of the row^th item of parent.
492 // Needs to be handled when ordering is accounted for.
493
494 // Handle dropping between items as well as on items.
495// if ( row != -1 && column != -1 )
496// {
497// }
498
499 if (action == Qt::IgnoreAction) {
500 return true;
501 }
502
503// Shouldn't do this. Need to be able to drop vcards for example.
504// if ( !data->hasFormat( "text/uri-list" ) )
505// return false;
506
507 Node *node = reinterpret_cast<Node *>(parent.internalId());
508
509 Q_ASSERT(node);
510
511 if (Node::Item == node->type) {
512 if (!parent.parent().isValid()) {
513 // The drop is somehow on an item with no parent (shouldn't happen)
514 // The drop should be considered handled anyway.
515 kWarning() << "Dropped onto item with no parent collection";
516 return true;
517 }
518
519 // A drop onto an item should be considered as a drop onto its parent collection
520 node = reinterpret_cast<Node *>(parent.parent().internalId());
521 }
522
523 if (Node::Collection == node->type) {
524 const Collection destCollection = d->m_collections.value(node->id);
525
526 // Applications can't create new collections in root. Only resources can.
527 if (destCollection == Collection::root()) {
528 // Accept the event so that it doesn't propagate.
529 return true;
530 }
531
532 if (data->hasFormat(QLatin1String("text/uri-list"))) {
533
534 MimeTypeChecker mimeChecker;
535 mimeChecker.setWantedMimeTypes(destCollection.contentMimeTypes());
536
537 const KUrl::List urls = KUrl::List::fromMimeData(data);
538 foreach (const KUrl &url, urls) {
539 const Collection collection = d->m_collections.value(Collection::fromUrl(url).id());
540 if (collection.isValid()) {
541 if (collection.parentCollection().id() == destCollection.id() &&
542 action != Qt::CopyAction) {
543 kDebug() << "Error: source and destination of move are the same.";
544 return false;
545 }
546
547 if (!mimeChecker.isWantedCollection(collection)) {
548 kDebug() << "unwanted collection" << mimeChecker.wantedMimeTypes() << collection.contentMimeTypes();
549 return false;
550 }
551
552 if (url.hasQueryItem(QLatin1String("name"))) {
553 const QString collectionName = url.queryItemValue(QLatin1String("name"));
554 const QStringList collectionNames = d->childCollectionNames(destCollection);
555
556 if (collectionNames.contains(collectionName)) {
557 KMessageBox::error(0, i18n("The target collection '%1' contains already\na collection with name '%2'.",
558 destCollection.name(), collection.name()));
559 return false;
560 }
561 }
562 } else {
563 const Item item = d->m_items.value(Item::fromUrl(url).id());
564 if (item.isValid()) {
565 if (item.parentCollection().id() == destCollection.id() && action != Qt::CopyAction) {
566 kDebug() << "Error: source and destination of move are the same.";
567 return false;
568 }
569
570 if (!mimeChecker.isWantedItem(item)) {
571 kDebug() << "unwanted item" << mimeChecker.wantedMimeTypes() << item.mimeType();
572 return false;
573 }
574 }
575 }
576 }
577
578 KJob *job = PasteHelper::pasteUriList(data, destCollection, action, d->m_session);
579 if (!job) {
580 return false;
581 }
582
583 connect(job, SIGNAL(result(KJob*)), SLOT(pasteJobDone(KJob*)));
584
585 // Accpet the event so that it doesn't propagate.
586 return true;
587 } else {
588// not a set of uris. Maybe vcards etc. Check if the parent supports them, and maybe do
589 // fromMimeData for them. Hmm, put it in the same transaction with the above?
590 // TODO: This should be handled first, not last.
591 }
592 }
593
594 return false;
595}
596
597QModelIndex EntityTreeModel::index(int row, int column, const QModelIndex &parent) const
598{
599
600 Q_D(const EntityTreeModel);
601
602 if (parent.column() > 0) {
603 return QModelIndex();
604 }
605
606 //TODO: don't use column count here? Use some d-> func.
607 if (column >= columnCount() ||
608 column < 0) {
609 return QModelIndex();
610 }
611
612 QList<Node *> childEntities;
613
614 const Node *parentNode = reinterpret_cast<Node *>(parent.internalPointer());
615
616 if (!parentNode || !parent.isValid()) {
617 if (d->m_showRootCollection) {
618 childEntities << d->m_childEntities.value(-1);
619 } else {
620 childEntities = d->m_childEntities.value(d->m_rootCollection.id());
621 }
622 } else {
623 if (parentNode->id >= 0) {
624 childEntities = d->m_childEntities.value(parentNode->id);
625 }
626 }
627
628 const int size = childEntities.size();
629 if (row < 0 || row >= size) {
630 return QModelIndex();
631 }
632
633 Node *node = childEntities.at(row);
634
635 return createIndex(row, column, reinterpret_cast<void *>(node));
636}
637
638QModelIndex EntityTreeModel::parent(const QModelIndex &index) const
639{
640 Q_D(const EntityTreeModel);
641
642 if (!index.isValid()) {
643 return QModelIndex();
644 }
645
646 if (d->m_collectionFetchStrategy == InvisibleCollectionFetch ||
647 d->m_collectionFetchStrategy == FetchNoCollections) {
648 return QModelIndex();
649 }
650
651 const Node *node = reinterpret_cast<Node *>(index.internalPointer());
652
653 if (!node) {
654 return QModelIndex();
655 }
656
657 const Collection collection = d->m_collections.value(node->parent);
658
659 if (!collection.isValid()) {
660 return QModelIndex();
661 }
662
663 if (collection.id() == d->m_rootCollection.id()) {
664 if (!d->m_showRootCollection) {
665 return QModelIndex();
666 } else {
667 return createIndex(0, 0, reinterpret_cast<void *>(d->m_rootNode));
668 }
669 }
670
671 Q_ASSERT(collection.parentCollection().isValid());
672 const int row = d->indexOf<Node::Collection>(d->m_childEntities.value(collection.parentCollection().id()), collection.id());
673
674 Q_ASSERT(row >= 0);
675 Node *parentNode = d->m_childEntities.value(collection.parentCollection().id()).at(row);
676
677 return createIndex(row, 0, reinterpret_cast<void *>(parentNode));
678}
679
680int EntityTreeModel::rowCount(const QModelIndex &parent) const
681{
682 Q_D(const EntityTreeModel);
683
684 if (d->m_collectionFetchStrategy == InvisibleCollectionFetch ||
685 d->m_collectionFetchStrategy == FetchNoCollections) {
686 if (parent.isValid()) {
687 return 0;
688 } else {
689 return d->m_items.size();
690 }
691 }
692
693 if (!parent.isValid()) {
694 // If we're showing the root collection then it will be the only child of the root.
695 if (d->m_showRootCollection) {
696 return d->m_childEntities.value(-1).size();
697 }
698 return d->m_childEntities.value(d->m_rootCollection.id()).size();
699 }
700
701 if (parent.column() != 0) {
702 return 0;
703 }
704
705 const Node *node = reinterpret_cast<Node *>(parent.internalPointer());
706
707 if (!node) {
708 return 0;
709 }
710
711 if (Node::Item == node->type) {
712 return 0;
713 }
714
715 Q_ASSERT(parent.isValid());
716 return d->m_childEntities.value(node->id).size();
717}
718
719int EntityTreeModel::entityColumnCount(HeaderGroup headerGroup) const
720{
721 // Not needed in this model.
722 Q_UNUSED(headerGroup);
723
724 return 1;
725}
726
727QVariant EntityTreeModel::entityHeaderData(int section, Qt::Orientation orientation, int role, HeaderGroup headerGroup) const
728{
729 Q_D(const EntityTreeModel);
730 // Not needed in this model.
731 Q_UNUSED(headerGroup);
732
733 if (section == 0 &&
734 orientation == Qt::Horizontal &&
735 role == Qt::DisplayRole) {
736 if (d->m_rootCollection == Collection::root()) {
737 return i18nc("@title:column Name of a thing", "Name");
738 }
739 return d->m_rootCollection.name();
740 }
741
742 return QAbstractItemModel::headerData(section, orientation, role);
743}
744
745QVariant EntityTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
746{
747 const HeaderGroup headerGroup = static_cast<HeaderGroup>((role / static_cast<int>(TerminalUserRole)));
748
749 role %= TerminalUserRole;
750 return entityHeaderData(section, orientation, role, headerGroup);
751}
752
753QMimeData *EntityTreeModel::mimeData(const QModelIndexList &indexes) const
754{
755 Q_D(const EntityTreeModel);
756
757 QMimeData *data = new QMimeData();
758 KUrl::List urls;
759 foreach (const QModelIndex &index, indexes) {
760 if (index.column() != 0) {
761 continue;
762 }
763
764 if (!index.isValid()) {
765 continue;
766 }
767
768 const Node *node = reinterpret_cast<Node *>(index.internalPointer());
769
770 if (Node::Collection == node->type) {
771 urls << d->m_collections.value(node->id).url(Collection::UrlWithName);
772 } else if (Node::Item == node->type) {
773 KUrl url = d->m_items.value(node->id).url(Item::Item::UrlWithMimeType);
774 // Encode the "virtual" parent
775 url.addQueryItem(QLatin1String("parent"), QString::number(node->parent));
776 urls << url;
777 } else { // if that happens something went horrible wrong
778 Q_ASSERT(false);
779 }
780 }
781
782 urls.populateMimeData(data);
783
784 return data;
785}
786
787// Always return false for actions which take place asyncronously, eg via a Job.
788bool EntityTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
789{
790 Q_D(EntityTreeModel);
791
792 const Node *node = reinterpret_cast<Node *>(index.internalPointer());
793
794 if (role == PendingCutRole) {
795 if (index.isValid() && value.toBool()) {
796 if (Node::Collection == node->type) {
797 d->m_pendingCutCollections.append(node->id);
798 }
799
800 if (Node::Item == node->type) {
801 d->m_pendingCutItems.append(node->id);
802 }
803 } else {
804 d->m_pendingCutCollections.clear();
805 d->m_pendingCutItems.clear();
806 }
807 return true;
808 }
809
810 if (index.isValid() &&
811 node->type == Node::Collection &&
812 (role == CollectionRefRole ||
813 role == CollectionDerefRole)) {
814 const Collection collection = index.data(CollectionRole).value<Collection>();
815 Q_ASSERT(collection.isValid());
816
817 if (role == CollectionDerefRole) {
818 d->deref(collection.id());
819 } else if (role == CollectionRefRole) {
820 d->ref(collection.id());
821 }
822 return true;
823 }
824
825 if (index.column() == 0 &&
826 (role &(Qt::EditRole | ItemRole | CollectionRole))) {
827 if (Node::Collection == node->type) {
828
829 Collection collection = d->m_collections.value(node->id);
830
831 if (!collection.isValid() || !value.isValid()) {
832 return false;
833 }
834
835 if (Qt::EditRole == role) {
836 collection.setName(value.toString());
837
838 if (collection.hasAttribute<EntityDisplayAttribute>()) {
839 EntityDisplayAttribute *displayAttribute = collection.attribute<EntityDisplayAttribute>();
840 displayAttribute->setDisplayName(value.toString());
841 }
842 }
843
844 if (Qt::BackgroundRole == role) {
845 QColor color = value.value<QColor>();
846
847 if (!color.isValid()) {
848 return false;
849 }
850
851 EntityDisplayAttribute *eda = collection.attribute<EntityDisplayAttribute>(Entity::AddIfMissing);
852 eda->setBackgroundColor(color);
853 }
854
855 if (CollectionRole == role) {
856 collection = value.value<Collection>();
857 }
858
859 CollectionModifyJob *job = new CollectionModifyJob(collection, d->m_session);
860 connect(job, SIGNAL(result(KJob*)),
861 SLOT(updateJobDone(KJob*)));
862
863 return false;
864 } else if (Node::Item == node->type) {
865
866 Item item = d->m_items.value(node->id);
867
868 if (!item.isValid() || !value.isValid()) {
869 return false;
870 }
871
872 if (Qt::EditRole == role) {
873 if (item.hasAttribute<EntityDisplayAttribute>()) {
874 EntityDisplayAttribute *displayAttribute = item.attribute<EntityDisplayAttribute>(Entity::AddIfMissing);
875 displayAttribute->setDisplayName(value.toString());
876 }
877 }
878
879 if (Qt::BackgroundRole == role) {
880 QColor color = value.value<QColor>();
881
882 if (!color.isValid()) {
883 return false;
884 }
885
886 EntityDisplayAttribute *eda = item.attribute<EntityDisplayAttribute>(Entity::AddIfMissing);
887 eda->setBackgroundColor(color);
888 }
889
890 if (ItemRole == role) {
891 item = value.value<Item>();
892 Q_ASSERT(item.id() == node->id);
893 }
894
895 ItemModifyJob *itemModifyJob = new ItemModifyJob(item, d->m_session);
896 connect(itemModifyJob, SIGNAL(result(KJob*)),
897 SLOT(updateJobDone(KJob*)));
898
899 return false;
900 }
901 }
902
903 return QAbstractItemModel::setData(index, value, role);
904}
905
906bool EntityTreeModel::canFetchMore(const QModelIndex &parent) const
907{
908 Q_UNUSED(parent)
909 return false;
910}
911
912void EntityTreeModel::fetchMore(const QModelIndex &parent)
913{
914 Q_D(EntityTreeModel);
915
916 if (!d->canFetchMore(parent)) {
917 return;
918 }
919
920 if (d->m_collectionFetchStrategy == InvisibleCollectionFetch) {
921 return;
922 }
923
924 if (d->m_itemPopulation == ImmediatePopulation) {
925 // Nothing to do. The items are already in the model.
926 return;
927 } else if (d->m_itemPopulation == LazyPopulation) {
928 const Collection collection = parent.data(CollectionRole).value<Collection>();
929
930 if (!collection.isValid()) {
931 return;
932 }
933
934 d->fetchItems(collection);
935 }
936}
937
938bool EntityTreeModel::hasChildren(const QModelIndex &parent) const
939{
940 Q_D(const EntityTreeModel);
941
942 if (d->m_collectionFetchStrategy == InvisibleCollectionFetch ||
943 d->m_collectionFetchStrategy == FetchNoCollections) {
944 return parent.isValid() ? false : !d->m_items.isEmpty();
945 }
946
947 // TODO: Empty collections right now will return true and get a little + to expand.
948 // There is probably no way to tell if a collection
949 // has child items in akonadi without first attempting an itemFetchJob...
950 // Figure out a way to fix this. (Statistics)
951 return ((rowCount(parent) > 0) ||
952 (canFetchMore(parent) && d->m_itemPopulation == LazyPopulation));
953}
954
955bool EntityTreeModel::isCollectionTreeFetched() const
956{
957 Q_D(const EntityTreeModel);
958
959 return d->m_collectionTreeFetched;
960}
961
962bool EntityTreeModel::isCollectionPopulated(Collection::Id id) const
963{
964 Q_D(const EntityTreeModel);
965 return d->m_populatedCols.contains(id);
966}
967
968bool EntityTreeModel::isFullyPopulated() const
969{
970 Q_D(const EntityTreeModel);
971 return d->m_collectionTreeFetched && d->m_pendingCollectionRetrieveJobs.isEmpty();
972}
973
974bool EntityTreeModel::entityMatch(const Item &item, const QVariant &value, Qt::MatchFlags flags) const
975{
976 Q_UNUSED(item);
977 Q_UNUSED(value);
978 Q_UNUSED(flags);
979 return false;
980}
981
982bool EntityTreeModel::entityMatch(const Collection &collection, const QVariant &value, Qt::MatchFlags flags) const
983{
984 Q_UNUSED(collection);
985 Q_UNUSED(value);
986 Q_UNUSED(flags);
987 return false;
988}
989
990QModelIndexList EntityTreeModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const
991{
992 Q_D(const EntityTreeModel);
993
994 if (role == CollectionIdRole || role == CollectionRole) {
995 Collection::Id id;
996 if (role == CollectionRole) {
997 const Collection collection = value.value<Collection>();
998 id = collection.id();
999 } else {
1000 id = value.toLongLong();
1001 }
1002
1003 QModelIndexList list;
1004
1005 const Collection collection = d->m_collections.value(id);
1006
1007 if (!collection.isValid()) {
1008 return list;
1009 }
1010
1011 const QModelIndex collectionIndex = d->indexForCollection(collection);
1012 Q_ASSERT(collectionIndex.isValid());
1013 list << collectionIndex;
1014
1015 return list;
1016 }
1017
1018 if (role == ItemIdRole || role == ItemRole) {
1019 Item::Id id;
1020 if (role == ItemRole) {
1021 const Item item = value.value<Item>();
1022 id = item.id();
1023 } else {
1024 id = value.toLongLong();
1025 }
1026 QModelIndexList list;
1027
1028 const Item item = d->m_items.value(id);
1029 if (!item.isValid()) {
1030 return list;
1031 }
1032
1033 return d->indexesForItem(item);
1034 }
1035
1036 if (role == EntityUrlRole) {
1037 const KUrl url(value.toString());
1038 const Item item = Item::fromUrl(url);
1039
1040 if (item.isValid()) {
1041 return d->indexesForItem(d->m_items.value(item.id()));
1042 }
1043
1044 const Collection collection = Collection::fromUrl(url);
1045 QModelIndexList list;
1046 if (collection.isValid()) {
1047 list << d->indexForCollection(collection);
1048 }
1049
1050 return list;
1051 }
1052
1053 if (role != AmazingCompletionRole) {
1054 return QAbstractItemModel::match(start, role, value, hits, flags);
1055 }
1056
1057 // Try to match names, and email addresses.
1058 QModelIndexList list;
1059
1060 if (role < 0 ||
1061 !start.isValid() ||
1062 !value.isValid()) {
1063 return list;
1064 }
1065
1066 const int column = 0;
1067 int row = start.row();
1068 const QModelIndex parentIndex = start.parent();
1069 const int parentRowCount = rowCount(parentIndex);
1070
1071 while (row < parentRowCount &&
1072 (hits == -1 || list.size() < hits)) {
1073 const QModelIndex idx = index(row, column, parentIndex);
1074 const Item item = idx.data(ItemRole).value<Item>();
1075
1076 if (!item.isValid()) {
1077 const Collection collection = idx.data(CollectionRole).value<Collection>();
1078 if (!collection.isValid()) {
1079 continue;
1080 }
1081
1082 if (entityMatch(collection, value, flags)) {
1083 list << idx;
1084 }
1085
1086 } else {
1087 if (entityMatch(item, value, flags)) {
1088 list << idx;
1089 }
1090 }
1091
1092 ++row;
1093 }
1094
1095 return list;
1096}
1097
1098bool EntityTreeModel::insertRows(int, int, const QModelIndex &)
1099{
1100 return false;
1101}
1102
1103bool EntityTreeModel::insertColumns(int, int, const QModelIndex &)
1104{
1105 return false;
1106}
1107
1108bool EntityTreeModel::removeRows(int, int, const QModelIndex &)
1109{
1110 return false;
1111}
1112
1113bool EntityTreeModel::removeColumns(int, int, const QModelIndex &)
1114{
1115 return false;
1116}
1117
1118void EntityTreeModel::setItemPopulationStrategy(ItemPopulationStrategy strategy)
1119{
1120 Q_D(EntityTreeModel);
1121 d->beginResetModel();
1122 d->m_itemPopulation = strategy;
1123
1124 if (strategy == NoItemPopulation) {
1125 disconnect(d->m_monitor, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)),
1126 this, SLOT(monitoredItemAdded(Akonadi::Item,Akonadi::Collection)));
1127 disconnect(d->m_monitor, SIGNAL(itemChanged(Akonadi::Item,QSet<QByteArray>)),
1128 this, SLOT(monitoredItemChanged(Akonadi::Item,QSet<QByteArray>)));
1129 disconnect(d->m_monitor, SIGNAL(itemRemoved(Akonadi::Item)),
1130 this, SLOT(monitoredItemRemoved(Akonadi::Item)));
1131 disconnect(d->m_monitor, SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)),
1132 this, SLOT(monitoredItemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)));
1133
1134 disconnect(d->m_monitor, SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection)),
1135 this, SLOT(monitoredItemLinked(Akonadi::Item,Akonadi::Collection)));
1136 disconnect(d->m_monitor, SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection)),
1137 this, SLOT(monitoredItemUnlinked(Akonadi::Item,Akonadi::Collection)));
1138 }
1139
1140 d->m_monitor->d_ptr->useRefCounting = (strategy == LazyPopulation);
1141
1142 d->endResetModel();
1143}
1144
1145EntityTreeModel::ItemPopulationStrategy EntityTreeModel::itemPopulationStrategy() const
1146{
1147 Q_D(const EntityTreeModel);
1148 return d->m_itemPopulation;
1149}
1150
1151void EntityTreeModel::setIncludeRootCollection(bool include)
1152{
1153 Q_D(EntityTreeModel);
1154 d->beginResetModel();
1155 d->m_showRootCollection = include;
1156 d->endResetModel();
1157}
1158
1159bool EntityTreeModel::includeRootCollection() const
1160{
1161 Q_D(const EntityTreeModel);
1162 return d->m_showRootCollection;
1163}
1164
1165void EntityTreeModel::setRootCollectionDisplayName(const QString &displayName)
1166{
1167 Q_D(EntityTreeModel);
1168 d->m_rootCollectionDisplayName = displayName;
1169
1170 // TODO: Emit datachanged if it is being shown.
1171}
1172
1173QString EntityTreeModel::rootCollectionDisplayName() const
1174{
1175 Q_D(const EntityTreeModel);
1176 return d->m_rootCollectionDisplayName;
1177}
1178
1179void EntityTreeModel::setCollectionFetchStrategy(CollectionFetchStrategy strategy)
1180{
1181 Q_D(EntityTreeModel);
1182 d->beginResetModel();
1183 d->m_collectionFetchStrategy = strategy;
1184
1185 if (strategy == FetchNoCollections ||
1186 strategy == InvisibleCollectionFetch) {
1187 disconnect(d->m_monitor, SIGNAL(collectionChanged(Akonadi::Collection)),
1188 this, SLOT(monitoredCollectionChanged(Akonadi::Collection)));
1189 disconnect(d->m_monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)),
1190 this, SLOT(monitoredCollectionAdded(Akonadi::Collection,Akonadi::Collection)));
1191 disconnect(d->m_monitor, SIGNAL(collectionRemoved(Akonadi::Collection)),
1192 this, SLOT(monitoredCollectionRemoved(Akonadi::Collection)));
1193 disconnect(d->m_monitor,
1194 SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection)),
1195 this, SLOT(monitoredCollectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection)));
1196 d->m_monitor->fetchCollection(false);
1197 } else {
1198 d->m_monitor->fetchCollection(true);
1199 }
1200
1201 d->endResetModel();
1202}
1203
1204EntityTreeModel::CollectionFetchStrategy EntityTreeModel::collectionFetchStrategy() const
1205{
1206 Q_D(const EntityTreeModel);
1207 return d->m_collectionFetchStrategy;
1208}
1209
1210static QPair<QList<const QAbstractProxyModel *>, const EntityTreeModel *> proxiesAndModel(const QAbstractItemModel *model)
1211{
1212 QList<const QAbstractProxyModel *> proxyChain;
1213 const QAbstractProxyModel *proxy = qobject_cast<const QAbstractProxyModel *>(model);
1214 const QAbstractItemModel *_model = model;
1215 while (proxy) {
1216 proxyChain.prepend(proxy);
1217 _model = proxy->sourceModel();
1218 proxy = qobject_cast<const QAbstractProxyModel *>(_model);
1219 }
1220
1221 const EntityTreeModel *etm = qobject_cast<const EntityTreeModel *>(_model);
1222 return qMakePair(proxyChain, etm);
1223}
1224
1225static QModelIndex proxiedIndex(const QModelIndex &idx, QList<const QAbstractProxyModel *> proxyChain)
1226{
1227 QListIterator<const QAbstractProxyModel *> it(proxyChain);
1228 QModelIndex _idx = idx;
1229 while (it.hasNext()) {
1230 _idx = it.next()->mapFromSource(_idx);
1231 }
1232 return _idx;
1233}
1234
1235QModelIndex EntityTreeModel::modelIndexForCollection(const QAbstractItemModel *model, const Collection &collection)
1236{
1237 QPair<QList<const QAbstractProxyModel *>, const EntityTreeModel *> pair = proxiesAndModel(model);
1238
1239 Q_ASSERT(pair.second);
1240 QModelIndex idx = pair.second->d_ptr->indexForCollection(collection);
1241 return proxiedIndex(idx, pair.first);
1242}
1243
1244QModelIndexList EntityTreeModel::modelIndexesForItem(const QAbstractItemModel *model, const Item &item)
1245{
1246 QPair<QList<const QAbstractProxyModel *>, const EntityTreeModel *> pair = proxiesAndModel(model);
1247
1248 if (!pair.second) {
1249 kWarning() << "Couldn't find an EntityTreeModel";
1250 return QModelIndexList();
1251 }
1252
1253 QModelIndexList list = pair.second->d_ptr->indexesForItem(item);
1254 QModelIndexList proxyList;
1255 foreach (const QModelIndex &idx, list) {
1256 const QModelIndex pIdx = proxiedIndex(idx, pair.first);
1257 if (pIdx.isValid()) {
1258 proxyList << pIdx;
1259 }
1260 }
1261 return proxyList;
1262}
1263
1264#include "moc_entitytreemodel.cpp"
1265