1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2009-03-23
7 * Description : Qt Model for Albums
8 *
9 * Copyright (C) 2008-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
10 * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
11 * Copyright (C) 2012-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
12 *
13 * This program is free software; you can redistribute it
14 * and/or modify it under the terms of the GNU General
15 * Public License as published by the Free Software Foundation;
16 * either version 2, or (at your option)
17 * any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * ============================================================ */
25
26 #include "abstractalbummodel.h"
27
28 // Qt includes
29
30 #include <QPainter>
31 #include <QIcon>
32
33 // KDE includes
34
35 #include <klocalizedstring.h>
36
37 // Local includes
38
39 #include "digikam_debug.h"
40 #include "albummanager.h"
41 #include "albummodeldragdrophandler.h"
42 #include "albumthumbnailloader.h"
43
44 namespace Digikam
45 {
46
47 class Q_DECL_HIDDEN AbstractAlbumModel::Private
48 {
49 public:
50
Private()51 explicit Private()
52 : rootAlbum (nullptr),
53 addingAlbum (nullptr),
54 type (Album::PHYSICAL),
55 dragDropHandler (nullptr),
56 rootBehavior (AbstractAlbumModel::IncludeRootAlbum),
57 removingAlbum (0),
58 itemDrag (true),
59 itemDrop (true),
60 isFaceTagModel (false)
61 {
62 }
63
64 Album* rootAlbum;
65 Album* addingAlbum;
66 Album::Type type;
67 AlbumModelDragDropHandler* dragDropHandler;
68 AbstractAlbumModel::RootAlbumBehavior rootBehavior;
69
70 quintptr removingAlbum;
71
72 bool itemDrag;
73 bool itemDrop;
74 bool isFaceTagModel;
75 };
76
AbstractAlbumModel(Album::Type albumType,Album * const rootAlbum,RootAlbumBehavior rootBehavior,QObject * const parent)77 AbstractAlbumModel::AbstractAlbumModel(Album::Type albumType,
78 Album* const rootAlbum,
79 RootAlbumBehavior rootBehavior,
80 QObject* const parent)
81 : QAbstractItemModel(parent),
82 d (new Private)
83 {
84 d->type = albumType;
85 d->rootAlbum = rootAlbum;
86 d->rootBehavior = rootBehavior;
87
88 // --- NOTE: use dynamic binding as all slots above are virtual methods which can be re-implemented in derived classes.
89
90 connect(AlbumManager::instance(), &AlbumManager::signalAlbumAboutToBeAdded,
91 this, &AbstractAlbumModel::slotAlbumAboutToBeAdded);
92
93 connect(AlbumManager::instance(), &AlbumManager::signalAlbumAdded,
94 this, &AbstractAlbumModel::slotAlbumAdded);
95
96 connect(AlbumManager::instance(), &AlbumManager::signalAlbumAboutToBeDeleted,
97 this, &AbstractAlbumModel::slotAlbumAboutToBeDeleted);
98
99 connect(AlbumManager::instance(), &AlbumManager::signalAlbumHasBeenDeleted,
100 this, &AbstractAlbumModel::slotAlbumHasBeenDeleted);
101
102 connect(AlbumManager::instance(), &AlbumManager::signalAlbumsCleared,
103 this, &AbstractAlbumModel::slotAlbumsCleared);
104
105 connect(AlbumManager::instance(), &AlbumManager::signalAlbumIconChanged,
106 this, &AbstractAlbumModel::slotAlbumIconChanged);
107
108 connect(AlbumManager::instance(), &AlbumManager::signalAlbumRenamed,
109 this, &AbstractAlbumModel::slotAlbumRenamed);
110
111 // ---
112 }
113
~AbstractAlbumModel()114 AbstractAlbumModel::~AbstractAlbumModel()
115 {
116 delete d;
117 }
118
data(const QModelIndex & index,int role) const119 QVariant AbstractAlbumModel::data(const QModelIndex& index, int role) const
120 {
121 if (!index.isValid())
122 {
123 return QVariant();
124 }
125
126 Album* const a = static_cast<Album*>(index.internalPointer());
127
128 return albumData(a, role);
129 }
130
albumData(Album * a,int role) const131 QVariant AbstractAlbumModel::albumData(Album* a, int role) const
132 {
133 switch (role)
134 {
135 case Qt::DisplayRole:
136 return a->title();
137
138 case Qt::ToolTipRole:
139 return a->title();
140
141 case Qt::DecorationRole:
142 // reimplement in subclasses
143 return decorationRoleData(a);
144
145 case Qt::FontRole:
146 return fontRoleData(a);
147
148 case AlbumTitleRole:
149 return a->title();
150
151 case AlbumTypeRole:
152 return a->type();
153
154 case AlbumPointerRole:
155 return QVariant::fromValue(a);
156
157 case AlbumIdRole:
158 return a->id();
159
160 case AlbumGlobalIdRole:
161 return a->globalID();
162
163 case AlbumSortRole:
164 // reimplement in subclass
165 return sortRoleData(a);
166
167 default:
168 return QVariant();
169 }
170 }
171
headerData(int section,Qt::Orientation orientation,int role) const172 QVariant AbstractAlbumModel::headerData(int section, Qt::Orientation orientation, int role) const
173 {
174 Q_UNUSED(orientation)
175
176 if ((section == 0) && (role == Qt::DisplayRole))
177 {
178 return columnHeader();
179 }
180
181 return QVariant();
182 }
183
rowCount(const QModelIndex & parent) const184 int AbstractAlbumModel::rowCount(const QModelIndex& parent) const
185 {
186 if (parent.isValid())
187 {
188 Album* const a = static_cast<Album*>(parent.internalPointer());
189 return a->childCount();
190 }
191 else
192 {
193 if (!d->rootAlbum)
194 {
195 return 0;
196 }
197
198 if (d->rootBehavior == IncludeRootAlbum)
199 {
200 return 1;
201 }
202 else
203 {
204 return d->rootAlbum->childCount();
205 }
206 }
207 }
208
columnCount(const QModelIndex &) const209 int AbstractAlbumModel::columnCount(const QModelIndex& /*parent*/) const
210 {
211 return 1;
212 }
213
flags(const QModelIndex & index) const214 Qt::ItemFlags AbstractAlbumModel::flags(const QModelIndex& index) const
215 {
216 if (!index.isValid())
217 {
218 return Qt::NoItemFlags;
219 }
220
221 Album* const a = static_cast<Album*>(index.internalPointer());
222
223 return itemFlags(a);
224 }
225
hasChildren(const QModelIndex & parent) const226 bool AbstractAlbumModel::hasChildren(const QModelIndex& parent) const
227 {
228 if (parent.isValid())
229 {
230 Album* const a = static_cast<Album*>(parent.internalPointer());
231
232 return a->firstChild();
233 }
234 else
235 {
236 if (!d->rootAlbum)
237 {
238 return false;
239 }
240
241 if (d->rootBehavior == IncludeRootAlbum)
242 {
243 return 1;
244 }
245 else
246 {
247 return d->rootAlbum->firstChild();
248 }
249 }
250 }
251
index(int row,int column,const QModelIndex & parent) const252 QModelIndex AbstractAlbumModel::index(int row, int column, const QModelIndex& parent) const
253 {
254 if ((column != 0) || (row < 0))
255 {
256 return QModelIndex();
257 }
258
259 if (parent.isValid())
260 {
261 Album* const parentAlbum = static_cast<Album*>(parent.internalPointer());
262 Album* const a = parentAlbum->childAtRow(row);
263
264 if (a)
265 {
266 return createIndex(row, column, a);
267 }
268 }
269 else
270 {
271 if (!d->rootAlbum)
272 {
273 return QModelIndex();
274 }
275
276 if (d->rootBehavior == IncludeRootAlbum)
277 {
278 if (row == 0)
279 {
280 return createIndex(0, 0, d->rootAlbum);
281 }
282 }
283 else
284 {
285 Album* const a = d->rootAlbum->childAtRow(row);
286
287 if (a)
288 {
289 return createIndex(row, column, a);
290 }
291 }
292 }
293
294 return QModelIndex();
295 }
296
parent(const QModelIndex & index) const297 QModelIndex AbstractAlbumModel::parent(const QModelIndex& index) const
298 {
299 if (index.isValid())
300 {
301 Album* const a = static_cast<Album*>(index.internalPointer());
302 return indexForAlbum(a->parent());
303 }
304
305 return QModelIndex();
306 }
307
supportedDropActions() const308 Qt::DropActions AbstractAlbumModel::supportedDropActions() const
309 {
310 return Qt::CopyAction|Qt::MoveAction;
311 }
312
mimeTypes() const313 QStringList AbstractAlbumModel::mimeTypes() const
314 {
315 if (d->dragDropHandler)
316 {
317 return d->dragDropHandler->mimeTypes();
318 }
319
320 return QStringList();
321 }
322
dropMimeData(const QMimeData *,Qt::DropAction,int,int,const QModelIndex &)323 bool AbstractAlbumModel::dropMimeData(const QMimeData*, Qt::DropAction, int, int, const QModelIndex&)
324 {
325 // we require custom solutions
326
327 return false;
328 }
329
mimeData(const QModelIndexList & indexes) const330 QMimeData* AbstractAlbumModel::mimeData(const QModelIndexList& indexes) const
331 {
332 if (!d->dragDropHandler)
333 {
334 return nullptr;
335 }
336
337 QList<Album*> albums;
338
339 foreach (const QModelIndex& index, indexes)
340 {
341 Album* const a = albumForIndex(index);
342
343 if (a)
344 {
345 albums << a;
346 }
347 }
348
349 return d->dragDropHandler->createMimeData(albums);
350 }
351
setEnableDrag(bool enable)352 void AbstractAlbumModel::setEnableDrag(bool enable)
353 {
354 d->itemDrag = enable;
355 }
356
setEnableDrop(bool enable)357 void AbstractAlbumModel::setEnableDrop(bool enable)
358 {
359 d->itemDrop = enable;
360 }
361
setDragDropHandler(AlbumModelDragDropHandler * handler)362 void AbstractAlbumModel::setDragDropHandler(AlbumModelDragDropHandler* handler)
363 {
364 d->dragDropHandler = handler;
365 }
366
dragDropHandler() const367 AlbumModelDragDropHandler* AbstractAlbumModel::dragDropHandler() const
368 {
369 return d->dragDropHandler;
370 }
371
setFaceTagModel(bool enable)372 void AbstractAlbumModel::setFaceTagModel(bool enable)
373 {
374 d->isFaceTagModel = enable;
375 }
376
indexForAlbum(Album * a) const377 QModelIndex AbstractAlbumModel::indexForAlbum(Album* a) const
378 {
379 if (!a)
380 {
381 return QModelIndex();
382 }
383
384 if (!filterAlbum(a))
385 {
386 return QModelIndex();
387 }
388
389 // a is root album? Decide on root behavior
390
391 if (a == d->rootAlbum)
392 {
393 if (d->rootBehavior == IncludeRootAlbum)
394 {
395 // create top-level indexes
396
397 return createIndex(0, 0, a);
398 }
399 else
400 {
401 // with this behavior, root album has no valid index
402
403 return QModelIndex();
404 }
405 }
406
407 // Normal album. Get its row.
408
409 return createIndex(a->rowFromAlbum(), 0, a);
410 }
411
albumForIndex(const QModelIndex & index) const412 Album* AbstractAlbumModel::albumForIndex(const QModelIndex& index) const
413 {
414 return (static_cast<Album*>(index.internalPointer()));
415 }
416
retrieveAlbum(const QModelIndex & index)417 Album* AbstractAlbumModel::retrieveAlbum(const QModelIndex& index)
418 {
419 return (index.data(AbstractAlbumModel::AlbumPointerRole).value<Album*>());
420 }
421
rootAlbum() const422 Album* AbstractAlbumModel::rootAlbum() const
423 {
424 return d->rootAlbum;
425 }
426
rootAlbumIndex() const427 QModelIndex AbstractAlbumModel::rootAlbumIndex() const
428 {
429 return indexForAlbum(d->rootAlbum);
430 }
431
rootAlbumBehavior() const432 AbstractAlbumModel::RootAlbumBehavior AbstractAlbumModel::rootAlbumBehavior() const
433 {
434 return d->rootBehavior;
435 }
436
albumType() const437 Album::Type AbstractAlbumModel::albumType() const
438 {
439 return d->type;
440 }
441
isFaceTagModel() const442 bool AbstractAlbumModel::isFaceTagModel() const
443 {
444 return d->isFaceTagModel;
445 }
446
decorationRoleData(Album *) const447 QVariant AbstractAlbumModel::decorationRoleData(Album*) const
448 {
449 return QVariant();
450 }
451
fontRoleData(Album *) const452 QVariant AbstractAlbumModel::fontRoleData(Album*) const
453 {
454 return QVariant();
455 }
456
sortRoleData(Album * a) const457 QVariant AbstractAlbumModel::sortRoleData(Album* a) const
458 {
459 return a->title();
460 }
461
columnHeader() const462 QString AbstractAlbumModel::columnHeader() const
463 {
464 return i18n("Album");
465 }
466
itemFlags(Album *) const467 Qt::ItemFlags AbstractAlbumModel::itemFlags(Album*) const
468 {
469 Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
470
471 if (d->itemDrag)
472 {
473 f |= Qt::ItemIsDragEnabled;
474 }
475
476 if (d->itemDrop)
477 {
478 f |= Qt::ItemIsDropEnabled;
479 }
480
481 return f;
482 }
483
filterAlbum(Album * album) const484 bool AbstractAlbumModel::filterAlbum(Album* album) const
485 {
486 return (album && album->type() == d->type);
487 }
488
slotAlbumAboutToBeAdded(Album * album,Album * parent,Album * prev)489 void AbstractAlbumModel::slotAlbumAboutToBeAdded(Album* album, Album* parent, Album* prev)
490 {
491 if (!filterAlbum(album))
492 {
493 return;
494 }
495
496 if (album->isRoot() && d->rootBehavior == IgnoreRootAlbum)
497 {
498 d->rootAlbum = album;
499 return;
500 }
501
502 // start inserting operation
503
504 int row = prev ? prev->rowFromAlbum()+1 : 0;
505 QModelIndex parentIndex = indexForAlbum(parent);
506 beginInsertRows(parentIndex, row, row);
507
508 // The root album will become available in time
509 // when the model is instantiated before albums are initialized.
510 // Set d->rootAlbum only after
511
512 if (album->isRoot() && !d->rootAlbum)
513 {
514 d->rootAlbum = album;
515 }
516
517 // store album for slotAlbumAdded
518
519 d->addingAlbum = album;
520 }
521
slotAlbumAdded(Album * album)522 void AbstractAlbumModel::slotAlbumAdded(Album* album)
523 {
524 if (d->addingAlbum == album)
525 {
526 bool isRoot = (d->addingAlbum == d->rootAlbum);
527 d->addingAlbum = nullptr;
528 endInsertRows();
529
530 if (isRoot)
531 {
532 emit rootAlbumAvailable();
533 }
534 }
535 }
536
slotAlbumAboutToBeDeleted(Album * album)537 void AbstractAlbumModel::slotAlbumAboutToBeDeleted(Album* album)
538 {
539 if (!filterAlbum(album))
540 {
541 return;
542 }
543
544 if (album->isRoot() && (d->rootBehavior == IgnoreRootAlbum))
545 {
546 albumCleared(album);
547 d->rootAlbum = nullptr;
548 return;
549 }
550
551 // begin removing operation
552
553 int row = album->rowFromAlbum();
554 QModelIndex parent = indexForAlbum(album->parent());
555 beginRemoveRows(parent, row, row);
556 albumCleared(album);
557
558 // store album for slotAlbumHasBeenDeleted
559
560 d->removingAlbum = reinterpret_cast<quintptr>(album);
561 }
562
slotAlbumHasBeenDeleted(quintptr p)563 void AbstractAlbumModel::slotAlbumHasBeenDeleted(quintptr p)
564 {
565 if (d->removingAlbum == p)
566 {
567 d->removingAlbum = 0;
568 endRemoveRows();
569 }
570 }
571
slotAlbumsCleared()572 void AbstractAlbumModel::slotAlbumsCleared()
573 {
574 d->rootAlbum = nullptr;
575 beginResetModel();
576 allAlbumsCleared();
577 endResetModel();
578 }
579
slotAlbumIconChanged(Album * album)580 void AbstractAlbumModel::slotAlbumIconChanged(Album* album)
581 {
582 if (!filterAlbum(album))
583 {
584 return;
585 }
586
587 QModelIndex index = indexForAlbum(album);
588 emit dataChanged(index, index);
589 }
590
slotAlbumRenamed(Album * album)591 void AbstractAlbumModel::slotAlbumRenamed(Album* album)
592 {
593 if (!filterAlbum(album))
594 {
595 return;
596 }
597
598 QModelIndex index = indexForAlbum(album);
599 emit dataChanged(index, index);
600 }
601
602 // ------------------------------------------------------------------
603
AbstractSpecificAlbumModel(Album::Type albumType,Album * const rootAlbum,RootAlbumBehavior rootBehavior,QObject * const parent)604 AbstractSpecificAlbumModel::AbstractSpecificAlbumModel(Album::Type albumType,
605 Album* const rootAlbum,
606 RootAlbumBehavior rootBehavior,
607 QObject* const parent)
608 : AbstractAlbumModel(albumType, rootAlbum, rootBehavior, parent)
609 {
610 }
611
setupThumbnailLoading()612 void AbstractSpecificAlbumModel::setupThumbnailLoading()
613 {
614 AlbumThumbnailLoader* const loader = AlbumThumbnailLoader::instance();
615
616 connect(loader, SIGNAL(signalThumbnail(Album*,QPixmap)),
617 this, SLOT(slotGotThumbnailFromIcon(Album*,QPixmap)));
618
619 connect(loader, SIGNAL(signalFailed(Album*)),
620 this, SLOT(slotThumbnailLost(Album*)));
621
622 connect(loader, SIGNAL(signalReloadThumbnails()),
623 this, SLOT(slotReloadThumbnails()));
624 }
625
columnHeader() const626 QString AbstractSpecificAlbumModel::columnHeader() const
627 {
628 return m_columnHeader;
629 }
630
setColumnHeader(const QString & header)631 void AbstractSpecificAlbumModel::setColumnHeader(const QString& header)
632 {
633 m_columnHeader = header;
634 emit headerDataChanged(Qt::Horizontal, 0, 0);
635 }
636
slotGotThumbnailFromIcon(Album * album,const QPixmap &)637 void AbstractSpecificAlbumModel::slotGotThumbnailFromIcon(Album* album, const QPixmap&)
638 {
639 // see decorationRole() method of subclasses
640
641 if (!filterAlbum(album))
642 {
643 return;
644 }
645
646 QModelIndex index = indexForAlbum(album);
647 emit dataChanged(index, index);
648 }
649
slotThumbnailLost(Album *)650 void AbstractSpecificAlbumModel::slotThumbnailLost(Album*)
651 {
652 // ignore, use default thumbnail
653 }
654
slotReloadThumbnails()655 void AbstractSpecificAlbumModel::slotReloadThumbnails()
656 {
657 // emit dataChanged() for all albums
658
659 emitDataChangedForChildren(rootAlbum());
660 }
661
emitDataChangedForChildren(Album * album)662 void AbstractSpecificAlbumModel::emitDataChangedForChildren(Album* album)
663 {
664 if (!album)
665 {
666 return;
667 }
668
669 for (Album* child = album->firstChild() ; child ; child = child->next())
670 {
671 if (filterAlbum(child))
672 {
673 // recurse to children of children
674
675 emitDataChangedForChildren(child);
676
677 // emit signal for child
678
679 QModelIndex index = indexForAlbum(child);
680 emit dataChanged(index, index);
681 }
682 }
683 }
684
685 // ------------------------------------------------------------------
686
687 class Q_DECL_HIDDEN AbstractCountingAlbumModel::Private
688 {
689 public:
690
Private()691 explicit Private()
692 : showCount(false)
693 {
694 }
695
696 bool showCount;
697 QMap<int, int> countMap;
698 QHash<int, int> countHashReady;
699 QSet<int> includeChildrenAlbums;
700 };
701
AbstractCountingAlbumModel(Album::Type albumType,Album * const rootAlbum,RootAlbumBehavior rootBehavior,QObject * const parent)702 AbstractCountingAlbumModel::AbstractCountingAlbumModel(Album::Type albumType,
703 Album* const rootAlbum,
704 RootAlbumBehavior rootBehavior,
705 QObject* const parent)
706 : AbstractSpecificAlbumModel(albumType, rootAlbum, rootBehavior, parent),
707 d(new Private)
708 {
709 }
710
setup()711 void AbstractCountingAlbumModel::setup()
712 {
713 connect(AlbumManager::instance(), SIGNAL(signalAlbumMoved(Album*)),
714 this, SLOT(slotAlbumMoved(Album*)));
715 }
716
~AbstractCountingAlbumModel()717 AbstractCountingAlbumModel::~AbstractCountingAlbumModel()
718 {
719 delete d;
720 }
721
setShowCount(bool show)722 void AbstractCountingAlbumModel::setShowCount(bool show)
723 {
724 if (d->showCount != show)
725 {
726 d->showCount = show;
727 emitDataChangedForChildren(rootAlbum());
728 }
729 }
730
showCount() const731 bool AbstractCountingAlbumModel::showCount() const
732 {
733 return d->showCount;
734 }
735
excludeChildrenCount(const QModelIndex & index)736 void AbstractCountingAlbumModel::excludeChildrenCount(const QModelIndex& index)
737 {
738 Album* const album = albumForIndex(index);
739
740 if (!album)
741 {
742 return;
743 }
744
745 d->includeChildrenAlbums.remove(album->id());
746 updateCount(album);
747 }
748
includeChildrenCount(const QModelIndex & index)749 void AbstractCountingAlbumModel::includeChildrenCount(const QModelIndex& index)
750 {
751 Album* const album = albumForIndex(index);
752
753 if (!album)
754 {
755 return;
756 }
757
758 d->includeChildrenAlbums << album->id();
759 updateCount(album);
760 }
761
setCountMap(const QMap<int,int> & idCountMap)762 void AbstractCountingAlbumModel::setCountMap(const QMap<int, int>& idCountMap)
763 {
764 d->countMap = idCountMap;
765 QMap<int, int>::const_iterator it = d->countMap.constBegin();
766
767 for ( ; it != d->countMap.constEnd() ; ++it)
768 {
769 updateCount(albumForId(it.key()));
770 }
771 }
772
updateCount(Album * album)773 void AbstractCountingAlbumModel::updateCount(Album* album)
774 {
775 if (!album)
776 {
777 return;
778 }
779
780 // if the model does not contain the album, do nothing.
781
782 QModelIndex index = indexForAlbum(album);
783
784 if (!index.isValid())
785 {
786 return;
787 }
788
789 QHash<int, int>::iterator includeIt = d->countHashReady.find(album->id());
790 bool changed = false;
791
792 // get count for album without children
793
794 int count = d->countMap.value(album->id());
795
796 // if wanted, add up children's counts
797
798 if (d->includeChildrenAlbums.contains(album->id()))
799 {
800 AlbumIterator it(album);
801
802 while (it.current())
803 {
804 count += d->countMap.value((*it)->id());
805 ++it;
806 }
807 }
808
809 // insert or update
810
811 if (includeIt == d->countHashReady.end())
812 {
813 changed = true;
814 d->countHashReady[album->id()] = count;
815 }
816 else
817 {
818 changed = (includeIt.value() != count);
819 includeIt.value() = count;
820 }
821
822 // notify views
823
824 if (changed)
825 {
826 emit dataChanged(index, index);
827 }
828 }
829
setCount(Album * album,int count)830 void AbstractCountingAlbumModel::setCount(Album* album, int count)
831 {
832 if (!album)
833 {
834 return;
835 }
836
837 // if the model does not contain the album, do nothing.
838
839 QModelIndex index = indexForAlbum(album);
840
841 if (!index.isValid())
842 {
843 return;
844 }
845
846 QHash<int, int>::iterator includeIt = d->countHashReady.find(album->id());
847 bool changed = false;
848
849 // insert or update
850
851 if (includeIt == d->countHashReady.end())
852 {
853 changed = true;
854 d->countHashReady[album->id()] = count;
855 }
856 else
857 {
858 changed = (includeIt.value() != count);
859 includeIt.value() = count;
860 }
861
862 // notify views
863
864 if (changed)
865 {
866 emit dataChanged(index, index);
867 }
868 }
869
albumData(Album * album,int role) const870 QVariant AbstractCountingAlbumModel::albumData(Album* album, int role) const
871 {
872 if ((role == Qt::DisplayRole) && d->showCount && !album->isRoot())
873 {
874 QHash<int, int>::const_iterator it = d->countHashReady.constFind(album->id());
875
876 if (it != d->countHashReady.constEnd())
877 {
878 return QString::fromUtf8("%1 (%2)").arg(albumName(album)).arg(it.value());
879 }
880 }
881
882 return AbstractSpecificAlbumModel::albumData(album, role);
883 }
884
albumCount(Album * album) const885 int AbstractCountingAlbumModel::albumCount(Album* album) const
886 {
887 QHash<int, int>::const_iterator it = d->countHashReady.constFind(album->id());
888
889 if (it != d->countHashReady.constEnd())
890 {
891 return it.value();
892 }
893
894 return -1;
895 }
896
albumName(Album * album) const897 QString AbstractCountingAlbumModel::albumName(Album* album) const
898 {
899 return album->title();
900 }
901
albumCleared(Album * album)902 void AbstractCountingAlbumModel::albumCleared(Album* album)
903 {
904 if (!AlbumManager::instance()->isMovingAlbum(album))
905 {
906 d->countMap.remove(album->id());
907 d->countHashReady.remove(album->id());
908 d->includeChildrenAlbums.remove(album->id());
909 }
910 }
911
allAlbumsCleared()912 void AbstractCountingAlbumModel::allAlbumsCleared()
913 {
914 d->countMap.clear();
915 d->countHashReady.clear();
916 d->includeChildrenAlbums.clear();
917 }
918
slotAlbumMoved(Album *)919 void AbstractCountingAlbumModel::slotAlbumMoved(Album*)
920 {
921 // need to update counts of all parents
922
923 setCountMap(d->countMap);
924 }
925
926 // ------------------------------------------------------------------
927
928 class Q_DECL_HIDDEN AbstractCheckableAlbumModel::Private
929 {
930 public:
931
Private()932 explicit Private()
933 : extraFlags(nullptr),
934 rootIsCheckable(true),
935 addExcludeTristate(false),
936 staticVectorContainingCheckStateRole(1, Qt::CheckStateRole)
937
938 {
939 }
940
941 Qt::ItemFlags extraFlags;
942 bool rootIsCheckable;
943 bool addExcludeTristate;
944 QHash<Album*, Qt::CheckState> checkedAlbums;
945
946 QVector<int> staticVectorContainingCheckStateRole;
947 };
948
AbstractCheckableAlbumModel(Album::Type albumType,Album * const rootAlbum,RootAlbumBehavior rootBehavior,QObject * const parent)949 AbstractCheckableAlbumModel::AbstractCheckableAlbumModel(Album::Type albumType,
950 Album* const rootAlbum,
951 RootAlbumBehavior rootBehavior,
952 QObject* const parent)
953 : AbstractCountingAlbumModel(albumType, rootAlbum, rootBehavior, parent),
954 d(new Private)
955 {
956 setup();
957 }
958
~AbstractCheckableAlbumModel()959 AbstractCheckableAlbumModel::~AbstractCheckableAlbumModel()
960 {
961 delete d;
962 }
963
setCheckable(bool isCheckable)964 void AbstractCheckableAlbumModel::setCheckable(bool isCheckable)
965 {
966 if (isCheckable)
967 {
968 d->extraFlags |= Qt::ItemIsUserCheckable;
969 }
970 else
971 {
972 d->extraFlags &= ~Qt::ItemIsUserCheckable;
973 resetCheckedAlbums();
974 }
975 }
976
isCheckable() const977 bool AbstractCheckableAlbumModel::isCheckable() const
978 {
979 return d->extraFlags & Qt::ItemIsUserCheckable;
980 }
981
setRootCheckable(bool isCheckable)982 void AbstractCheckableAlbumModel::setRootCheckable(bool isCheckable)
983 {
984 d->rootIsCheckable = isCheckable;
985 Album* const root = rootAlbum();
986
987 if (!d->rootIsCheckable && root)
988 {
989 setChecked(root, false);
990 }
991 }
992
rootIsCheckable() const993 bool AbstractCheckableAlbumModel::rootIsCheckable() const
994 {
995 return (d->rootIsCheckable && isCheckable());
996 }
997
setTristate(bool isTristate)998 void AbstractCheckableAlbumModel::setTristate(bool isTristate)
999 {
1000 if (isTristate)
1001 {
1002 d->extraFlags |=
1003
1004 #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
1005
1006 Qt::ItemIsAutoTristate;
1007
1008 #else
1009
1010 Qt::ItemIsTristate;
1011
1012 #endif
1013
1014 }
1015 else
1016 {
1017 d->extraFlags &= ~
1018
1019 #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
1020
1021 Qt::ItemIsAutoTristate;
1022
1023 #else
1024
1025 Qt::ItemIsTristate;
1026
1027 #endif
1028
1029 }
1030 }
1031
isTristate() const1032 bool AbstractCheckableAlbumModel::isTristate() const
1033 {
1034 return d->extraFlags &
1035
1036 #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
1037
1038 Qt::ItemIsAutoTristate;
1039
1040 #else
1041
1042 Qt::ItemIsTristate;
1043
1044 #endif
1045
1046 }
1047
setAddExcludeTristate(bool b)1048 void AbstractCheckableAlbumModel::setAddExcludeTristate(bool b)
1049 {
1050 d->addExcludeTristate = b;
1051 setCheckable(true);
1052 setTristate(b);
1053 }
1054
isAddExcludeTristate() const1055 bool AbstractCheckableAlbumModel::isAddExcludeTristate() const
1056 {
1057 return (d->addExcludeTristate && isTristate());
1058 }
1059
isChecked(Album * album) const1060 bool AbstractCheckableAlbumModel::isChecked(Album* album) const
1061 {
1062 return (d->checkedAlbums.value(album, Qt::Unchecked) == Qt::Checked);
1063 }
1064
checkState(Album * album) const1065 Qt::CheckState AbstractCheckableAlbumModel::checkState(Album* album) const
1066 {
1067 return (d->checkedAlbums.value(album, Qt::Unchecked));
1068 }
1069
setChecked(Album * album,bool isChecked)1070 void AbstractCheckableAlbumModel::setChecked(Album* album, bool isChecked)
1071 {
1072 setData(indexForAlbum(album), isChecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
1073 }
1074
setCheckState(Album * album,Qt::CheckState state)1075 void AbstractCheckableAlbumModel::setCheckState(Album* album, Qt::CheckState state)
1076 {
1077 setData(indexForAlbum(album), state, Qt::CheckStateRole);
1078 }
1079
toggleChecked(Album * album)1080 void AbstractCheckableAlbumModel::toggleChecked(Album* album)
1081 {
1082 if (checkState(album) != Qt::PartiallyChecked)
1083 {
1084 setChecked(album, !isChecked(album));
1085 }
1086 }
1087
checkedAlbums() const1088 QList<Album*> AbstractCheckableAlbumModel::checkedAlbums() const
1089 {
1090 // return a list with all keys with value Qt::Checked
1091
1092 return d->checkedAlbums.keys(Qt::Checked);
1093 }
1094
partiallyCheckedAlbums() const1095 QList<Album*> AbstractCheckableAlbumModel::partiallyCheckedAlbums() const
1096 {
1097 // return a list with all keys with value Qt::PartiallyChecked
1098
1099 return d->checkedAlbums.keys(Qt::PartiallyChecked);
1100 }
1101
resetAllCheckedAlbums()1102 void AbstractCheckableAlbumModel::resetAllCheckedAlbums()
1103 {
1104 const QHash<Album*, Qt::CheckState> oldChecked = d->checkedAlbums;
1105 d->checkedAlbums.clear();
1106
1107 for (QHash<Album*, Qt::CheckState>::const_iterator it = oldChecked.begin() ;
1108 it != oldChecked.end() ; ++it)
1109 {
1110 if (it.value() != Qt::Unchecked)
1111 {
1112 QModelIndex index = indexForAlbum(it.key());
1113 emit dataChanged(index, index, d->staticVectorContainingCheckStateRole);
1114 emit checkStateChanged(it.key(), Qt::Unchecked);
1115 }
1116 }
1117 }
1118
setDataForChildren(const QModelIndex & parent,const QVariant & value,int role)1119 void AbstractCheckableAlbumModel::setDataForChildren(const QModelIndex& parent, const QVariant& value, int role)
1120 {
1121 setData(parent, value, role);
1122
1123 for (int row = 0 ; row < rowCount(parent) ; ++row)
1124 {
1125 QModelIndex childIndex = index(row, 0, parent);
1126 setDataForChildren(childIndex, value, role);
1127 }
1128 }
1129
resetCheckedAlbums(const QModelIndex & parent)1130 void AbstractCheckableAlbumModel::resetCheckedAlbums(const QModelIndex& parent)
1131 {
1132 if (parent == rootAlbumIndex())
1133 {
1134 resetAllCheckedAlbums();
1135 return;
1136 }
1137
1138 setDataForChildren(parent, Qt::Unchecked, Qt::CheckStateRole);
1139 }
1140
setDataForParents(const QModelIndex & child,const QVariant & value,int role)1141 void AbstractCheckableAlbumModel::setDataForParents(const QModelIndex& child, const QVariant& value, int role)
1142 {
1143 QModelIndex current = child;
1144
1145 while (current.isValid() && (current != rootAlbumIndex()))
1146 {
1147 setData(current, value, role);
1148 current = parent(current);
1149 }
1150 }
1151
resetCheckedParentAlbums(const QModelIndex & child)1152 void AbstractCheckableAlbumModel::resetCheckedParentAlbums(const QModelIndex& child)
1153 {
1154 setDataForParents(child, Qt::Unchecked, Qt::CheckStateRole);
1155 }
1156
checkAllParentAlbums(const QModelIndex & child)1157 void AbstractCheckableAlbumModel::checkAllParentAlbums(const QModelIndex& child)
1158 {
1159 setDataForParents(child, Qt::Checked, Qt::CheckStateRole);
1160 }
1161
checkAllAlbums(const QModelIndex & parent)1162 void AbstractCheckableAlbumModel::checkAllAlbums(const QModelIndex& parent)
1163 {
1164 setDataForChildren(parent, Qt::Checked, Qt::CheckStateRole);
1165 }
1166
invertCheckedAlbums(const QModelIndex & parent)1167 void AbstractCheckableAlbumModel::invertCheckedAlbums(const QModelIndex& parent)
1168 {
1169 Album* const album = albumForIndex(parent);
1170
1171 if (album)
1172 {
1173 toggleChecked(album);
1174 }
1175
1176 for (int row = 0 ; row < rowCount(parent) ; ++row)
1177 {
1178 invertCheckedAlbums(index(row, 0, parent));
1179 }
1180 }
1181
setCheckStateForChildren(Album * album,Qt::CheckState state)1182 void AbstractCheckableAlbumModel::setCheckStateForChildren(Album* album, Qt::CheckState state)
1183 {
1184 QModelIndex index = indexForAlbum(album);
1185 setDataForChildren(index, state, Qt::CheckStateRole);
1186 }
1187
setCheckStateForParents(Album * album,Qt::CheckState state)1188 void AbstractCheckableAlbumModel::setCheckStateForParents(Album* album, Qt::CheckState state)
1189 {
1190 QModelIndex index = indexForAlbum(album);
1191 setDataForParents(index, state, Qt::CheckStateRole);
1192 }
1193
albumData(Album * a,int role) const1194 QVariant AbstractCheckableAlbumModel::albumData(Album* a, int role) const
1195 {
1196 if (role == Qt::CheckStateRole)
1197 {
1198 if ((d->extraFlags & Qt::ItemIsUserCheckable) &&
1199 (!a->isRoot() || d->rootIsCheckable))
1200 {
1201 // with Qt::Unchecked as default, albums not in the hash (initially all)
1202 // are simply regarded as unchecked
1203
1204 Qt::CheckState state = d->checkedAlbums.value(a, Qt::Unchecked);
1205
1206 if (d->addExcludeTristate)
1207 {
1208 // Use Qt::PartiallyChecked only internally, do not expose it to the TreeView
1209
1210 return ((state == Qt::Unchecked) ? Qt::Unchecked : Qt::Checked);
1211 }
1212
1213 return state;
1214 }
1215 }
1216
1217 return AbstractCountingAlbumModel::albumData(a, role);
1218 }
1219
prepareAddExcludeDecoration(Album * a,QPixmap & icon) const1220 void AbstractCheckableAlbumModel::prepareAddExcludeDecoration(Album* a, QPixmap& icon) const
1221 {
1222 if (!d->addExcludeTristate)
1223 {
1224 return;
1225 }
1226
1227 Qt::CheckState state = checkState(a);
1228
1229 if (state != Qt::Unchecked)
1230 {
1231 int iconSize = qMax(icon.width(), icon.height());
1232 int overlay_size = qMin(iconSize, qMax(16, iconSize * 2 / 3));
1233 QPainter p(&icon);
1234 p.drawPixmap((icon.width() - overlay_size) / 2,
1235 (icon.height() - overlay_size) / 2,
1236 QIcon::fromTheme(state == Qt::PartiallyChecked ? QLatin1String("list-remove")
1237 : QLatin1String("list-add")).pixmap(overlay_size, overlay_size));
1238 }
1239 }
1240
flags(const QModelIndex & index) const1241 Qt::ItemFlags AbstractCheckableAlbumModel::flags(const QModelIndex& index) const
1242 {
1243 Qt::ItemFlags extraFlags = d->extraFlags;
1244
1245 if (!d->rootIsCheckable)
1246 {
1247 QModelIndex root = rootAlbumIndex();
1248
1249 if (root.isValid() && (index == root))
1250 {
1251 extraFlags &= ~Qt::ItemIsUserCheckable;
1252 }
1253 }
1254
1255 return AbstractCountingAlbumModel::flags(index) | extraFlags;
1256 }
1257
setData(const QModelIndex & index,const QVariant & value,int role)1258 bool AbstractCheckableAlbumModel::setData(const QModelIndex& index, const QVariant& value, int role)
1259 {
1260 if (role == Qt::CheckStateRole)
1261 {
1262 Qt::CheckState state = (Qt::CheckState)value.toInt();
1263 Album* const album = albumForIndex(index);
1264
1265 if (!album)
1266 {
1267 return false;
1268 }
1269 /*
1270 qCDebug(DIGIKAM_GENERAL_LOG) << "Updating check state for album" << album->title() << "to" << value;
1271 */
1272 d->checkedAlbums.insert(album, state);
1273 emit dataChanged(index, index);
1274 emit checkStateChanged(album, state);
1275
1276 return true;
1277 }
1278 else
1279 {
1280 return AbstractCountingAlbumModel::setData(index, value, role);
1281 }
1282 }
1283
albumCleared(Album * album)1284 void AbstractCheckableAlbumModel::albumCleared(Album* album)
1285 {
1286 // preserve check state if album is only being moved
1287
1288 if (!AlbumManager::instance()->isMovingAlbum(album))
1289 {
1290 d->checkedAlbums.remove(album);
1291 }
1292
1293 AbstractCountingAlbumModel::albumCleared(album);
1294 }
1295
allAlbumsCleared()1296 void AbstractCheckableAlbumModel::allAlbumsCleared()
1297 {
1298 d->checkedAlbums.clear();
1299 AbstractCountingAlbumModel::allAlbumsCleared();
1300 }
1301
1302 } // namespace Digikam
1303