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"