File indexing completed on 2025-01-05 03:54:12

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-03-05
0007  * Description : Qt item model for database entries
0008  *
0009  * SPDX-FileCopyrightText: 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  * SPDX-FileCopyrightText: 2012-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "itemmodel.h"
0017 
0018 // Qt includes
0019 
0020 #include <QHash>
0021 #include <QItemSelection>
0022 
0023 // Local includes
0024 
0025 #include "digikam_debug.h"
0026 #include "coredbchangesets.h"
0027 #include "coredbfields.h"
0028 #include "coredbwatch.h"
0029 #include "iteminfolist.h"
0030 #include "abstractitemdragdrophandler.h"
0031 
0032 namespace Digikam
0033 {
0034 
0035 class Q_DECL_HIDDEN ItemModel::Private
0036 {
0037 public:
0038 
0039     explicit Private()
0040       : keepFilePathCache          (false),
0041         sendRemovalSignals         (false),
0042         preprocessor               (nullptr),
0043         refreshing                 (false),
0044         reAdding                   (false),
0045         incrementalRefreshRequested(false),
0046         incrementalUpdater         (nullptr)
0047     {
0048     }
0049 
0050 public:
0051 
0052     ItemInfoList                       infos;
0053     ItemInfo                           itemInfo;
0054 
0055     QList<QVariant>                    extraValues;
0056     QMultiHash<qlonglong, int>         idHash;
0057 
0058     bool                               keepFilePathCache;
0059     QHash<QString, qlonglong>          filePathHash;
0060 
0061     bool                               sendRemovalSignals;
0062 
0063     QObject*                           preprocessor;
0064     bool                               refreshing;
0065     bool                               reAdding;
0066     bool                               incrementalRefreshRequested;
0067 
0068     DatabaseFields::Set                watchFlags;
0069 
0070     class ItemModelIncrementalUpdater* incrementalUpdater;
0071 
0072     ItemInfoList                       pendingInfos;
0073     QList<QVariant>                    pendingExtraValues;
0074 
0075 public:
0076 
0077     inline bool isValid(const QModelIndex& index)
0078     {
0079         if (!index.isValid())
0080         {
0081             return false;
0082         }
0083 
0084         if ((index.row() < 0) || (index.row() >= infos.size()))
0085         {
0086             qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index" << index;
0087             return false;
0088         }
0089 
0090         return true;
0091     }
0092 
0093     inline bool extraValueValid(const QModelIndex& index)
0094     {
0095         // we assume isValid() being called before, no duplicate checks
0096 
0097         if      (extraValues.size() == 0)
0098         {
0099             return false;
0100         }
0101         else if (index.row() >= extraValues.size())
0102         {
0103             qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index for extraData" << index;
0104             return false;
0105         }
0106 
0107         return true;
0108     }
0109 };
0110 
0111 // -------------------------------------------------------------------------------------
0112 
0113 typedef QPair<int, int> IntPair; // to make foreach macro happy
0114 typedef QList<IntPair>  IntPairList;
0115 
0116 class Q_DECL_HIDDEN ItemModelIncrementalUpdater
0117 {
0118 public:
0119 
0120     explicit ItemModelIncrementalUpdater(ItemModel::Private* d);
0121 
0122     void                  appendInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues);
0123     void                  aboutToBeRemovedInModel(const IntPairList& aboutToBeRemoved);
0124     QList<IntPair>        oldIndexes();
0125 
0126     static QList<IntPair> toContiguousPairs(const QList<int>& ids);
0127 
0128 public:
0129 
0130     QMultiHash<qlonglong, int> oldIds;
0131     QList<QVariant>            oldExtraValues;
0132     QList<ItemInfo>            newInfos;
0133     QList<QVariant>            newExtraValues;
0134     QList<IntPairList>         modelRemovals;
0135 };
0136 
0137 // -------------------------------------------------------------------------------------
0138 
0139 ItemModel::ItemModel(QObject* const parent)
0140     : QAbstractListModel(parent),
0141       d                 (new Private)
0142 {
0143     // --- NOTE: use dynamic binding as slotAlbumChange() is a virtual slot which can be re-implemented in derived classes.
0144     connect(CoreDbAccess::databaseWatch(), static_cast<void (CoreDbWatch::*)(const AlbumChangeset&)>(&CoreDbWatch::albumChange),
0145             this, &ItemModel::slotAlbumChange);
0146 
0147     // --- NOTE: use dynamic binding as slotImageChange() is a virtual slot which can be re-implemented in derived classes.
0148     connect(CoreDbAccess::databaseWatch(), static_cast<void (CoreDbWatch::*)(const ImageChangeset&)>(&CoreDbWatch::imageChange),
0149             this, &ItemModel::slotImageChange);
0150 
0151     // --- NOTE: use dynamic binding as slotImageTagChange() is a virtual slot which can be re-implemented in derived classes.
0152     connect(CoreDbAccess::databaseWatch(), static_cast<void (CoreDbWatch::*)(const ImageTagChangeset&)>(&CoreDbWatch::imageTagChange),
0153             this, &ItemModel::slotImageTagChange);
0154 }
0155 
0156 ItemModel::~ItemModel()
0157 {
0158     delete d->incrementalUpdater;
0159     delete d;
0160 }
0161 
0162 // ------------ Access methods -------------
0163 
0164 void ItemModel::setKeepsFilePathCache(bool keepCache)
0165 {
0166     d->keepFilePathCache = keepCache;
0167 }
0168 
0169 bool ItemModel::keepsFilePathCache() const
0170 {
0171     return d->keepFilePathCache;
0172 }
0173 
0174 bool ItemModel::isEmpty() const
0175 {
0176     return d->infos.isEmpty();
0177 }
0178 
0179 int ItemModel::itemCount() const
0180 {
0181     return d->infos.count();
0182 }
0183 
0184 void ItemModel::setWatchFlags(const DatabaseFields::Set& set)
0185 {
0186     d->watchFlags = set;
0187 }
0188 
0189 ItemInfo ItemModel::imageInfo(const QModelIndex& index) const
0190 {
0191     if (!d->isValid(index))
0192     {
0193         return ItemInfo();
0194     }
0195 
0196     return d->infos.at(index.row());
0197 }
0198 
0199 ItemInfo& ItemModel::imageInfoRef(const QModelIndex& index) const
0200 {
0201     if (!d->isValid(index))
0202     {
0203         return d->itemInfo;
0204     }
0205 
0206     return d->infos[index.row()];
0207 }
0208 
0209 qlonglong ItemModel::imageId(const QModelIndex& index) const
0210 {
0211     if (!d->isValid(index))
0212     {
0213         return 0;
0214     }
0215 
0216     return d->infos.at(index.row()).id();
0217 }
0218 
0219 QList<ItemInfo> ItemModel::imageInfos(const QList<QModelIndex>& indexes) const
0220 {
0221     QList<ItemInfo> infos;
0222 
0223     Q_FOREACH (const QModelIndex& index, indexes)
0224     {
0225         infos << imageInfo(index);
0226     }
0227 
0228     return infos;
0229 }
0230 
0231 QList<qlonglong> ItemModel::imageIds(const QList<QModelIndex>& indexes) const
0232 {
0233     QList<qlonglong> ids;
0234 
0235     Q_FOREACH (const QModelIndex& index, indexes)
0236     {
0237         ids << imageId(index);
0238     }
0239 
0240     return ids;
0241 }
0242 
0243 ItemInfo ItemModel::imageInfo(int row) const
0244 {
0245     if ((row < 0) || (row >= d->infos.size()))
0246     {
0247         return ItemInfo();
0248     }
0249 
0250     return d->infos.at(row);
0251 }
0252 
0253 ItemInfo& ItemModel::imageInfoRef(int row) const
0254 {
0255     if ((row < 0) || (row >= d->infos.size()))
0256     {
0257         return d->itemInfo;
0258     }
0259 
0260     return d->infos[row];
0261 }
0262 
0263 qlonglong ItemModel::imageId(int row) const
0264 {
0265     if ((row < 0) || (row >= d->infos.size()))
0266     {
0267         return -1;
0268     }
0269 
0270     return d->infos.at(row).id();
0271 }
0272 
0273 QModelIndex ItemModel::indexForItemInfo(const ItemInfo& info) const
0274 {
0275     return indexForImageId(info.id());
0276 }
0277 
0278 QModelIndex ItemModel::indexForItemInfo(const ItemInfo& info, const QVariant& extraValue) const
0279 {
0280     return indexForImageId(info.id(), extraValue);
0281 }
0282 
0283 QList<QModelIndex> ItemModel::indexesForItemInfo(const ItemInfo& info) const
0284 {
0285     return indexesForImageId(info.id());
0286 }
0287 
0288 QModelIndex ItemModel::indexForImageId(qlonglong id) const
0289 {
0290     int index = d->idHash.value(id, -1);
0291 
0292     if (index != -1)
0293     {
0294         return createIndex(index, 0);
0295     }
0296 
0297     return QModelIndex();
0298 }
0299 
0300 QModelIndex ItemModel::indexForImageId(qlonglong id, const QVariant& extraValue) const
0301 {
0302     if (d->extraValues.isEmpty())
0303     {
0304         return indexForImageId(id);
0305     }
0306 
0307     QMultiHash<qlonglong, int>::const_iterator it;
0308 
0309     for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it)
0310     {
0311         if (d->extraValues.at(it.value()) == extraValue)
0312         {
0313             return createIndex(it.value(), 0);
0314         }
0315     }
0316 
0317     return QModelIndex();
0318 }
0319 
0320 QList<QModelIndex> ItemModel::indexesForImageId(qlonglong id) const
0321 {
0322     QList<QModelIndex> indexes;
0323     QMultiHash<qlonglong, int>::const_iterator it;
0324 
0325     for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it)
0326     {
0327         indexes << createIndex(it.value(), 0);
0328     }
0329 
0330     return indexes;
0331 }
0332 
0333 int ItemModel::numberOfIndexesForItemInfo(const ItemInfo& info) const
0334 {
0335     return numberOfIndexesForImageId(info.id());
0336 }
0337 
0338 int ItemModel::numberOfIndexesForImageId(qlonglong id) const
0339 {
0340     if (d->extraValues.isEmpty())
0341     {
0342         return 0;
0343     }
0344 
0345     int count = 0;
0346     QMultiHash<qlonglong, int>::const_iterator it;
0347 
0348     for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it)
0349     {
0350         ++count;
0351     }
0352 
0353     return count;
0354 }
0355 
0356 // static method
0357 ItemInfo ItemModel::retrieveItemInfo(const QModelIndex& index)
0358 {
0359     if (!index.isValid())
0360     {
0361         return ItemInfo();
0362     }
0363 
0364     ItemModel* const model = index.data(ItemModelPointerRole).value<ItemModel*>();
0365     int row                = index.data(ItemModelInternalId).toInt();
0366 
0367     if (!model)
0368     {
0369         return ItemInfo();
0370     }
0371 
0372     return model->imageInfo(row);
0373 }
0374 
0375 // static method
0376 qlonglong ItemModel::retrieveImageId(const QModelIndex& index)
0377 {
0378     if (!index.isValid())
0379     {
0380         return 0;
0381     }
0382 
0383     ItemModel* const model = index.data(ItemModelPointerRole).value<ItemModel*>();
0384     int row                = index.data(ItemModelInternalId).toInt();
0385 
0386     if (!model)
0387     {
0388         return 0;
0389     }
0390 
0391     return model->imageId(row);
0392 }
0393 
0394 QModelIndex ItemModel::indexForPath(const QString& filePath) const
0395 {
0396     if (d->keepFilePathCache)
0397     {
0398         return indexForImageId(d->filePathHash.value(filePath));
0399     }
0400     else
0401     {
0402         const int size = d->infos.size();
0403 
0404         for (int i = 0 ; i < size ; ++i)
0405         {
0406             if (d->infos.at(i).filePath() == filePath)
0407             {
0408                 return createIndex(i, 0);
0409             }
0410         }
0411     }
0412 
0413     return QModelIndex();
0414 }
0415 
0416 QList<QModelIndex> ItemModel::indexesForPath(const QString& filePath) const
0417 {
0418     if (d->keepFilePathCache)
0419     {
0420         return indexesForImageId(d->filePathHash.value(filePath));
0421     }
0422     else
0423     {
0424         QList<QModelIndex> indexes;
0425         const int size = d->infos.size();
0426 
0427         for (int i = 0 ; i < size ; ++i)
0428         {
0429             if (d->infos.at(i).filePath() == filePath)
0430             {
0431                 indexes << createIndex(i, 0);
0432             }
0433         }
0434 
0435         return indexes;
0436     }
0437 }
0438 
0439 ItemInfo ItemModel::imageInfo(const QString& filePath) const
0440 {
0441     if (d->keepFilePathCache)
0442     {
0443         qlonglong id = d->filePathHash.value(filePath, -1);
0444 
0445         if (id != -1)
0446         {
0447             int index = d->idHash.value(id, -1);
0448 
0449             if (index != -1)
0450             {
0451                 return d->infos.at(index);
0452             }
0453         }
0454     }
0455     else
0456     {
0457         Q_FOREACH (const ItemInfo& info, d->infos)
0458         {
0459             if (info.filePath() == filePath)
0460             {    // cppcheck-suppress useStlAlgorithm
0461                 return info;
0462             }
0463         }
0464     }
0465 
0466     return ItemInfo();
0467 }
0468 
0469 QList<ItemInfo> ItemModel::imageInfos(const QString& filePath) const
0470 {
0471     QList<ItemInfo> infos;
0472 
0473     if (d->keepFilePathCache)
0474     {
0475         qlonglong id = d->filePathHash.value(filePath, -1);
0476 
0477         if (id != -1)
0478         {
0479             Q_FOREACH (int index, d->idHash.values(id))
0480             {
0481                 infos << d->infos.at(index);
0482             }
0483         }
0484     }
0485     else
0486     {
0487         Q_FOREACH (const ItemInfo& info, d->infos)
0488         {
0489             if (info.filePath() == filePath)
0490             {
0491                 infos << info;
0492             }
0493         }
0494     }
0495 
0496     return infos;
0497 }
0498 
0499 void ItemModel::addItemInfo(const ItemInfo& info)
0500 {
0501     addItemInfos(QList<ItemInfo>() << info, QList<QVariant>());
0502 }
0503 
0504 void ItemModel::addItemInfos(const QList<ItemInfo>& infos)
0505 {
0506     addItemInfos(infos, QList<QVariant>());
0507 }
0508 
0509 void ItemModel::addItemInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
0510 {
0511     if (infos.isEmpty())
0512     {
0513         return;
0514     }
0515 
0516     if (d->incrementalUpdater)
0517     {
0518         d->incrementalUpdater->appendInfos(infos, extraValues);
0519     }
0520     else
0521     {
0522         appendInfos(infos, extraValues);
0523     }
0524 }
0525 
0526 void ItemModel::addItemInfoSynchronously(const ItemInfo& info)
0527 {
0528     addItemInfosSynchronously(QList<ItemInfo>() << info, QList<QVariant>());
0529 }
0530 
0531 void ItemModel::addItemInfosSynchronously(const QList<ItemInfo>& infos)
0532 {
0533     addItemInfos(infos, QList<QVariant>());
0534 }
0535 
0536 void ItemModel::addItemInfosSynchronously(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
0537 {
0538     if (infos.isEmpty())
0539     {
0540         return;
0541     }
0542 
0543     publiciseInfos(infos, extraValues);
0544     Q_EMIT processAdded(infos, extraValues);
0545 }
0546 
0547 void ItemModel::ensureHasItemInfo(const ItemInfo& info)
0548 {
0549     ensureHasItemInfos(QList<ItemInfo>() << info, QList<QVariant>());
0550 }
0551 
0552 void ItemModel::ensureHasItemInfos(const QList<ItemInfo>& infos)
0553 {
0554     ensureHasItemInfos(infos, QList<QVariant>());
0555 }
0556 
0557 void ItemModel::ensureHasItemInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
0558 {
0559     if (extraValues.isEmpty())
0560     {
0561         if (!d->pendingExtraValues.isEmpty())
0562         {
0563             qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos.";
0564             return;
0565         }
0566     }
0567     else
0568     {
0569         if (d->pendingInfos.size() != d->pendingExtraValues.size())
0570         {
0571             qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos.";
0572             return;
0573         }
0574     }
0575 
0576     d->pendingInfos << infos;
0577     d->pendingExtraValues << extraValues;
0578     cleanSituationChecks();
0579 }
0580 
0581 void ItemModel::clearItemInfos()
0582 {
0583     beginResetModel();
0584 
0585     d->infos.clear();
0586     d->extraValues.clear();
0587     d->idHash.clear();
0588     d->filePathHash.clear();
0589 
0590     delete d->incrementalUpdater;
0591 
0592     d->incrementalUpdater          = nullptr;
0593     d->pendingInfos.clear();
0594     d->pendingExtraValues.clear();
0595     d->refreshing                  = false;
0596     d->reAdding                    = false;
0597     d->incrementalRefreshRequested = false;
0598 
0599     imageInfosCleared();
0600     endResetModel();
0601 }
0602 
0603 void ItemModel::setItemInfos(const QList<ItemInfo>& infos)
0604 {
0605     clearItemInfos();
0606     addItemInfos(infos);
0607 }
0608 
0609 QList<ItemInfo> ItemModel::imageInfos() const
0610 {
0611     return d->infos;
0612 }
0613 
0614 QList<qlonglong> ItemModel::imageIds() const
0615 {
0616     return d->idHash.keys();
0617 }
0618 
0619 bool ItemModel::hasImage(qlonglong id) const
0620 {
0621     return d->idHash.contains(id);
0622 }
0623 
0624 bool ItemModel::hasImage(const ItemInfo& info) const
0625 {
0626     return d->idHash.contains(info.id());
0627 }
0628 
0629 bool ItemModel::hasImage(const ItemInfo& info, const QVariant& extraValue) const
0630 {
0631     return hasImage(info.id(), extraValue);
0632 }
0633 
0634 bool ItemModel::hasImage(qlonglong id, const QVariant& extraValue) const
0635 {
0636     if (d->extraValues.isEmpty())
0637     {
0638         return hasImage(id);
0639     }
0640 
0641     QMultiHash<qlonglong, int>::const_iterator it;
0642 
0643     for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it)
0644     {
0645         if (d->extraValues.at(it.value()) == extraValue)
0646         {
0647             return true;
0648         }
0649     }
0650 
0651     return false;;
0652 }
0653 
0654 QList<ItemInfo> ItemModel::uniqueItemInfos() const
0655 {
0656     if (d->extraValues.isEmpty())
0657     {
0658         return d->infos;
0659     }
0660 
0661     QList<ItemInfo> uniqueInfos;
0662     const int size = d->infos.size();
0663 
0664     for (int i = 0 ; i < size ; ++i)
0665     {
0666         const ItemInfo& info = d->infos.at(i);
0667 
0668         if (d->idHash.value(info.id()) == i)
0669         {
0670             uniqueInfos << info;
0671         }
0672     }
0673 
0674     return uniqueInfos;
0675 }
0676 
0677 void ItemModel::emitDataChangedForAll()
0678 {
0679     if (d->infos.isEmpty())
0680     {
0681         return;
0682     }
0683 
0684     QModelIndex first = createIndex(0, 0);
0685     QModelIndex last  = createIndex(d->infos.size() - 1, 0);
0686     Q_EMIT dataChanged(first, last);
0687 }
0688 
0689 void ItemModel::emitDataChangedForSelection(const QItemSelection& selection)
0690 {
0691     if (!selection.isEmpty())
0692     {
0693         Q_FOREACH (const QItemSelectionRange& range, selection)
0694         {
0695             Q_EMIT dataChanged(range.topLeft(), range.bottomRight());
0696         }
0697     }
0698 }
0699 
0700 void ItemModel::ensureHasGroupedImages(const ItemInfo& groupLeader)
0701 {
0702     ensureHasItemInfos(groupLeader.groupedImages());
0703 }
0704 
0705 // ------------ Preprocessing -------------
0706 
0707 void ItemModel::setPreprocessor(QObject* const preprocessor)
0708 {
0709     unsetPreprocessor(d->preprocessor);
0710     d->preprocessor = preprocessor;
0711 }
0712 
0713 void ItemModel::unsetPreprocessor(QObject* const preprocessor)
0714 {
0715     if (preprocessor && (d->preprocessor == preprocessor))
0716     {
0717         disconnect(this, SIGNAL(preprocess(QList<ItemInfo>,QList<QVariant>)),
0718                    nullptr, nullptr);
0719 
0720         disconnect(d->preprocessor, nullptr,
0721                    this, SLOT(reAddItemInfos(QList<ItemInfo>,QList<QVariant>)));
0722 
0723         disconnect(d->preprocessor, nullptr,
0724                    this, SLOT(reAddingFinished()));
0725     }
0726 }
0727 
0728 void ItemModel::appendInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
0729 {
0730     if (infos.isEmpty())
0731     {
0732         return;
0733     }
0734 
0735     if (d->preprocessor)
0736     {
0737         d->reAdding = true;
0738         Q_EMIT preprocess(infos, extraValues);
0739     }
0740     else
0741     {
0742         publiciseInfos(infos, extraValues);
0743     }
0744 }
0745 
0746 void ItemModel::appendInfosChecked(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
0747 {
0748     // This method does deduplication. It is private because in context of readding or refreshing it is of no use.
0749 
0750     if (extraValues.isEmpty())
0751     {
0752         QList<ItemInfo> checkedInfos;
0753 
0754         Q_FOREACH (const ItemInfo& info, infos)
0755         {
0756             if (!hasImage(info))
0757             {
0758                 checkedInfos << info;
0759             }
0760         }
0761 
0762         appendInfos(checkedInfos, QList<QVariant>());
0763     }
0764     else
0765     {
0766         QList<ItemInfo> checkedInfos;
0767         QList<QVariant> checkedExtraValues;
0768         const int size = infos.size();
0769 
0770         for (int i = 0 ; i < size ; ++i)
0771         {
0772             if (!hasImage(infos[i], extraValues[i]))
0773             {
0774                 checkedInfos       << infos[i];
0775                 checkedExtraValues << extraValues[i];
0776             }
0777         }
0778 
0779         appendInfos(checkedInfos, checkedExtraValues);
0780     }
0781 }
0782 
0783 void ItemModel::reAddItemInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
0784 {
0785     // addItemInfos -> appendInfos -> preprocessor -> reAddItemInfos
0786 
0787     publiciseInfos(infos, extraValues);
0788 }
0789 
0790 void ItemModel::reAddingFinished()
0791 {
0792     d->reAdding = false;
0793     cleanSituationChecks();
0794 }
0795 
0796 void ItemModel::startRefresh()
0797 {
0798     d->refreshing = true;
0799 }
0800 
0801 void ItemModel::finishRefresh()
0802 {
0803     d->refreshing = false;
0804     cleanSituationChecks();
0805 }
0806 
0807 bool ItemModel::isRefreshing() const
0808 {
0809     return d->refreshing;
0810 }
0811 
0812 void ItemModel::cleanSituationChecks()
0813 {
0814     // For starting an incremental refresh we want a clear situation:
0815     // Any remaining batches from non-incremental refreshing subclasses have been received in appendInfos(),
0816     // any batches sent to preprocessor for re-adding have been re-added.
0817 
0818     if (d->refreshing || d->reAdding)
0819     {
0820         return;
0821     }
0822 
0823     if (!d->pendingInfos.isEmpty())
0824     {
0825         appendInfosChecked(d->pendingInfos, d->pendingExtraValues);
0826         d->pendingInfos.clear();
0827         d->pendingExtraValues.clear();
0828         cleanSituationChecks();
0829         return;
0830     }
0831 
0832     if (d->incrementalRefreshRequested)
0833     {
0834         d->incrementalRefreshRequested = false;
0835         Q_EMIT readyForIncrementalRefresh();
0836     }
0837     else
0838     {
0839         Q_EMIT allRefreshingFinished();
0840     }
0841 }
0842 
0843 void ItemModel::publiciseInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
0844 {
0845     if (infos.isEmpty())
0846     {
0847         return;
0848     }
0849 
0850     Q_ASSERT(
0851              (infos.size() == extraValues.size()) ||
0852              (extraValues.isEmpty() && d->extraValues.isEmpty())
0853             );
0854 
0855     Q_EMIT imageInfosAboutToBeAdded(infos);
0856     const int firstNewIndex = d->infos.size();
0857     const int lastNewIndex  = d->infos.size() + infos.size() - 1;
0858     beginInsertRows(QModelIndex(), firstNewIndex, lastNewIndex);
0859     d->infos       << infos;
0860     d->extraValues << extraValues;
0861 
0862     for (int i = firstNewIndex ; i <= lastNewIndex ; ++i)
0863     {
0864         const ItemInfo& info = d->infos.at(i);
0865         qlonglong id         = info.id();
0866         d->idHash.insert(id, i);
0867 
0868         if (d->keepFilePathCache)
0869         {
0870             d->filePathHash[info.filePath()] = id;
0871         }
0872     }
0873 
0874     endInsertRows();
0875     Q_EMIT imageInfosAdded(infos);
0876 }
0877 
0878 void ItemModel::requestIncrementalRefresh()
0879 {
0880     if (d->reAdding)
0881     {
0882         d->incrementalRefreshRequested = true;
0883     }
0884     else
0885     {
0886         Q_EMIT readyForIncrementalRefresh();
0887     }
0888 }
0889 
0890 bool ItemModel::hasIncrementalRefreshPending() const
0891 {
0892     return d->incrementalRefreshRequested;
0893 }
0894 
0895 void ItemModel::startIncrementalRefresh()
0896 {
0897     delete d->incrementalUpdater;
0898 
0899     d->incrementalUpdater = new ItemModelIncrementalUpdater(d);
0900 }
0901 
0902 void ItemModel::finishIncrementalRefresh()
0903 {
0904     if (!d->incrementalUpdater)
0905     {
0906         return;
0907     }
0908 
0909     // remove old entries
0910 
0911     QList<QPair<int, int> > pairs = d->incrementalUpdater->oldIndexes();
0912     removeRowPairs(pairs);
0913 
0914     // add new indexes
0915 
0916     appendInfos(d->incrementalUpdater->newInfos, d->incrementalUpdater->newExtraValues);
0917 
0918     delete d->incrementalUpdater;
0919     d->incrementalUpdater = nullptr;
0920 }
0921 
0922 void ItemModel::removeIndex(const QModelIndex& index)
0923 {
0924     removeIndexes(QList<QModelIndex>() << index);
0925 }
0926 
0927 void ItemModel::removeIndexes(const QList<QModelIndex>& indexes)
0928 {
0929     QList<int> listIndexes;
0930 
0931     Q_FOREACH (const QModelIndex& index, indexes)
0932     {
0933         if (d->isValid(index))
0934         {
0935             listIndexes << index.row();
0936         }
0937     }
0938 
0939     if (listIndexes.isEmpty())
0940     {
0941         return;
0942     }
0943 
0944     removeRowPairsWithCheck(ItemModelIncrementalUpdater::toContiguousPairs(listIndexes));
0945 }
0946 
0947 void ItemModel::removeItemInfo(const ItemInfo& info)
0948 {
0949     removeItemInfos(QList<ItemInfo>() << info);
0950 }
0951 
0952 void ItemModel::removeItemInfos(const QList<ItemInfo>& infos)
0953 {
0954     QList<int> listIndexes;
0955 
0956     Q_FOREACH (const ItemInfo& info, infos)
0957     {
0958         QModelIndex index = indexForImageId(info.id());
0959 
0960         if (index.isValid())
0961         {
0962             listIndexes << index.row();
0963         }
0964     }
0965 
0966     removeRowPairsWithCheck(ItemModelIncrementalUpdater::toContiguousPairs(listIndexes));
0967 }
0968 
0969 void ItemModel::removeItemInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
0970 {
0971     if (extraValues.isEmpty())
0972     {
0973         removeItemInfos(infos);
0974         return;
0975     }
0976 
0977     QList<int> listIndexes;
0978 
0979     for (int i = 0 ; i < infos.size() ; ++i)
0980     {
0981         QModelIndex index = indexForImageId(infos.at(i).id(), extraValues.at(i));
0982 
0983         if (index.isValid())
0984         {
0985             listIndexes << index.row();
0986         }
0987     }
0988 
0989     removeRowPairsWithCheck(ItemModelIncrementalUpdater::toContiguousPairs(listIndexes));
0990 }
0991 
0992 void ItemModel::setSendRemovalSignals(bool send)
0993 {
0994     d->sendRemovalSignals = send;
0995 }
0996 /*
0997 template <class List, typename T>
0998 static bool pairsContain(const List& list, T value)
0999 {
1000     typename List::const_iterator middle;
1001     typename List::const_iterator begin = list.begin();
1002     typename List::const_iterator end   = list.end();
1003     int n                               = int(end - begin);
1004     int half;
1005 
1006     while (n > 0)
1007     {
1008         half   = n >> 1;
1009         middle = begin + half;
1010 
1011         if      ((middle->first <= value) && (middle->second >= value))
1012         {
1013             return true;
1014         }
1015         else if (middle->second < value)
1016         {
1017             begin = middle + 1;
1018             n    -= half   + 1;
1019         }
1020         else
1021         {
1022             n = half;
1023         }
1024     }
1025 
1026     return false;
1027 }
1028 */
1029 void ItemModel::removeRowPairsWithCheck(const QList<QPair<int, int> >& toRemove)
1030 {
1031     if (d->incrementalUpdater)
1032     {
1033         d->incrementalUpdater->aboutToBeRemovedInModel(toRemove);
1034     }
1035 
1036     removeRowPairs(toRemove);
1037 }
1038 
1039 void ItemModel::removeRowPairs(const QList<QPair<int, int> >& toRemove)
1040 {
1041     if (toRemove.isEmpty())
1042     {
1043         return;
1044     }
1045 
1046     // Remove old indexes
1047     // Keep in mind that when calling beginRemoveRows all structures announced to be removed
1048     // must still be valid, and this includes our hashes as well, which limits what we can optimize
1049 
1050     int                     removedRows = 0;
1051     int                     offset      = 0;
1052     QList<qlonglong>        removeFilePaths;
1053     typedef QPair<int, int> IntPair;            // to make foreach macro happy
1054 
1055 
1056     Q_FOREACH (const IntPair& pair, toRemove)
1057     {
1058         const int begin = pair.first  - offset;
1059         const int end   = pair.second - offset; // inclusive
1060         removedRows     = end - begin + 1;
1061 
1062         // when removing from the list, all subsequent indexes are affected
1063 
1064         offset         += removedRows;
1065 
1066         QList<ItemInfo> removedInfos;
1067 
1068         if (d->sendRemovalSignals)
1069         {
1070             // cppcheck-suppress knownEmptyContainer
1071             std::copy(d->infos.begin() + begin, d->infos.begin() + end, removedInfos.begin());
1072             Q_EMIT imageInfosAboutToBeRemoved(removedInfos);
1073         }
1074 
1075         imageInfosAboutToBeRemoved(begin, end);
1076         beginRemoveRows(QModelIndex(), begin, end);
1077 
1078         // update idHash - which points to indexes of d->infos, and these change now!
1079 
1080         QMultiHash<qlonglong, int>::iterator it;
1081 
1082         for (it = d->idHash.begin() ; it != d->idHash.end() ; )
1083         {
1084             if (it.value() >= begin)
1085             {
1086                 if (it.value() > end)
1087                 {
1088                     // after the removed interval: adjust index
1089 
1090                     it.value() -= removedRows;
1091                 }
1092                 else
1093                 {
1094                     // in the removed interval
1095 
1096                     removeFilePaths << it.key();
1097                     it = d->idHash.erase(it);
1098                     continue;
1099                 }
1100             }
1101 
1102             ++it;
1103         }
1104 
1105         // remove from list
1106 
1107         d->infos.erase(d->infos.begin() + begin, d->infos.begin() + (end + 1));
1108 
1109         if (!d->extraValues.isEmpty())
1110         {
1111             d->extraValues.erase(d->extraValues.begin() + begin, d->extraValues.begin() + (end + 1));
1112         }
1113 
1114         endRemoveRows();
1115 
1116         if (d->sendRemovalSignals)
1117         {
1118             Q_EMIT imageInfosRemoved(removedInfos);
1119         }
1120     }
1121 
1122     // tidy up: remove old indexes from file path hash now
1123 
1124     if (d->keepFilePathCache)
1125     {
1126         QHash<QString, qlonglong>::iterator it;
1127 
1128         for (it = d->filePathHash.begin() ; it != d->filePathHash.end() ; )
1129         {
1130             if (removeFilePaths.contains(it.value()))
1131             {
1132                 it = d->filePathHash.erase(it);
1133             }
1134             else
1135             {
1136                 ++it;
1137             }
1138         }
1139     }
1140 }
1141 
1142 // ---------------------------------------------------------------------------------
1143 
1144 ItemModelIncrementalUpdater::ItemModelIncrementalUpdater(ItemModel::Private* d)
1145     : oldIds        (d->idHash),
1146       oldExtraValues(d->extraValues)
1147 {
1148 }
1149 
1150 void ItemModelIncrementalUpdater::appendInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues)
1151 {
1152     if (extraValues.isEmpty())
1153     {
1154         Q_FOREACH (const ItemInfo& info, infos)
1155         {
1156             QMultiHash<qlonglong, int>::iterator it = oldIds.find(info.id());
1157 
1158             if (it != oldIds.end())
1159             {
1160                 oldIds.erase(it);
1161             }
1162             else
1163             {
1164                 newInfos << info;
1165             }
1166         }
1167     }
1168     else
1169     {
1170         for (int i = 0 ; i < infos.size() ; ++i)
1171         {
1172             const ItemInfo& info = infos.at(i);
1173             bool found           = false;
1174             QMultiHash<qlonglong, int>::iterator it;
1175 
1176             for (it = oldIds.find(info.id()) ; it != oldIds.end() && it.key() == info.id() ; ++it)
1177             {
1178                 // first check is for bug #262596. Not sure if needed.
1179 
1180                 if (it.value() < oldExtraValues.size() && extraValues.at(i) == oldExtraValues.at(it.value()))
1181                 {
1182                     found = true;
1183                     break;
1184                 }
1185             }
1186 
1187             if (found)
1188             {
1189                 oldIds.erase(it);
1190 
1191                 // do not erase from oldExtraValues - oldIds is a hash id -> index.
1192             }
1193             else
1194             {
1195                 newInfos << info;
1196                 newExtraValues << extraValues.at(i);
1197             }
1198         }
1199     }
1200 }
1201 
1202 void ItemModelIncrementalUpdater::aboutToBeRemovedInModel(const IntPairList& toRemove)
1203 {
1204     modelRemovals << toRemove;
1205 }
1206 
1207 QList<QPair<int, int> > ItemModelIncrementalUpdater::oldIndexes()
1208 {
1209     // first, apply all changes to indexes by direct removal in model
1210     // while the updater was active
1211 
1212     Q_FOREACH (const IntPairList& list, modelRemovals)
1213     {
1214         int removedRows = 0;
1215         int offset      = 0;
1216 
1217         Q_FOREACH (const IntPair& pair, list)
1218         {
1219             const int begin = pair.first - offset;
1220             const int end   = pair.second - offset; // inclusive
1221             removedRows     = end - begin + 1;
1222 
1223             // when removing from the list, all subsequent indexes are affected
1224 
1225             offset         += removedRows;
1226 
1227             // update idHash - which points to indexes of d->infos, and these change now!
1228 
1229             QMultiHash<qlonglong, int>::iterator it;
1230 
1231             for (it = oldIds.begin() ; it != oldIds.end() ; )
1232             {
1233                 if (it.value() >= begin)
1234                 {
1235                     if (it.value() > end)
1236                     {
1237                         // after the removed interval: adjust index
1238 
1239                         it.value() -= removedRows;
1240                     }
1241                     else
1242                     {
1243                         // in the removed interval
1244 
1245                         it = oldIds.erase(it);
1246                         continue;
1247                     }
1248                 }
1249 
1250                 ++it;
1251             }
1252         }
1253     }
1254 
1255     modelRemovals.clear();
1256 
1257     return toContiguousPairs(oldIds.values());
1258 }
1259 
1260 QList<QPair<int, int> > ItemModelIncrementalUpdater::toContiguousPairs(const QList<int>& unsorted)
1261 {
1262     // Take the given indices and return them as contiguous pairs [begin, end]
1263 
1264     QList<QPair<int, int> > pairs;
1265 
1266     if (unsorted.isEmpty())
1267     {
1268         return pairs;
1269     }
1270 
1271     QList<int> indices(unsorted);
1272     std::sort(indices.begin(), indices.end());
1273 
1274     QPair<int, int> pair(indices.first(), indices.first());
1275 
1276     for (int i = 1 ; i < indices.size() ; ++i)
1277     {
1278         const int& index = indices.at(i);
1279 
1280         if (index == (pair.second + 1))
1281         {
1282             pair.second = index;
1283             continue;
1284         }
1285 
1286         pairs << pair;          // insert last pair
1287         pair.first  = index;
1288         pair.second = index;
1289     }
1290 
1291     pairs << pair;
1292 
1293     return pairs;
1294 }
1295 
1296 // ------------ QAbstractItemModel implementation -------------
1297 
1298 QVariant ItemModel::data(const QModelIndex& index, int role) const
1299 {
1300     if (!d->isValid(index))
1301     {
1302         return QVariant();
1303     }
1304 
1305     switch (role)
1306     {
1307         case Qt::DisplayRole:
1308         case Qt::ToolTipRole:
1309         {
1310             return d->infos.at(index.row()).name();
1311         }
1312 
1313         case ItemModelPointerRole:
1314         {
1315             return QVariant::fromValue(const_cast<ItemModel*>(this));
1316         }
1317 
1318         case ItemModelInternalId:
1319         {
1320             return index.row();
1321         }
1322 
1323         case CreationDateRole:
1324         {
1325             return d->infos.at(index.row()).dateTime();
1326         }
1327 
1328         case ExtraDataRole:
1329         {
1330             if (d->extraValueValid(index))
1331             {
1332                 return d->extraValues.at(index.row());
1333             }
1334             else
1335             {
1336                 return QVariant();
1337             }
1338         }
1339 
1340         case ExtraDataDuplicateCount:
1341         {
1342             qlonglong id = d->infos.at(index.row()).id();
1343 
1344             return numberOfIndexesForImageId(id);
1345         }
1346     }
1347 
1348     return QVariant();
1349 }
1350 
1351 QVariant ItemModel::headerData(int section, Qt::Orientation orientation, int role) const
1352 {
1353     Q_UNUSED(section)
1354     Q_UNUSED(orientation)
1355     Q_UNUSED(role)
1356 
1357     return QVariant();
1358 }
1359 
1360 int ItemModel::rowCount(const QModelIndex& parent) const
1361 {
1362     if (parent.isValid())
1363     {
1364         return 0;
1365     }
1366 
1367     return d->infos.size();
1368 }
1369 
1370 Qt::ItemFlags ItemModel::flags(const QModelIndex& index) const
1371 {
1372     if (!d->isValid(index))
1373     {
1374         return Qt::NoItemFlags;
1375     }
1376 
1377     Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
1378     f              |= dragDropFlags(index);
1379 
1380     return f;
1381 }
1382 
1383 QModelIndex ItemModel::index(int row, int column, const QModelIndex& parent) const
1384 {
1385     if ((column != 0) || (row < 0) || parent.isValid() || (row >= d->infos.size()))
1386     {
1387         return QModelIndex();
1388     }
1389 
1390     return createIndex(row, 0);
1391 }
1392 
1393 // ------------ Database watch -------------
1394 
1395 void ItemModel::slotAlbumChange(const AlbumChangeset& changeset)
1396 {
1397     if (d->infos.isEmpty() || !d->keepFilePathCache)
1398     {
1399         return;
1400     }
1401 
1402     if (changeset.operation() & AlbumChangeset::Renamed)
1403     {
1404         Q_FOREACH (const ItemInfo& info, d->infos)
1405         {
1406             if (changeset.albumId() == info.albumId())
1407             {
1408                 d->filePathHash.remove(d->filePathHash.key(info.id()));
1409                 d->filePathHash[info.filePath()] = info.id();
1410             }
1411         }
1412     }
1413 }
1414 
1415 void ItemModel::slotImageChange(const ImageChangeset& changeset)
1416 {
1417     if (d->infos.isEmpty())
1418     {
1419         return;
1420     }
1421 
1422     if (d->watchFlags & changeset.changes())
1423     {
1424         if (changeset.changes() & DatabaseFields::Name)
1425         {
1426             if (d->keepFilePathCache)
1427             {
1428                 Q_FOREACH (const qlonglong& id, changeset.ids())
1429                 {
1430                     d->filePathHash.remove(d->filePathHash.key(id));
1431                     int index = d->idHash.value(id, -1);
1432 
1433                     if (index != -1)
1434                     {
1435                         const ItemInfo& info = d->infos.at(index);
1436                         d->filePathHash[info.filePath()] = id;
1437                     }
1438                 }
1439             }
1440 
1441             return;
1442         }
1443 
1444         QItemSelection items;
1445 
1446         Q_FOREACH (const qlonglong& id, changeset.ids())
1447         {
1448             QModelIndex index = indexForImageId(id);
1449 
1450             if (index.isValid())
1451             {
1452                 items.select(index, index);
1453             }
1454         }
1455 
1456         if (!items.isEmpty())
1457         {
1458             emitDataChangedForSelection(items);
1459             Q_EMIT imageChange(changeset, items);
1460         }
1461     }
1462 }
1463 
1464 void ItemModel::slotImageTagChange(const ImageTagChangeset& changeset)
1465 {
1466     if (d->infos.isEmpty())
1467     {
1468         return;
1469     }
1470 
1471     QItemSelection items;
1472 
1473     Q_FOREACH (const qlonglong& id, changeset.ids())
1474     {
1475         QModelIndex index = indexForImageId(id);
1476 
1477         if (index.isValid())
1478         {
1479             items.select(index, index);
1480         }
1481     }
1482 
1483     if (!items.isEmpty())
1484     {
1485         emitDataChangedForSelection(items);
1486         Q_EMIT imageTagChange(changeset, items);
1487     }
1488 }
1489 
1490 } // namespace Digikam
1491 
1492 #include "moc_itemmodel.cpp"