File indexing completed on 2025-01-19 03:59:24

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2012-05-22
0007  * Description : Qt item model for camera entries
0008  *
0009  * SPDX-FileCopyrightText: 2012 by Islam Wazery <wazery at ubuntu dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "importimagemodel.h"
0016 
0017 // Qt includes
0018 
0019 #include <QHash>
0020 
0021 // Local includes
0022 
0023 #include "digikam_debug.h"
0024 #include "coredbdownloadhistory.h"
0025 #include "cameracontroller.h"
0026 
0027 namespace Digikam
0028 {
0029 
0030 class Q_DECL_HIDDEN ImportItemModel::Private
0031 {
0032 public:
0033 
0034     explicit Private()
0035       : controller                  (nullptr),
0036         keepFileUrlCache            (false),
0037         refreshing                  (false),
0038         reAdding                    (false),
0039         incrementalRefreshRequested (false),
0040         sendRemovalSignals          (false),
0041         incrementalUpdater          (nullptr)
0042     {
0043     }
0044 
0045     inline bool isValid(const QModelIndex& index)
0046     {
0047         return (
0048                 index.isValid()              &&
0049                 (index.row() >= 0)           &&
0050                 (index.row() < infos.size())
0051                );
0052     }
0053 
0054 public:
0055 
0056     CameraController*                        controller;
0057     CamItemInfoList                          infos;
0058     CamItemInfo                              camItemInfo;
0059 
0060     QMultiHash<qlonglong, int>               idHash;
0061     QHash<QString, qlonglong>                fileUrlHash;
0062 
0063     bool                                     keepFileUrlCache;
0064 
0065     bool                                     refreshing;
0066     bool                                     reAdding;
0067     bool                                     incrementalRefreshRequested;
0068 
0069     bool                                     sendRemovalSignals;
0070 
0071     class ImportItemModelIncrementalUpdater* incrementalUpdater;
0072 };
0073 
0074 // ----------------------------------------------------------------------------------------------------
0075 
0076 typedef QPair<int, int> IntPair;
0077 typedef QList<IntPair>  IntPairList;
0078 
0079 class Q_DECL_HIDDEN ImportItemModelIncrementalUpdater
0080 {
0081 public:
0082 
0083     explicit ImportItemModelIncrementalUpdater(ImportItemModel::Private* const d);
0084 
0085     void            appendInfos(const QList<CamItemInfo>& infos);
0086     void            aboutToBeRemovedInModel(const IntPairList& aboutToBeRemoved);
0087     QList<IntPair>  oldIndexes();
0088 
0089     static QList<IntPair> toContiguousPairs(const QList<int>& ids);
0090 
0091 public:
0092 
0093     QMultiHash<qlonglong, int> oldIds;
0094     QList<CamItemInfo>         newInfos;
0095     QList<IntPairList>         modelRemovals;
0096 };
0097 
0098 // ----------------------------------------------------------------------------------------------------
0099 
0100 ImportItemModel::ImportItemModel(QObject* const parent)
0101     : QAbstractListModel(parent),
0102       d                 (new Private)
0103 {
0104 }
0105 
0106 ImportItemModel::~ImportItemModel()
0107 {
0108     delete d;
0109 }
0110 
0111 void ImportItemModel::setCameraThumbsController(CameraThumbsCtrl* const thumbsCtrl)
0112 {
0113     d->controller = thumbsCtrl->cameraController();
0114 
0115     connect(d->controller, SIGNAL(signalFileList(CamItemInfoList)),
0116             SLOT(addCamItemInfos(CamItemInfoList)));
0117 
0118     connect(d->controller, SIGNAL(signalDeleted(QString,QString,bool)),
0119             SLOT(slotFileDeleted(QString,QString,bool)));
0120 
0121     connect(d->controller, SIGNAL(signalUploaded(CamItemInfo)),
0122             SLOT(slotFileUploaded(CamItemInfo)));
0123 }
0124 
0125 void ImportItemModel::setKeepsFileUrlCache(bool keepCache)
0126 {
0127     d->keepFileUrlCache = keepCache;
0128 }
0129 
0130 bool ImportItemModel::keepsFileUrlCache() const
0131 {
0132     return d->keepFileUrlCache;
0133 }
0134 
0135 bool ImportItemModel::isEmpty() const
0136 {
0137     return d->infos.isEmpty();
0138 }
0139 
0140 CamItemInfo ImportItemModel::camItemInfo(const QModelIndex& index) const
0141 {
0142     if (!d->isValid(index))
0143     {
0144         return CamItemInfo();
0145     }
0146 
0147     return d->infos.at(index.row());
0148 }
0149 
0150 CamItemInfo& ImportItemModel::camItemInfoRef(const QModelIndex& index) const
0151 {
0152     if (!d->isValid(index))
0153     {
0154         return d->camItemInfo;
0155     }
0156 
0157     return d->infos[index.row()];
0158 }
0159 
0160 qlonglong ImportItemModel::camItemId(const QModelIndex& index) const
0161 {
0162     if (!d->isValid(index))
0163     {
0164         return -1;
0165     }
0166 
0167     return d->infos.at(index.row()).id;
0168 }
0169 
0170 QList<CamItemInfo> ImportItemModel::camItemInfos(const QList<QModelIndex>& indexes) const
0171 {
0172     QList<CamItemInfo> infos;
0173 
0174     Q_FOREACH (const QModelIndex& index, indexes)
0175     {
0176         infos << camItemInfo(index);
0177     }
0178 
0179     return infos;
0180 }
0181 
0182 QList<qlonglong> ImportItemModel::camItemIds(const QList<QModelIndex>& indexes) const
0183 {
0184     QList<qlonglong> ids;
0185 
0186     Q_FOREACH (const QModelIndex& index, indexes)
0187     {
0188         ids << camItemId(index);
0189     }
0190 
0191     return ids;
0192 }
0193 
0194 CamItemInfo ImportItemModel::camItemInfo(int row) const
0195 {
0196     if ((row < 0) || (row >= d->infos.size()))
0197     {
0198         return CamItemInfo();
0199     }
0200 
0201     return d->infos.at(row);
0202 }
0203 
0204 CamItemInfo& ImportItemModel::camItemInfoRef(int row) const
0205 {
0206     if ((row < 0) || (row >= d->infos.size()))
0207     {
0208         return d->camItemInfo;
0209     }
0210 
0211     return d->infos[row];
0212 }
0213 
0214 qlonglong ImportItemModel::camItemId(int row) const
0215 {
0216     if ((row < 0) || (row >= d->infos.size()))
0217     {
0218         return -1;
0219     }
0220 
0221     return d->infos.at(row).id;
0222 }
0223 
0224 QModelIndex ImportItemModel::indexForCamItemInfo(const CamItemInfo& info) const
0225 {
0226     return indexForCamItemId(info.id);
0227 }
0228 
0229 QList<QModelIndex> ImportItemModel::indexesForCamItemInfo(const CamItemInfo& info) const
0230 {
0231     return indexesForCamItemId(info.id);
0232 }
0233 
0234 QModelIndex ImportItemModel::indexForCamItemId(qlonglong id) const
0235 {
0236     int index = d->idHash.value(id, -1);
0237 
0238     if (index == -1)
0239     {
0240         return QModelIndex();
0241     }
0242 
0243     return createIndex(index, 0);
0244 }
0245 
0246 QList<QModelIndex> ImportItemModel::indexesForCamItemId(qlonglong id) const
0247 {
0248     QList<QModelIndex> indexes;
0249 
0250     QMultiHash<qlonglong, int>::const_iterator it;
0251 
0252     for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it)
0253     {
0254        indexes << createIndex(it.value(), 0);
0255     }
0256 
0257     return indexes;
0258 }
0259 
0260 int ImportItemModel::numberOfIndexesForCamItemInfo(const CamItemInfo& info) const
0261 {
0262     return numberOfIndexesForCamItemId(info.id);
0263 }
0264 
0265 int ImportItemModel::numberOfIndexesForCamItemId(qlonglong id) const
0266 {
0267     int count = 0;
0268     QMultiHash<qlonglong,int>::const_iterator it;
0269 
0270     for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it)
0271     {
0272         ++count;
0273     }
0274 
0275     return count;
0276 }
0277 
0278 // static method
0279 CamItemInfo ImportItemModel::retrieveCamItemInfo(const QModelIndex& index)
0280 {
0281     if (!index.isValid())
0282     {
0283         return CamItemInfo();
0284     }
0285 
0286     ImportItemModel* const model = index.data(ImportItemModelPointerRole).value<ImportItemModel*>();
0287     int                    row   = index.data(ImportItemModelInternalId).toInt();
0288 
0289     if (!model)
0290     {
0291         return CamItemInfo();
0292     }
0293 
0294     return model->camItemInfo(row);
0295 }
0296 
0297 // static method
0298 qlonglong ImportItemModel::retrieveCamItemId(const QModelIndex& index)
0299 {
0300     if (!index.isValid())
0301     {
0302         return -1;
0303     }
0304 
0305     ImportItemModel* const model = index.data(ImportItemModelPointerRole).value<ImportItemModel*>();
0306     int                    row   = index.data(ImportItemModelInternalId).toInt();
0307 
0308     if (!model)
0309     {
0310         return -1;
0311     }
0312 
0313     return model->camItemId(row);
0314 }
0315 
0316 QModelIndex ImportItemModel::indexForUrl(const QUrl& fileUrl) const
0317 {
0318     if (d->keepFileUrlCache)
0319     {
0320         return indexForCamItemId(d->fileUrlHash.value(fileUrl.toLocalFile()));
0321     }
0322     else
0323     {
0324         const int size = d->infos.size();
0325 
0326         for (int i = 0 ; i < size ; ++i)
0327         {
0328             if (d->infos.at(i).url() == fileUrl)
0329             {
0330                 return createIndex(i, 0);
0331             }
0332         }
0333     }
0334 
0335     return QModelIndex();
0336 }
0337 
0338 QList<QModelIndex> ImportItemModel::indexesForUrl(const QUrl& fileUrl) const
0339 {
0340     if (d->keepFileUrlCache)
0341     {
0342         return indexesForCamItemId(d->fileUrlHash.value(fileUrl.toLocalFile()));
0343     }
0344     else
0345     {
0346         QList<QModelIndex> indexes;
0347         const int          size = d->infos.size();
0348 
0349         for (int i = 0 ; i < size ; ++i)
0350         {
0351             if (d->infos.at(i).url() == fileUrl)
0352             {
0353                 indexes << createIndex(i, 0);
0354             }
0355         }
0356 
0357         return indexes;
0358     }
0359 }
0360 
0361 CamItemInfo ImportItemModel::camItemInfo(const QUrl& fileUrl) const
0362 {
0363     if (d->keepFileUrlCache)
0364     {
0365         qlonglong id = d->fileUrlHash.value(fileUrl.toLocalFile(), -1);
0366 
0367         if (id != -1)
0368         {
0369             int index = d->idHash.value(id, -1);
0370 
0371             if (index != -1)
0372             {
0373                 return d->infos.at(index);
0374             }
0375         }
0376     }
0377     else
0378     {
0379         Q_FOREACH (const CamItemInfo& info, d->infos)
0380         {
0381             if (info.url() == fileUrl)
0382             {   // cppcheck-suppress useStlAlgorithm
0383                 return info;
0384             }
0385         }
0386     }
0387 
0388     return CamItemInfo();
0389 }
0390 
0391 QList<CamItemInfo> ImportItemModel::camItemInfos(const QUrl& fileUrl) const
0392 {
0393     QList<CamItemInfo> infos;
0394 
0395     if (d->keepFileUrlCache)
0396     {
0397         qlonglong id = d->fileUrlHash.value(fileUrl.toLocalFile(), -1);
0398 
0399         if (id != -1)
0400         {
0401             Q_FOREACH (int index, d->idHash.values(id))
0402             {
0403                 infos << d->infos.at(index);
0404             }
0405         }
0406     }
0407     else
0408     {
0409         Q_FOREACH (const CamItemInfo& info, d->infos)
0410         {
0411             if (info.url() == fileUrl)
0412             {
0413                 infos << info;
0414             }
0415         }
0416     }
0417 
0418     return infos;
0419 }
0420 
0421 void ImportItemModel::addCamItemInfo(const CamItemInfo& info)
0422 {
0423     addCamItemInfos(QList<CamItemInfo>() << info);
0424 }
0425 
0426 void ImportItemModel::addCamItemInfos(const CamItemInfoList& infos)
0427 {
0428     if (infos.isEmpty())
0429     {
0430         return;
0431     }
0432 
0433     if (d->incrementalUpdater)
0434     {
0435         d->incrementalUpdater->appendInfos(infos);
0436     }
0437     else
0438     {
0439         appendInfos(infos);
0440     }
0441 }
0442 
0443 void ImportItemModel::addCamItemInfoSynchronously(const CamItemInfo& info)
0444 {
0445     addCamItemInfosSynchronously(QList<CamItemInfo>() << info);
0446 }
0447 
0448 void ImportItemModel::addCamItemInfosSynchronously(const CamItemInfoList& infos)
0449 {
0450     if (infos.isEmpty())
0451     {
0452         return;
0453     }
0454 
0455     publiciseInfos(infos);
0456     Q_EMIT processAdded(infos);
0457 }
0458 
0459 void ImportItemModel::clearCamItemInfos()
0460 {
0461     beginResetModel();
0462 
0463     d->infos.clear();
0464     d->idHash.clear();
0465     d->fileUrlHash.clear();
0466 
0467     delete d->incrementalUpdater;
0468 
0469     d->incrementalUpdater          = nullptr;
0470     d->reAdding                    = false;
0471     d->refreshing                  = false;
0472     d->incrementalRefreshRequested = false;
0473 
0474     camItemInfosCleared();
0475     endResetModel();
0476 }
0477 
0478 // TODO unused
0479 void ImportItemModel::setCamItemInfos(const CamItemInfoList& infos)
0480 {
0481     clearCamItemInfos();
0482     addCamItemInfos(infos);
0483 }
0484 
0485 QList<CamItemInfo> ImportItemModel::camItemInfos() const
0486 {
0487     return d->infos;
0488 }
0489 
0490 QList<qlonglong> ImportItemModel::camItemIds() const
0491 {
0492     return d->idHash.keys();
0493 }
0494 
0495 QList<CamItemInfo> ImportItemModel::uniqueCamItemInfos() const
0496 {
0497     QList<CamItemInfo> uniqueInfos;
0498     const int          size = d->infos.size();
0499 
0500     for (int i = 0 ; i < size ; ++i)
0501     {
0502         const CamItemInfo& info = d->infos.at(i);
0503 
0504         if (d->idHash.value(info.id) == i)
0505         {
0506             uniqueInfos << info;
0507         }
0508     }
0509 
0510     return uniqueInfos;
0511 }
0512 
0513 bool ImportItemModel::hasImage(qlonglong id) const
0514 {
0515     return d->idHash.contains(id);
0516 }
0517 
0518 bool ImportItemModel::hasImage(const CamItemInfo& info) const
0519 {
0520     return d->fileUrlHash.contains(info.url().toLocalFile());
0521 }
0522 
0523 void ImportItemModel::emitDataChangedForAll()
0524 {
0525     if (d->infos.isEmpty())
0526     {
0527         return;
0528     }
0529 
0530     QModelIndex first = createIndex(0, 0);
0531     QModelIndex last  = createIndex(d->infos.size() - 1, 0);
0532     Q_EMIT dataChanged(first, last);
0533 }
0534 
0535 void ImportItemModel::emitDataChangedForSelections(const QItemSelection& selection)
0536 {
0537     if (!selection.isEmpty())
0538     {
0539         Q_FOREACH (const QItemSelectionRange& range, selection)
0540         {
0541             Q_EMIT dataChanged(range.topLeft(), range.bottomRight());
0542         }
0543     }
0544 }
0545 
0546 void ImportItemModel::appendInfos(const CamItemInfoList& infos)
0547 {
0548     if (infos.isEmpty())
0549     {
0550         return;
0551     }
0552 
0553     publiciseInfos(infos);
0554 }
0555 
0556 void ImportItemModel::reAddCamItemInfos(const CamItemInfoList& infos)
0557 {
0558     publiciseInfos(infos);
0559 }
0560 
0561 void ImportItemModel::reAddingFinished()
0562 {
0563     d->reAdding = false;
0564     cleanSituationChecks();
0565 }
0566 
0567 void ImportItemModel::slotFileDeleted(const QString& folder, const QString& file, bool status)
0568 {
0569     Q_UNUSED(status)
0570 
0571     QUrl url = QUrl::fromLocalFile(folder);
0572     url      = url.adjusted(QUrl::StripTrailingSlash);
0573     url.setPath(url.path() + QLatin1Char('/') + file);
0574     CamItemInfo info = camItemInfo(url);
0575     removeCamItemInfo(info);
0576 }
0577 
0578 void ImportItemModel::slotFileUploaded(const CamItemInfo& info)
0579 {
0580     addCamItemInfo(info);
0581 }
0582 
0583 void ImportItemModel::startRefresh()
0584 {
0585     d->refreshing = true;
0586 }
0587 
0588 void ImportItemModel::finishRefresh()
0589 {
0590     d->refreshing = false;
0591     cleanSituationChecks();
0592 }
0593 
0594 bool ImportItemModel::isRefreshing() const
0595 {
0596     return d->refreshing;
0597 }
0598 
0599 void ImportItemModel::cleanSituationChecks()
0600 {
0601     // For starting an incremental refresh we want a clear situation:
0602     // Any remaining batches from non-incremental refreshing subclasses have been received in appendInfos(),
0603     // any batches sent to preprocessor for re-adding have been re-added.
0604 
0605     if (d->refreshing || d->reAdding)
0606     {
0607         return;
0608     }
0609 
0610     if (d->incrementalRefreshRequested)
0611     {
0612         d->incrementalRefreshRequested = false;
0613         Q_EMIT readyForIncrementalRefresh();
0614     }
0615     else
0616     {
0617         Q_EMIT allRefreshingFinished();
0618     }
0619 }
0620 
0621 void ImportItemModel::publiciseInfos(const CamItemInfoList& infos)
0622 {
0623     if (infos.isEmpty())
0624     {
0625         return;
0626     }
0627 
0628     Q_EMIT itemInfosAboutToBeAdded(infos);
0629 
0630     const int firstNewIndex = d->infos.size();
0631     const int lastNewIndex  = d->infos.size() + infos.size() -1;
0632     beginInsertRows(QModelIndex(), firstNewIndex, lastNewIndex);
0633     d->infos << infos;
0634 
0635     for (int i = firstNewIndex ; i <= lastNewIndex ; ++i)
0636     {
0637         CamItemInfo& info = d->infos[i];
0638 
0639         // TODO move this to a separate thread, see CameraHistoryUpdater
0640         // TODO can we/do we want to differentiate at all between whether the status is unknown and not downloaded?
0641 
0642         info.downloaded   = CoreDbDownloadHistory::status(QString::fromUtf8(d->controller->cameraMD5ID()),
0643                                                           info.name, info.size, info.ctime);
0644 
0645         // TODO is this safe? if so, is there a need to store this inside idHash separately?
0646 
0647         info.id           = i;
0648         qlonglong id      = info.id;
0649         d->idHash.insert(id, i);
0650 
0651         if (d->keepFileUrlCache)
0652         {
0653             d->fileUrlHash[info.url().toLocalFile()] = id;
0654         }
0655     }
0656 
0657     endInsertRows();
0658     Q_EMIT processAdded(infos);
0659     Q_EMIT itemInfosAdded(infos);
0660 }
0661 
0662 void ImportItemModel::requestIncrementalRefresh()
0663 {
0664     if (d->reAdding)
0665     {
0666         d->incrementalRefreshRequested = true;
0667     }
0668     else
0669     {
0670         Q_EMIT readyForIncrementalRefresh();
0671     }
0672 }
0673 
0674 bool ImportItemModel::hasIncrementalRefreshPending() const
0675 {
0676     return d->incrementalRefreshRequested;
0677 }
0678 
0679 void ImportItemModel::startIncrementalRefresh()
0680 {
0681     delete d->incrementalUpdater;
0682 
0683     d->incrementalUpdater = new ImportItemModelIncrementalUpdater(d);
0684 }
0685 
0686 void ImportItemModel::finishIncrementalRefresh()
0687 {
0688     if (!d->incrementalUpdater)
0689     {
0690         return;
0691     }
0692 
0693     // remove old entries
0694 
0695     QList<QPair<int, int> > pairs = d->incrementalUpdater->oldIndexes();
0696     removeRowPairs(pairs);
0697 
0698     // add new indexes
0699 
0700     appendInfos(d->incrementalUpdater->newInfos);
0701 
0702     delete d->incrementalUpdater;
0703     d->incrementalUpdater = nullptr;
0704 }
0705 /*
0706 template <class List, typename T>
0707 static bool pairsContain(const List& list, T value)
0708 {
0709     typename List::const_iterator middle;
0710     typename List::const_iterator begin = list.begin();
0711     typename List::const_iterator end   = list.end();
0712     int n                               = int(end - begin);
0713 
0714     while (n > 0)
0715     {
0716         int half   = (n >> 1);
0717         middle = begin + half;
0718 
0719         if      ((middle->first <= value) && (middle->second >= value))
0720         {
0721             return true;
0722         }
0723         else if (middle->second < value)
0724         {
0725             begin = middle + 1;
0726             n    -= half   + 1;
0727         }
0728         else
0729         {
0730             n = half;
0731         }
0732     }
0733 
0734     return false;
0735 }
0736 */
0737 void ImportItemModel::removeIndex(const QModelIndex& index)
0738 {
0739     removeIndexs(QList<QModelIndex>() << index);
0740 }
0741 
0742 void ImportItemModel::removeIndexs(const QList<QModelIndex>& indexes)
0743 {
0744     QList<int> indexesList;
0745 
0746     Q_FOREACH (const QModelIndex& index, indexes)
0747     {
0748         if (d->isValid(index))
0749         {
0750             indexesList << index.row();
0751         }
0752     }
0753 
0754     if (indexesList.isEmpty())
0755     {
0756         return;
0757     }
0758 
0759     removeRowPairsWithCheck(ImportItemModelIncrementalUpdater::toContiguousPairs(indexesList));
0760 }
0761 
0762 void ImportItemModel::removeCamItemInfo(const CamItemInfo& info)
0763 {
0764     removeCamItemInfos(QList<CamItemInfo>() << info);
0765 }
0766 
0767 void ImportItemModel::removeCamItemInfos(const QList<CamItemInfo>& infos)
0768 {
0769     QList<int> indexesList;
0770 
0771     Q_FOREACH (const CamItemInfo& info, infos)
0772     {
0773         QModelIndex index = indexForCamItemId(info.id);
0774 
0775         if (index.isValid())
0776         {
0777             indexesList << index.row();
0778         }
0779     }
0780 
0781     removeRowPairsWithCheck(ImportItemModelIncrementalUpdater::toContiguousPairs(indexesList));
0782 }
0783 
0784 void ImportItemModel::setSendRemovalSignals(bool send)
0785 {
0786     d->sendRemovalSignals = send;
0787 }
0788 
0789 void ImportItemModel::removeRowPairsWithCheck(const QList<QPair<int, int> >& toRemove)
0790 {
0791     if (d->incrementalUpdater)
0792     {
0793         d->incrementalUpdater->aboutToBeRemovedInModel(toRemove);
0794     }
0795 
0796     removeRowPairs(toRemove);
0797 }
0798 
0799 void ImportItemModel::removeRowPairs(const QList<QPair<int, int> >& toRemove)
0800 {
0801     if (toRemove.isEmpty())
0802     {
0803         return;
0804     }
0805 
0806     // Remove old indexes
0807     // Keep in mind that when calling beginRemoveRows all structures announced to be removed
0808     // must still be valid, and this includes our hashes as well, which limits what we can optimize
0809 
0810     int              removedRows = 0;
0811     int              offset      = 0;
0812     QList<qlonglong> removeFileUrls;
0813 
0814     Q_FOREACH (const IntPair& pair, toRemove)
0815     {
0816         const int begin = pair.first  - offset;
0817         const int end   = pair.second - offset;
0818         removedRows     = end - begin + 1;
0819 
0820         // when removing from the list, all subsequent indexes are affected
0821 
0822         offset += removedRows;
0823 
0824         QList<CamItemInfo> removedInfos;
0825 
0826         if (d->sendRemovalSignals)
0827         {
0828             // cppcheck-suppress knownEmptyContainer
0829             std::copy(d->infos.begin() + begin, d->infos.begin() + end, removedInfos.begin());
0830             Q_EMIT itemInfosAboutToBeRemoved(removedInfos);
0831         }
0832 
0833         itemInfosAboutToBeRemoved(begin, end);
0834         beginRemoveRows(QModelIndex(), begin, end);
0835 
0836         // update idHash - which points to indexes of d->infos
0837 
0838         QMultiHash<qlonglong, int>::iterator it;
0839 
0840         for (it = d->idHash.begin() ; it != d->idHash.end() ; )
0841         {
0842             if (it.value() >= begin)
0843             {
0844                 if (it.value() > end)
0845                 {
0846                     // after the removed interval, adjust index
0847 
0848                     it.value() -= removedRows;
0849                 }
0850                 else
0851                 {
0852                     // in the removed interval
0853 
0854                     removeFileUrls << it.key();
0855                     it = d->idHash.erase(it);
0856                     continue;
0857                 }
0858             }
0859 
0860             ++it;
0861         }
0862 
0863         // remove from list
0864 
0865         d->infos.erase(d->infos.begin() + begin, d->infos.begin() + (end + 1));
0866 
0867         endRemoveRows();
0868 
0869         if (d->sendRemovalSignals)
0870         {
0871             Q_EMIT itemInfosRemoved(removedInfos);
0872         }
0873     }
0874 
0875     // tidy up: remove old indexes from file path hash now
0876 
0877     if (d->keepFileUrlCache)
0878     {
0879         QHash<QString, qlonglong>::iterator it;
0880 
0881         for (it = d->fileUrlHash.begin() ; it != d->fileUrlHash.end() ; )
0882         {
0883             if (removeFileUrls.contains(it.value()))
0884             {
0885                 it = d->fileUrlHash.erase(it);
0886             }
0887             else
0888             {
0889                 ++it;
0890             }
0891         }
0892     }
0893 }
0894 
0895 // ------------ ImportItemModelIncrementalUpdater ------------
0896 
0897 ImportItemModelIncrementalUpdater::ImportItemModelIncrementalUpdater(ImportItemModel::Private* const d)
0898     : oldIds(d->idHash)
0899 {
0900 }
0901 
0902 void ImportItemModelIncrementalUpdater::aboutToBeRemovedInModel(const IntPairList& toRemove)
0903 {
0904     modelRemovals << toRemove;
0905 }
0906 
0907 void ImportItemModelIncrementalUpdater::appendInfos(const QList<CamItemInfo>& infos)
0908 {
0909     for (int i = 0 ; i < infos.size() ; ++i)
0910     {
0911         const CamItemInfo& info = infos.at(i);
0912         bool found              = false;
0913         QMultiHash<qlonglong, int>::iterator it;
0914 
0915         for (it = oldIds.find(info.id) ; it != oldIds.end() ; ++it)
0916         {
0917             if (it.key() == info.id)
0918             {
0919                 found = true;
0920                 break;
0921             }
0922         }
0923 
0924         if (found)
0925         {
0926             oldIds.erase(it);
0927         }
0928         else
0929         {
0930             newInfos << info;
0931         }
0932     }
0933 }
0934 
0935 QList<QPair<int, int> > ImportItemModelIncrementalUpdater::toContiguousPairs(const QList<int>& unsorted)
0936 {
0937     // Take the given indices and return them as contiguous pairs [begin, end]
0938 
0939     QList<QPair<int, int> > pairs;
0940 
0941     if (unsorted.isEmpty())
0942     {
0943         return pairs;
0944     }
0945 
0946     QList<int> indices(unsorted);
0947     std::sort(indices.begin(), indices.end());
0948 
0949     QPair<int, int> pair(indices.first(), indices.first());
0950 
0951     for (int i = 1 ; i < indices.size() ; ++i)
0952     {
0953         const int& index = indices.at(i);
0954 
0955         if (index == (pair.second + 1))
0956         {
0957             pair.second = index;
0958             continue;
0959         }
0960 
0961         pairs << pair; // insert last pair
0962         pair.first  = index;
0963         pair.second = index;
0964     }
0965 
0966     pairs << pair;
0967 
0968     return pairs;
0969 }
0970 
0971 QList<QPair<int, int> > ImportItemModelIncrementalUpdater::oldIndexes()
0972 {
0973     // first, apply all changes to indexes by direct removal in model
0974     // while the updater was active
0975 
0976     Q_FOREACH (const IntPairList& list, modelRemovals)
0977     {
0978         int removedRows = 0;
0979         int offset      = 0;
0980 
0981         Q_FOREACH (const IntPair& pair, list)
0982         {
0983             const int begin = pair.first  - offset;
0984             const int end   = pair.second - offset; // inclusive
0985             removedRows     = end - begin + 1;
0986 
0987             // when removing from the list, all subsequent indexes are affected
0988 
0989             offset += removedRows;
0990 
0991             // update idHash - which points to indexes of d->infos, and these change now!
0992 
0993             QMultiHash<qlonglong, int>::iterator it;
0994 
0995             for (it = oldIds.begin() ; it != oldIds.end() ; )
0996             {
0997                 if (it.value() >= begin)
0998                 {
0999                     if (it.value() > end)
1000                     {
1001                         // after the removed interval: adjust index
1002 
1003                         it.value() -= removedRows;
1004                     }
1005                     else
1006                     {
1007                         // in the removed interval
1008 
1009                         it = oldIds.erase(it);
1010                         continue;
1011                     }
1012                 }
1013 
1014                 ++it;
1015             }
1016         }
1017     }
1018 
1019     modelRemovals.clear();
1020 
1021     return toContiguousPairs(oldIds.values());
1022 }
1023 
1024 // ------------ QAbstractItemModel implementation -------------
1025 
1026 QVariant ImportItemModel::data(const QModelIndex& index, int role) const
1027 {
1028     if (!d->isValid(index))
1029     {
1030         return QVariant();
1031     }
1032 
1033     switch (role)
1034     {
1035         case Qt::DisplayRole:
1036         case Qt::ToolTipRole:
1037         {
1038             return d->infos.at(index.row()).name;
1039         }
1040 
1041         case ImportItemModelPointerRole:
1042         {
1043             return QVariant::fromValue(const_cast<ImportItemModel*>(this));
1044         }
1045 
1046         case ImportItemModelInternalId:
1047         {
1048             return index.row();
1049         }
1050     }
1051 
1052     return QVariant();
1053 }
1054 
1055 QVariant ImportItemModel::headerData(int section, Qt::Orientation orientation, int role) const
1056 {
1057     Q_UNUSED(section)
1058     Q_UNUSED(orientation)
1059     Q_UNUSED(role)
1060 
1061     return QVariant();
1062 }
1063 
1064 int ImportItemModel::rowCount(const QModelIndex& parent) const
1065 {
1066     if (parent.isValid())
1067     {
1068         return 0;
1069     }
1070 
1071     return d->infos.size();
1072 }
1073 
1074 Qt::ItemFlags ImportItemModel::flags(const QModelIndex& index) const
1075 {
1076     if (!d->isValid(index))
1077     {
1078         return Qt::NoItemFlags;
1079     }
1080 
1081     Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
1082 
1083     f |= dragDropFlags(index);
1084 
1085     return f;
1086 }
1087 
1088 QModelIndex ImportItemModel::index(int row, int column, const QModelIndex& parent) const
1089 {
1090     if ((column != 0) || (row < 0) || parent.isValid() || (row >= d->infos.size()))
1091     {
1092         return QModelIndex();
1093     }
1094 
1095     return createIndex(row, 0);
1096 }
1097 
1098 } // namespace Digikam
1099 
1100 #include "moc_importimagemodel.cpp"