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