1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2009-03-05
7 * Description : Qt item model for database entries
8 *
9 * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
10 * Copyright (C) 2012-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11 *
12 * This program is free software; you can redistribute it
13 * and/or modify it under the terms of the GNU General
14 * Public License as published by the Free Software Foundation;
15 * either version 2, or (at your option)
16 * any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * ============================================================ */
24
25 #include "itemmodel.h"
26
27 // Qt includes
28
29 #include <QHash>
30 #include <QItemSelection>
31
32 // Local includes
33
34 #include "digikam_debug.h"
35 #include "coredbchangesets.h"
36 #include "coredbfields.h"
37 #include "coredbwatch.h"
38 #include "iteminfo.h"
39 #include "iteminfolist.h"
40 #include "abstractitemdragdrophandler.h"
41
42 namespace Digikam
43 {
44
45 class Q_DECL_HIDDEN ItemModel::Private
46 {
47 public:
48
Private()49 explicit Private()
50 : keepFilePathCache (false),
51 sendRemovalSignals (false),
52 preprocessor (nullptr),
53 refreshing (false),
54 reAdding (false),
55 incrementalRefreshRequested(false),
56 incrementalUpdater (nullptr)
57 {
58 }
59
60 public:
61
62 ItemInfoList infos;
63 ItemInfo itemInfo;
64
65 QList<QVariant> extraValues;
66 QMultiHash<qlonglong, int> idHash;
67
68 bool keepFilePathCache;
69 QHash<QString, qlonglong> filePathHash;
70
71 bool sendRemovalSignals;
72
73 QObject* preprocessor;
74 bool refreshing;
75 bool reAdding;
76 bool incrementalRefreshRequested;
77
78 DatabaseFields::Set watchFlags;
79
80 class ItemModelIncrementalUpdater* incrementalUpdater;
81
82 ItemInfoList pendingInfos;
83 QList<QVariant> pendingExtraValues;
84
85 public:
86
isValid(const QModelIndex & index)87 inline bool isValid(const QModelIndex& index)
88 {
89 if (!index.isValid())
90 {
91 return false;
92 }
93
94 if ((index.row() < 0) || (index.row() >= infos.size()))
95 {
96 qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index" << index;
97 return false;
98 }
99
100 return true;
101 }
102
extraValueValid(const QModelIndex & index)103 inline bool extraValueValid(const QModelIndex& index)
104 {
105 // we assume isValid() being called before, no duplicate checks
106
107 if (extraValues.size() == 0)
108 {
109 return false;
110 }
111 else if (index.row() >= extraValues.size())
112 {
113 qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index for extraData" << index;
114 return false;
115 }
116
117 return true;
118 }
119 };
120
121 // -------------------------------------------------------------------------------------
122
123 typedef QPair<int, int> IntPair; // to make foreach macro happy
124 typedef QList<IntPair> IntPairList;
125
126 class Q_DECL_HIDDEN ItemModelIncrementalUpdater
127 {
128 public:
129
130 explicit ItemModelIncrementalUpdater(ItemModel::Private* d);
131
132 void appendInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues);
133 void aboutToBeRemovedInModel(const IntPairList& aboutToBeRemoved);
134 QList<IntPair> oldIndexes();
135
136 static QList<IntPair> toContiguousPairs(const QList<int>& ids);
137
138 public:
139
140 QHash<qlonglong, int> oldIds;
141 QList<QVariant> oldExtraValues;
142 QList<ItemInfo> newInfos;
143 QList<QVariant> newExtraValues;
144 QList<IntPairList> modelRemovals;
145 };
146
147 // -------------------------------------------------------------------------------------
148
ItemModel(QObject * const parent)149 ItemModel::ItemModel(QObject* const parent)
150 : QAbstractListModel(parent),
151 d (new Private)
152 {
153 // --- NOTE: use dynamic binding as slotImageChange() is a virtual slot which can be re-implemented in derived classes.
154 connect(CoreDbAccess::databaseWatch(), static_cast<void (CoreDbWatch::*)(const ImageChangeset&)>(&CoreDbWatch::imageChange),
155 this, &ItemModel::slotImageChange);
156
157 // --- NOTE: use dynamic binding as slotImageTagChange() is a virtual slot which can be re-implemented in derived classes.
158 connect(CoreDbAccess::databaseWatch(), static_cast<void (CoreDbWatch::*)(const ImageTagChangeset&)>(&CoreDbWatch::imageTagChange),
159 this, &ItemModel::slotImageTagChange);
160 }
161
~ItemModel()162 ItemModel::~ItemModel()
163 {
164 delete d->incrementalUpdater;
165 delete d;
166 }
167
168 // ------------ Access methods -------------
169
setKeepsFilePathCache(bool keepCache)170 void ItemModel::setKeepsFilePathCache(bool keepCache)
171 {
172 d->keepFilePathCache = keepCache;
173 }
174
keepsFilePathCache() const175 bool ItemModel::keepsFilePathCache() const
176 {
177 return d->keepFilePathCache;
178 }
179
isEmpty() const180 bool ItemModel::isEmpty() const
181 {
182 return d->infos.isEmpty();
183 }
184
itemCount() const185 int ItemModel::itemCount() const
186 {
187 return d->infos.count();
188 }
189
setWatchFlags(const DatabaseFields::Set & set)190 void ItemModel::setWatchFlags(const DatabaseFields::Set& set)
191 {
192 d->watchFlags = set;
193 }
194
imageInfo(const QModelIndex & index) const195 ItemInfo ItemModel::imageInfo(const QModelIndex& index) const
196 {
197 if (!d->isValid(index))
198 {
199 return ItemInfo();
200 }
201
202 return d->infos.at(index.row());
203 }
204
imageInfoRef(const QModelIndex & index) const205 ItemInfo& ItemModel::imageInfoRef(const QModelIndex& index) const
206 {
207 if (!d->isValid(index))
208 {
209 return d->itemInfo;
210 }
211
212 return d->infos[index.row()];
213 }
214
imageId(const QModelIndex & index) const215 qlonglong ItemModel::imageId(const QModelIndex& index) const
216 {
217 if (!d->isValid(index))
218 {
219 return 0;
220 }
221
222 return d->infos.at(index.row()).id();
223 }
224
imageInfos(const QList<QModelIndex> & indexes) const225 QList<ItemInfo> ItemModel::imageInfos(const QList<QModelIndex>& indexes) const
226 {
227 QList<ItemInfo> infos;
228
229 foreach (const QModelIndex& index, indexes)
230 {
231 infos << imageInfo(index);
232 }
233
234 return infos;
235 }
236
imageIds(const QList<QModelIndex> & indexes) const237 QList<qlonglong> ItemModel::imageIds(const QList<QModelIndex>& indexes) const
238 {
239 QList<qlonglong> ids;
240
241 foreach (const QModelIndex& index, indexes)
242 {
243 ids << imageId(index);
244 }
245
246 return ids;
247 }
248
imageInfo(int row) const249 ItemInfo ItemModel::imageInfo(int row) const
250 {
251 if ((row < 0) || (row >= d->infos.size()))
252 {
253 return ItemInfo();
254 }
255
256 return d->infos.at(row);
257 }
258
imageInfoRef(int row) const259 ItemInfo& ItemModel::imageInfoRef(int row) const
260 {
261 if ((row < 0) || (row >= d->infos.size()))
262 {
263 return d->itemInfo;
264 }
265
266 return d->infos[row];
267 }
268
imageId(int row) const269 qlonglong ItemModel::imageId(int row) const
270 {
271 if ((row < 0) || (row >= d->infos.size()))
272 {
273 return -1;
274 }
275
276 return d->infos.at(row).id();
277 }
278
indexForItemInfo(const ItemInfo & info) const279 QModelIndex ItemModel::indexForItemInfo(const ItemInfo& info) const
280 {
281 return indexForImageId(info.id());
282 }
283
indexForItemInfo(const ItemInfo & info,const QVariant & extraValue) const284 QModelIndex ItemModel::indexForItemInfo(const ItemInfo& info, const QVariant& extraValue) const
285 {
286 return indexForImageId(info.id(), extraValue);
287 }
288
indexesForItemInfo(const ItemInfo & info) const289 QList<QModelIndex> ItemModel::indexesForItemInfo(const ItemInfo& info) const
290 {
291 return indexesForImageId(info.id());
292 }
293
indexForImageId(qlonglong id) const294 QModelIndex ItemModel::indexForImageId(qlonglong id) const
295 {
296 int index = d->idHash.value(id, -1);
297
298 if (index != -1)
299 {
300 return createIndex(index, 0);
301 }
302
303 return QModelIndex();
304 }
305
indexForImageId(qlonglong id,const QVariant & extraValue) const306 QModelIndex ItemModel::indexForImageId(qlonglong id, const QVariant& extraValue) const
307 {
308 if (d->extraValues.isEmpty())
309 {
310 return indexForImageId(id);
311 }
312
313 QMultiHash<qlonglong, int>::const_iterator it;
314
315 for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it)
316 {
317 if (d->extraValues.at(it.value()) == extraValue)
318 {
319 return createIndex(it.value(), 0);
320 }
321 }
322
323 return QModelIndex();
324 }
325
indexesForImageId(qlonglong id) const326 QList<QModelIndex> ItemModel::indexesForImageId(qlonglong id) const
327 {
328 QList<QModelIndex> indexes;
329 QMultiHash<qlonglong, int>::const_iterator it;
330
331 for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it)
332 {
333 indexes << createIndex(it.value(), 0);
334 }
335
336 return indexes;
337 }
338
numberOfIndexesForItemInfo(const ItemInfo & info) const339 int ItemModel::numberOfIndexesForItemInfo(const ItemInfo& info) const
340 {
341 return numberOfIndexesForImageId(info.id());
342 }
343
numberOfIndexesForImageId(qlonglong id) const344 int ItemModel::numberOfIndexesForImageId(qlonglong id) const
345 {
346 if (d->extraValues.isEmpty())
347 {
348 return 0;
349 }
350
351 int count = 0;
352 QMultiHash<qlonglong, int>::const_iterator it;
353
354 for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it)
355 {
356 ++count;
357 }
358
359 return count;
360 }
361
362 // static method
retrieveItemInfo(const QModelIndex & index)363 ItemInfo ItemModel::retrieveItemInfo(const QModelIndex& index)
364 {
365 if (!index.isValid())
366 {
367 return ItemInfo();
368 }
369
370 ItemModel* const model = index.data(ItemModelPointerRole).value<ItemModel*>();
371 int row = index.data(ItemModelInternalId).toInt();
372
373 if (!model)
374 {
375 return ItemInfo();
376 }
377
378 return model->imageInfo(row);
379 }
380
381 // static method
retrieveImageId(const QModelIndex & index)382 qlonglong ItemModel::retrieveImageId(const QModelIndex& index)
383 {
384 if (!index.isValid())
385 {
386 return 0;
387 }
388
389 ItemModel* const model = index.data(ItemModelPointerRole).value<ItemModel*>();
390 int row = index.data(ItemModelInternalId).toInt();
391
392 if (!model)
393 {
394 return 0;
395 }
396
397 return model->imageId(row);
398 }
399
indexForPath(const QString & filePath) const400 QModelIndex ItemModel::indexForPath(const QString& filePath) const
401 {
402 if (d->keepFilePathCache)
403 {
404 return indexForImageId(d->filePathHash.value(filePath));
405 }
406 else
407 {
408 const int size = d->infos.size();
409
410 for (int i = 0 ; i < size ; ++i)
411 {
412 if (d->infos.at(i).filePath() == filePath)
413 {
414 return createIndex(i, 0);
415 }
416 }
417 }
418
419 return QModelIndex();
420 }
421
indexesForPath(const QString & filePath) const422 QList<QModelIndex> ItemModel::indexesForPath(const QString& filePath) const
423 {
424 if (d->keepFilePathCache)
425 {
426 return indexesForImageId(d->filePathHash.value(filePath));
427 }
428 else
429 {
430 QList<QModelIndex> indexes;
431 const int size = d->infos.size();
432
433 for (int i = 0 ; i < size ; ++i)
434 {
435 if (d->infos.at(i).filePath() == filePath)
436 {
437 indexes << createIndex(i, 0);
438 }
439 }
440
441 return indexes;
442 }
443 }
444
imageInfo(const QString & filePath) const445 ItemInfo ItemModel::imageInfo(const QString& filePath) const
446 {
447 if (d->keepFilePathCache)
448 {
449 qlonglong id = d->filePathHash.value(filePath, -1);
450
451 if (id != -1)
452 {
453 int index = d->idHash.value(id, -1);
454
455 if (index != -1)
456 {
457 return d->infos.at(index);
458 }
459 }
460 }
461 else
462 {
463 foreach (const ItemInfo& info, d->infos)
464 {
465 if (info.filePath() == filePath)
466 {
467 return info;
468 }
469 }
470 }
471
472 return ItemInfo();
473 }
474
imageInfos(const QString & filePath) const475 QList<ItemInfo> ItemModel::imageInfos(const QString& filePath) const
476 {
477 QList<ItemInfo> infos;
478
479 if (d->keepFilePathCache)
480 {
481 qlonglong id = d->filePathHash.value(filePath, -1);
482
483 if (id != -1)
484 {
485 foreach (int index, d->idHash.values(id))
486 {
487 infos << d->infos.at(index);
488 }
489 }
490 }
491 else
492 {
493 foreach (const ItemInfo& info, d->infos)
494 {
495 if (info.filePath() == filePath)
496 {
497 infos << info;
498 }
499 }
500 }
501
502 return infos;
503 }
504
addItemInfo(const ItemInfo & info)505 void ItemModel::addItemInfo(const ItemInfo& info)
506 {
507 addItemInfos(QList<ItemInfo>() << info, QList<QVariant>());
508 }
509
addItemInfos(const QList<ItemInfo> & infos)510 void ItemModel::addItemInfos(const QList<ItemInfo>& infos)
511 {
512 addItemInfos(infos, QList<QVariant>());
513 }
514
addItemInfos(const QList<ItemInfo> & infos,const QList<QVariant> & extraValues)515 void ItemModel::addItemInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
516 {
517 if (infos.isEmpty())
518 {
519 return;
520 }
521
522 if (d->incrementalUpdater)
523 {
524 d->incrementalUpdater->appendInfos(infos, extraValues);
525 }
526 else
527 {
528 appendInfos(infos, extraValues);
529 }
530 }
531
addItemInfoSynchronously(const ItemInfo & info)532 void ItemModel::addItemInfoSynchronously(const ItemInfo& info)
533 {
534 addItemInfosSynchronously(QList<ItemInfo>() << info, QList<QVariant>());
535 }
536
addItemInfosSynchronously(const QList<ItemInfo> & infos)537 void ItemModel::addItemInfosSynchronously(const QList<ItemInfo>& infos)
538 {
539 addItemInfos(infos, QList<QVariant>());
540 }
541
addItemInfosSynchronously(const QList<ItemInfo> & infos,const QList<QVariant> & extraValues)542 void ItemModel::addItemInfosSynchronously(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
543 {
544 if (infos.isEmpty())
545 {
546 return;
547 }
548
549 publiciseInfos(infos, extraValues);
550 emit processAdded(infos, extraValues);
551 }
552
ensureHasItemInfo(const ItemInfo & info)553 void ItemModel::ensureHasItemInfo(const ItemInfo& info)
554 {
555 ensureHasItemInfos(QList<ItemInfo>() << info, QList<QVariant>());
556 }
557
ensureHasItemInfos(const QList<ItemInfo> & infos)558 void ItemModel::ensureHasItemInfos(const QList<ItemInfo>& infos)
559 {
560 ensureHasItemInfos(infos, QList<QVariant>());
561 }
562
ensureHasItemInfos(const QList<ItemInfo> & infos,const QList<QVariant> & extraValues)563 void ItemModel::ensureHasItemInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
564 {
565 if (extraValues.isEmpty())
566 {
567 if (!d->pendingExtraValues.isEmpty())
568 {
569 qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos.";
570 return;
571 }
572 }
573 else
574 {
575 if (d->pendingInfos.size() != d->pendingExtraValues.size())
576 {
577 qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos.";
578 return;
579 }
580 }
581
582 d->pendingInfos << infos;
583 d->pendingExtraValues << extraValues;
584 cleanSituationChecks();
585 }
586
clearItemInfos()587 void ItemModel::clearItemInfos()
588 {
589 beginResetModel();
590
591 d->infos.clear();
592 d->extraValues.clear();
593 d->idHash.clear();
594 d->filePathHash.clear();
595
596 delete d->incrementalUpdater;
597
598 d->incrementalUpdater = nullptr;
599 d->pendingInfos.clear();
600 d->pendingExtraValues.clear();
601 d->refreshing = false;
602 d->reAdding = false;
603 d->incrementalRefreshRequested = false;
604
605 imageInfosCleared();
606 endResetModel();
607 }
608
setItemInfos(const QList<ItemInfo> & infos)609 void ItemModel::setItemInfos(const QList<ItemInfo>& infos)
610 {
611 clearItemInfos();
612 addItemInfos(infos);
613 }
614
imageInfos() const615 QList<ItemInfo> ItemModel::imageInfos() const
616 {
617 return d->infos;
618 }
619
imageIds() const620 QList<qlonglong> ItemModel::imageIds() const
621 {
622 return d->idHash.keys();
623 }
624
hasImage(qlonglong id) const625 bool ItemModel::hasImage(qlonglong id) const
626 {
627 return d->idHash.contains(id);
628 }
629
hasImage(const ItemInfo & info) const630 bool ItemModel::hasImage(const ItemInfo& info) const
631 {
632 return d->idHash.contains(info.id());
633 }
634
hasImage(const ItemInfo & info,const QVariant & extraValue) const635 bool ItemModel::hasImage(const ItemInfo& info, const QVariant& extraValue) const
636 {
637 return hasImage(info.id(), extraValue);
638 }
639
hasImage(qlonglong id,const QVariant & extraValue) const640 bool ItemModel::hasImage(qlonglong id, const QVariant& extraValue) const
641 {
642 if (d->extraValues.isEmpty())
643 {
644 return hasImage(id);
645 }
646
647 QMultiHash<qlonglong, int>::const_iterator it;
648
649 for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it)
650 {
651 if (d->extraValues.at(it.value()) == extraValue)
652 {
653 return true;
654 }
655 }
656
657 return false;;
658 }
659
uniqueItemInfos() const660 QList<ItemInfo> ItemModel::uniqueItemInfos() const
661 {
662 if (d->extraValues.isEmpty())
663 {
664 return d->infos;
665 }
666
667 QList<ItemInfo> uniqueInfos;
668 const int size = d->infos.size();
669
670 for (int i = 0 ; i < size ; ++i)
671 {
672 const ItemInfo& info = d->infos.at(i);
673
674 if (d->idHash.value(info.id()) == i)
675 {
676 uniqueInfos << info;
677 }
678 }
679
680 return uniqueInfos;
681 }
682
emitDataChangedForAll()683 void ItemModel::emitDataChangedForAll()
684 {
685 if (d->infos.isEmpty())
686 {
687 return;
688 }
689
690 QModelIndex first = createIndex(0, 0);
691 QModelIndex last = createIndex(d->infos.size() - 1, 0);
692 emit dataChanged(first, last);
693 }
694
emitDataChangedForSelection(const QItemSelection & selection)695 void ItemModel::emitDataChangedForSelection(const QItemSelection& selection)
696 {
697 if (!selection.isEmpty())
698 {
699 foreach (const QItemSelectionRange& range, selection)
700 {
701 emit dataChanged(range.topLeft(), range.bottomRight());
702 }
703 }
704 }
705
ensureHasGroupedImages(const ItemInfo & groupLeader)706 void ItemModel::ensureHasGroupedImages(const ItemInfo& groupLeader)
707 {
708 ensureHasItemInfos(groupLeader.groupedImages());
709 }
710
711 // ------------ Preprocessing -------------
712
setPreprocessor(QObject * const preprocessor)713 void ItemModel::setPreprocessor(QObject* const preprocessor)
714 {
715 unsetPreprocessor(d->preprocessor);
716 d->preprocessor = preprocessor;
717 }
718
unsetPreprocessor(QObject * const preprocessor)719 void ItemModel::unsetPreprocessor(QObject* const preprocessor)
720 {
721 if (preprocessor && (d->preprocessor == preprocessor))
722 {
723 disconnect(this, SIGNAL(preprocess(QList<ItemInfo>,QList<QVariant>)),
724 nullptr, nullptr);
725
726 disconnect(d->preprocessor, nullptr,
727 this, SLOT(reAddItemInfos(QList<ItemInfo>,QList<QVariant>)));
728
729 disconnect(d->preprocessor, nullptr,
730 this, SLOT(reAddingFinished()));
731 }
732 }
733
appendInfos(const QList<ItemInfo> & infos,const QList<QVariant> & extraValues)734 void ItemModel::appendInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
735 {
736 if (infos.isEmpty())
737 {
738 return;
739 }
740
741 if (d->preprocessor)
742 {
743 d->reAdding = true;
744 emit preprocess(infos, extraValues);
745 }
746 else
747 {
748 publiciseInfos(infos, extraValues);
749 }
750 }
751
appendInfosChecked(const QList<ItemInfo> & infos,const QList<QVariant> & extraValues)752 void ItemModel::appendInfosChecked(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
753 {
754 // This method does deduplication. It is private because in context of readding or refreshing it is of no use.
755
756 if (extraValues.isEmpty())
757 {
758 QList<ItemInfo> checkedInfos;
759
760 foreach (const ItemInfo& info, infos)
761 {
762 if (!hasImage(info))
763 {
764 checkedInfos << info;
765 }
766 }
767
768 appendInfos(checkedInfos, QList<QVariant>());
769 }
770 else
771 {
772 QList<ItemInfo> checkedInfos;
773 QList<QVariant> checkedExtraValues;
774 const int size = infos.size();
775
776 for (int i = 0 ; i < size ; ++i)
777 {
778 if (!hasImage(infos[i], extraValues[i]))
779 {
780 checkedInfos << infos[i];
781 checkedExtraValues << extraValues[i];
782 }
783 }
784
785 appendInfos(checkedInfos, checkedExtraValues);
786 }
787 }
788
reAddItemInfos(const QList<ItemInfo> & infos,const QList<QVariant> & extraValues)789 void ItemModel::reAddItemInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
790 {
791 // addItemInfos -> appendInfos -> preprocessor -> reAddItemInfos
792
793 publiciseInfos(infos, extraValues);
794 }
795
reAddingFinished()796 void ItemModel::reAddingFinished()
797 {
798 d->reAdding = false;
799 cleanSituationChecks();
800 }
801
startRefresh()802 void ItemModel::startRefresh()
803 {
804 d->refreshing = true;
805 }
806
finishRefresh()807 void ItemModel::finishRefresh()
808 {
809 d->refreshing = false;
810 cleanSituationChecks();
811 }
812
isRefreshing() const813 bool ItemModel::isRefreshing() const
814 {
815 return d->refreshing;
816 }
817
cleanSituationChecks()818 void ItemModel::cleanSituationChecks()
819 {
820 // For starting an incremental refresh we want a clear situation:
821 // Any remaining batches from non-incremental refreshing subclasses have been received in appendInfos(),
822 // any batches sent to preprocessor for re-adding have been re-added.
823
824 if (d->refreshing || d->reAdding)
825 {
826 return;
827 }
828
829 if (!d->pendingInfos.isEmpty())
830 {
831 appendInfosChecked(d->pendingInfos, d->pendingExtraValues);
832 d->pendingInfos.clear();
833 d->pendingExtraValues.clear();
834 cleanSituationChecks();
835 return;
836 }
837
838 if (d->incrementalRefreshRequested)
839 {
840 d->incrementalRefreshRequested = false;
841 emit readyForIncrementalRefresh();
842 }
843 else
844 {
845 emit allRefreshingFinished();
846 }
847 }
848
publiciseInfos(const QList<ItemInfo> & infos,const QList<QVariant> & extraValues)849 void ItemModel::publiciseInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
850 {
851 if (infos.isEmpty())
852 {
853 return;
854 }
855
856 Q_ASSERT(
857 (infos.size() == extraValues.size()) ||
858 (extraValues.isEmpty() && d->extraValues.isEmpty())
859 );
860
861 emit imageInfosAboutToBeAdded(infos);
862 const int firstNewIndex = d->infos.size();
863 const int lastNewIndex = d->infos.size() + infos.size() - 1;
864 beginInsertRows(QModelIndex(), firstNewIndex, lastNewIndex);
865 d->infos << infos;
866 d->extraValues << extraValues;
867
868 for (int i = firstNewIndex ; i <= lastNewIndex ; ++i)
869 {
870 const ItemInfo& info = d->infos.at(i);
871 qlonglong id = info.id();
872 d->idHash.insert(id, i);
873
874 if (d->keepFilePathCache)
875 {
876 d->filePathHash[info.filePath()] = id;
877 }
878 }
879
880 endInsertRows();
881 emit imageInfosAdded(infos);
882 }
883
requestIncrementalRefresh()884 void ItemModel::requestIncrementalRefresh()
885 {
886 if (d->reAdding)
887 {
888 d->incrementalRefreshRequested = true;
889 }
890 else
891 {
892 emit readyForIncrementalRefresh();
893 }
894 }
895
hasIncrementalRefreshPending() const896 bool ItemModel::hasIncrementalRefreshPending() const
897 {
898 return d->incrementalRefreshRequested;
899 }
900
startIncrementalRefresh()901 void ItemModel::startIncrementalRefresh()
902 {
903 delete d->incrementalUpdater;
904
905 d->incrementalUpdater = new ItemModelIncrementalUpdater(d);
906 }
907
finishIncrementalRefresh()908 void ItemModel::finishIncrementalRefresh()
909 {
910 if (!d->incrementalUpdater)
911 {
912 return;
913 }
914
915 // remove old entries
916
917 QList<QPair<int, int> > pairs = d->incrementalUpdater->oldIndexes();
918 removeRowPairs(pairs);
919
920 // add new indexes
921
922 appendInfos(d->incrementalUpdater->newInfos, d->incrementalUpdater->newExtraValues);
923
924 delete d->incrementalUpdater;
925 d->incrementalUpdater = nullptr;
926 }
927
removeIndex(const QModelIndex & index)928 void ItemModel::removeIndex(const QModelIndex& index)
929 {
930 removeIndexes(QList<QModelIndex>() << index);
931 }
932
removeIndexes(const QList<QModelIndex> & indexes)933 void ItemModel::removeIndexes(const QList<QModelIndex>& indexes)
934 {
935 QList<int> listIndexes;
936
937 foreach (const QModelIndex& index, indexes)
938 {
939 if (d->isValid(index))
940 {
941 listIndexes << index.row();
942 }
943 }
944
945 if (listIndexes.isEmpty())
946 {
947 return;
948 }
949
950 removeRowPairsWithCheck(ItemModelIncrementalUpdater::toContiguousPairs(listIndexes));
951 }
952
removeItemInfo(const ItemInfo & info)953 void ItemModel::removeItemInfo(const ItemInfo& info)
954 {
955 removeItemInfos(QList<ItemInfo>() << info);
956 }
957
removeItemInfos(const QList<ItemInfo> & infos)958 void ItemModel::removeItemInfos(const QList<ItemInfo>& infos)
959 {
960 QList<int> listIndexes;
961
962 foreach (const ItemInfo& info, infos)
963 {
964 QModelIndex index = indexForImageId(info.id());
965
966 if (index.isValid())
967 {
968 listIndexes << index.row();
969 }
970 }
971
972 removeRowPairsWithCheck(ItemModelIncrementalUpdater::toContiguousPairs(listIndexes));
973 }
974
removeItemInfos(const QList<ItemInfo> & infos,const QList<QVariant> & extraValues)975 void ItemModel::removeItemInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
976 {
977 if (extraValues.isEmpty())
978 {
979 removeItemInfos(infos);
980 return;
981 }
982
983 QList<int> listIndexes;
984
985 for (int i = 0 ; i < infos.size() ; ++i)
986 {
987 QModelIndex index = indexForImageId(infos.at(i).id(), extraValues.at(i));
988
989 if (index.isValid())
990 {
991 listIndexes << index.row();
992 }
993 }
994
995 removeRowPairsWithCheck(ItemModelIncrementalUpdater::toContiguousPairs(listIndexes));
996 }
997
setSendRemovalSignals(bool send)998 void ItemModel::setSendRemovalSignals(bool send)
999 {
1000 d->sendRemovalSignals = send;
1001 }
1002 /*
1003 template <class List, typename T>
1004 static bool pairsContain(const List& list, T value)
1005 {
1006 typename List::const_iterator middle;
1007 typename List::const_iterator begin = list.begin();
1008 typename List::const_iterator end = list.end();
1009 int n = int(end - begin);
1010 int half;
1011
1012 while (n > 0)
1013 {
1014 half = n >> 1;
1015 middle = begin + half;
1016
1017 if ((middle->first <= value) && (middle->second >= value))
1018 {
1019 return true;
1020 }
1021 else if (middle->second < value)
1022 {
1023 begin = middle + 1;
1024 n -= half + 1;
1025 }
1026 else
1027 {
1028 n = half;
1029 }
1030 }
1031
1032 return false;
1033 }
1034 */
removeRowPairsWithCheck(const QList<QPair<int,int>> & toRemove)1035 void ItemModel::removeRowPairsWithCheck(const QList<QPair<int, int> >& toRemove)
1036 {
1037 if (d->incrementalUpdater)
1038 {
1039 d->incrementalUpdater->aboutToBeRemovedInModel(toRemove);
1040 }
1041
1042 removeRowPairs(toRemove);
1043 }
1044
removeRowPairs(const QList<QPair<int,int>> & toRemove)1045 void ItemModel::removeRowPairs(const QList<QPair<int, int> >& toRemove)
1046 {
1047 if (toRemove.isEmpty())
1048 {
1049 return;
1050 }
1051
1052 // Remove old indexes
1053 // Keep in mind that when calling beginRemoveRows all structures announced to be removed
1054 // must still be valid, and this includes our hashes as well, which limits what we can optimize
1055
1056 int removedRows = 0;
1057 int offset = 0;
1058 QList<qlonglong> removeFilePaths;
1059 typedef QPair<int, int> IntPair; // to make foreach macro happy
1060
1061
1062 foreach (const IntPair& pair, toRemove)
1063 {
1064 const int begin = pair.first - offset;
1065 const int end = pair.second - offset; // inclusive
1066 removedRows = end - begin + 1;
1067
1068 // when removing from the list, all subsequent indexes are affected
1069
1070 offset += removedRows;
1071
1072 QList<ItemInfo> removedInfos;
1073
1074 if (d->sendRemovalSignals)
1075 {
1076 // cppcheck-suppress knownEmptyContainer
1077 std::copy(d->infos.begin() + begin, d->infos.begin() + end, removedInfos.begin());
1078 emit imageInfosAboutToBeRemoved(removedInfos);
1079 }
1080
1081 imageInfosAboutToBeRemoved(begin, end);
1082 beginRemoveRows(QModelIndex(), begin, end);
1083
1084 // update idHash - which points to indexes of d->infos, and these change now!
1085
1086 QMultiHash<qlonglong, int>::iterator it;
1087
1088 for (it = d->idHash.begin() ; it != d->idHash.end() ; )
1089 {
1090 if (it.value() >= begin)
1091 {
1092 if (it.value() > end)
1093 {
1094 // after the removed interval: adjust index
1095
1096 it.value() -= removedRows;
1097 }
1098 else
1099 {
1100 // in the removed interval
1101
1102 removeFilePaths << it.key();
1103 it = d->idHash.erase(it);
1104 continue;
1105 }
1106 }
1107
1108 ++it;
1109 }
1110
1111 // remove from list
1112
1113 d->infos.erase(d->infos.begin() + begin, d->infos.begin() + (end + 1));
1114
1115 if (!d->extraValues.isEmpty())
1116 {
1117 d->extraValues.erase(d->extraValues.begin() + begin, d->extraValues.begin() + (end + 1));
1118 }
1119
1120 endRemoveRows();
1121
1122 if (d->sendRemovalSignals)
1123 {
1124 emit imageInfosRemoved(removedInfos);
1125 }
1126 }
1127
1128 // tidy up: remove old indexes from file path hash now
1129
1130 if (d->keepFilePathCache)
1131 {
1132 QHash<QString, qlonglong>::iterator it;
1133
1134 for (it = d->filePathHash.begin() ; it != d->filePathHash.end() ; )
1135 {
1136 if (removeFilePaths.contains(it.value()))
1137 {
1138 it = d->filePathHash.erase(it);
1139 }
1140 else
1141 {
1142 ++it;
1143 }
1144 }
1145 }
1146 }
1147
1148 // ---------------------------------------------------------------------------------
1149
ItemModelIncrementalUpdater(ItemModel::Private * d)1150 ItemModelIncrementalUpdater::ItemModelIncrementalUpdater(ItemModel::Private* d)
1151 : oldIds (d->idHash),
1152 oldExtraValues(d->extraValues)
1153 {
1154 }
1155
appendInfos(const QList<ItemInfo> & infos,const QList<QVariant> & extraValues)1156 void ItemModelIncrementalUpdater::appendInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
1157 {
1158 if (extraValues.isEmpty())
1159 {
1160 foreach (const ItemInfo& info, infos)
1161 {
1162 QHash<qlonglong, int>::iterator it = oldIds.find(info.id());
1163
1164 if (it != oldIds.end())
1165 {
1166 oldIds.erase(it);
1167 }
1168 else
1169 {
1170 newInfos << info;
1171 }
1172 }
1173 }
1174 else
1175 {
1176 for (int i = 0 ; i < infos.size() ; ++i)
1177 {
1178 const ItemInfo& info = infos.at(i);
1179 bool found = false;
1180 QHash<qlonglong, int>::iterator it;
1181
1182 for (it = oldIds.find(info.id()) ; it != oldIds.end() && it.key() == info.id() ; ++it)
1183 {
1184 // first check is for bug #262596. Not sure if needed.
1185
1186 if (it.value() < oldExtraValues.size() && extraValues.at(i) == oldExtraValues.at(it.value()))
1187 {
1188 found = true;
1189 break;
1190 }
1191 }
1192
1193 if (found)
1194 {
1195 oldIds.erase(it);
1196
1197 // do not erase from oldExtraValues - oldIds is a hash id -> index.
1198 }
1199 else
1200 {
1201 newInfos << info;
1202 newExtraValues << extraValues.at(i);
1203 }
1204 }
1205 }
1206 }
1207
aboutToBeRemovedInModel(const IntPairList & toRemove)1208 void ItemModelIncrementalUpdater::aboutToBeRemovedInModel(const IntPairList& toRemove)
1209 {
1210 modelRemovals << toRemove;
1211 }
1212
oldIndexes()1213 QList<QPair<int, int> > ItemModelIncrementalUpdater::oldIndexes()
1214 {
1215 // first, apply all changes to indexes by direct removal in model
1216 // while the updater was active
1217
1218 foreach (const IntPairList& list, modelRemovals)
1219 {
1220 int removedRows = 0;
1221 int offset = 0;
1222
1223 foreach (const IntPair& pair, list)
1224 {
1225 const int begin = pair.first - offset;
1226 const int end = pair.second - offset; // inclusive
1227 removedRows = end - begin + 1;
1228
1229 // when removing from the list, all subsequent indexes are affected
1230
1231 offset += removedRows;
1232
1233 // update idHash - which points to indexes of d->infos, and these change now!
1234
1235 QHash<qlonglong, int>::iterator it;
1236
1237 for (it = oldIds.begin() ; it != oldIds.end() ; )
1238 {
1239 if (it.value() >= begin)
1240 {
1241 if (it.value() > end)
1242 {
1243 // after the removed interval: adjust index
1244
1245 it.value() -= removedRows;
1246 }
1247 else
1248 {
1249 // in the removed interval
1250
1251 it = oldIds.erase(it);
1252 continue;
1253 }
1254 }
1255
1256 ++it;
1257 }
1258 }
1259 }
1260
1261 modelRemovals.clear();
1262
1263 return toContiguousPairs(oldIds.values());
1264 }
1265
toContiguousPairs(const QList<int> & unsorted)1266 QList<QPair<int, int> > ItemModelIncrementalUpdater::toContiguousPairs(const QList<int>& unsorted)
1267 {
1268 // Take the given indices and return them as contiguous pairs [begin, end]
1269
1270 QList<QPair<int, int> > pairs;
1271
1272 if (unsorted.isEmpty())
1273 {
1274 return pairs;
1275 }
1276
1277 QList<int> indices(unsorted);
1278 std::sort(indices.begin(), indices.end());
1279
1280 QPair<int, int> pair(indices.first(), indices.first());
1281
1282 for (int i = 1 ; i < indices.size() ; ++i)
1283 {
1284 const int& index = indices.at(i);
1285
1286 if (index == (pair.second + 1))
1287 {
1288 pair.second = index;
1289 continue;
1290 }
1291
1292 pairs << pair; // insert last pair
1293 pair.first = index;
1294 pair.second = index;
1295 }
1296
1297 pairs << pair;
1298
1299 return pairs;
1300 }
1301
1302 // ------------ QAbstractItemModel implementation -------------
1303
data(const QModelIndex & index,int role) const1304 QVariant ItemModel::data(const QModelIndex& index, int role) const
1305 {
1306 if (!d->isValid(index))
1307 {
1308 return QVariant();
1309 }
1310
1311 switch (role)
1312 {
1313 case Qt::DisplayRole:
1314 case Qt::ToolTipRole:
1315 {
1316 return d->infos.at(index.row()).name();
1317 }
1318
1319 case ItemModelPointerRole:
1320 {
1321 return QVariant::fromValue(const_cast<ItemModel*>(this));
1322 }
1323
1324 case ItemModelInternalId:
1325 {
1326 return index.row();
1327 }
1328
1329 case CreationDateRole:
1330 {
1331 return d->infos.at(index.row()).dateTime();
1332 }
1333
1334 case ExtraDataRole:
1335 {
1336 if (d->extraValueValid(index))
1337 {
1338 return d->extraValues.at(index.row());
1339 }
1340 else
1341 {
1342 return QVariant();
1343 }
1344 }
1345
1346 case ExtraDataDuplicateCount:
1347 {
1348 qlonglong id = d->infos.at(index.row()).id();
1349
1350 return numberOfIndexesForImageId(id);
1351 }
1352 }
1353
1354 return QVariant();
1355 }
1356
headerData(int section,Qt::Orientation orientation,int role) const1357 QVariant ItemModel::headerData(int section, Qt::Orientation orientation, int role) const
1358 {
1359 Q_UNUSED(section)
1360 Q_UNUSED(orientation)
1361 Q_UNUSED(role)
1362
1363 return QVariant();
1364 }
1365
rowCount(const QModelIndex & parent) const1366 int ItemModel::rowCount(const QModelIndex& parent) const
1367 {
1368 if (parent.isValid())
1369 {
1370 return 0;
1371 }
1372
1373 return d->infos.size();
1374 }
1375
flags(const QModelIndex & index) const1376 Qt::ItemFlags ItemModel::flags(const QModelIndex& index) const
1377 {
1378 if (!d->isValid(index))
1379 {
1380 return Qt::NoItemFlags;
1381 }
1382
1383 Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
1384 f |= dragDropFlags(index);
1385
1386 return f;
1387 }
1388
index(int row,int column,const QModelIndex & parent) const1389 QModelIndex ItemModel::index(int row, int column, const QModelIndex& parent) const
1390 {
1391 if ((column != 0) || (row < 0) || parent.isValid() || (row >= d->infos.size()))
1392 {
1393 return QModelIndex();
1394 }
1395
1396 return createIndex(row, 0);
1397 }
1398
1399 // ------------ Database watch -------------
1400
slotImageChange(const ImageChangeset & changeset)1401 void ItemModel::slotImageChange(const ImageChangeset& changeset)
1402 {
1403 if (d->infos.isEmpty())
1404 {
1405 return;
1406 }
1407
1408 if (d->watchFlags & changeset.changes())
1409 {
1410 QItemSelection items;
1411
1412 foreach (const qlonglong& id, changeset.ids())
1413 {
1414 QModelIndex index = indexForImageId(id);
1415
1416 if (index.isValid())
1417 {
1418 items.select(index, index);
1419 }
1420 }
1421
1422 if (!items.isEmpty())
1423 {
1424 emitDataChangedForSelection(items);
1425 emit imageChange(changeset, items);
1426 }
1427 }
1428 }
1429
slotImageTagChange(const ImageTagChangeset & changeset)1430 void ItemModel::slotImageTagChange(const ImageTagChangeset& changeset)
1431 {
1432 if (d->infos.isEmpty())
1433 {
1434 return;
1435 }
1436
1437 QItemSelection items;
1438
1439 foreach (const qlonglong& id, changeset.ids())
1440 {
1441 QModelIndex index = indexForImageId(id);
1442
1443 if (index.isValid())
1444 {
1445 items.select(index, index);
1446 }
1447 }
1448
1449 if (!items.isEmpty())
1450 {
1451 emitDataChangedForSelection(items);
1452 emit imageTagChange(changeset, items);
1453 }
1454 }
1455
1456 } // namespace Digikam
1457