File indexing completed on 2025-03-09 03:58:42

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2012-07-13
0007  * Description : Qt categorized item view for camera items
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 "importcategorizedview.h"
0016 
0017 // Qt includes
0018 
0019 #include <QTimer>
0020 
0021 // Local includes
0022 
0023 #include "digikam_debug.h"
0024 #include "camitemsortsettings.h"
0025 #include "iccsettings.h"
0026 #include "itemselectionoverlay.h"
0027 #include "importdelegate.h"
0028 #include "importtooltipfiller.h"
0029 #include "importsettings.h"
0030 #include "itemviewtooltip.h"
0031 #include "loadingcacheinterface.h"
0032 #include "thumbnailloadthread.h"
0033 
0034 namespace Digikam
0035 {
0036 
0037 class Q_DECL_HIDDEN ImportItemViewToolTip : public ItemViewToolTip
0038 {
0039     Q_OBJECT
0040 
0041 public:
0042 
0043     explicit ImportItemViewToolTip(ImportCategorizedView* const view)
0044         : ItemViewToolTip(view)
0045     {
0046     }
0047 
0048     ImportCategorizedView* view() const
0049     {
0050         return static_cast<ImportCategorizedView*>(ItemViewToolTip::view());
0051     }
0052 
0053 protected:
0054 
0055     QString tipContents() override
0056     {
0057         CamItemInfo info = ImportItemModel::retrieveCamItemInfo(currentIndex());
0058 
0059         return ImportToolTipFiller::CamItemInfoTipContents(info);
0060     }
0061 };
0062 
0063 class Q_DECL_HIDDEN ImportCategorizedView::Private
0064 {
0065 public:
0066 
0067     explicit Private()
0068       : model            (nullptr),
0069         filterModel      (nullptr),
0070         delegate         (nullptr),
0071         showToolTip      (false),
0072         scrollToItemId   (0),
0073         delayedEnterTimer(nullptr),
0074         currentMouseEvent(nullptr)
0075     {
0076     }
0077 
0078     ImportItemModel*       model;
0079     ImportSortFilterModel* filterModel;
0080 
0081     ImportDelegate*        delegate;
0082     bool                   showToolTip;
0083 
0084     qlonglong              scrollToItemId;
0085 
0086     QTimer*                delayedEnterTimer;
0087 
0088     QMouseEvent*           currentMouseEvent;
0089 };
0090 
0091 ImportCategorizedView::ImportCategorizedView(QWidget* const parent)
0092     : ItemViewCategorized(parent),
0093       d                  (new Private)
0094 {
0095     setToolTip(new ImportItemViewToolTip(this));
0096 
0097     LoadingCacheInterface::connectToSignalFileChanged(this,
0098             SLOT(slotFileChanged(QString)));
0099 
0100     d->delayedEnterTimer = new QTimer(this);
0101     d->delayedEnterTimer->setInterval(10);
0102     d->delayedEnterTimer->setSingleShot(true);
0103 
0104     connect(d->delayedEnterTimer, SIGNAL(timeout()),
0105             this, SLOT(slotDelayedEnter()));
0106 
0107     connect(IccSettings::instance(), SIGNAL(signalICCSettingsChanged(ICCSettingsContainer,ICCSettingsContainer)),
0108             this, SLOT(slotIccSettingsChanged(ICCSettingsContainer,ICCSettingsContainer)));
0109 }
0110 
0111 ImportCategorizedView::~ImportCategorizedView()
0112 {
0113     d->delegate->removeAllOverlays();
0114     delete d;
0115 }
0116 
0117 void ImportCategorizedView::setModels(ImportItemModel* model, ImportSortFilterModel* filterModel)
0118 {
0119     if (d->delegate)
0120     {
0121         d->delegate->setAllOverlaysActive(false);
0122     }
0123 
0124     if (d->filterModel)
0125     {
0126         disconnect(d->filterModel, SIGNAL(layoutAboutToBeChanged()),
0127                    this, SLOT(layoutAboutToBeChanged()));
0128 
0129         disconnect(d->filterModel, SIGNAL(layoutChanged()),
0130                    this, SLOT(layoutWasChanged()));
0131     }
0132 
0133     if (d->model)
0134     {
0135         disconnect(d->model, SIGNAL(itemInfosAdded(QList<CamItemInfo>)),
0136                    this, SLOT(slotCamItemInfosAdded()));
0137     }
0138 
0139     d->model       = model;
0140     d->filterModel = filterModel;
0141 
0142     setModel(d->filterModel);
0143 
0144     connect(d->filterModel, SIGNAL(layoutAboutToBeChanged()),
0145             this, SLOT(layoutAboutToBeChanged()));
0146 
0147     connect(d->filterModel, SIGNAL(layoutChanged()),
0148             this, SLOT(layoutWasChanged()),
0149             Qt::QueuedConnection);
0150 
0151     connect(d->model, SIGNAL(itemInfosAdded(QList<CamItemInfo>)),
0152             this, SLOT(slotCamItemInfosAdded()));
0153 
0154     Q_EMIT modelChanged();
0155 
0156     if (d->delegate)
0157     {
0158         d->delegate->setAllOverlaysActive(true);
0159     }
0160 }
0161 
0162 ImportItemModel* ImportCategorizedView::importItemModel() const
0163 {
0164     return d->model;
0165 }
0166 
0167 ImportSortFilterModel* ImportCategorizedView::importSortFilterModel() const
0168 {
0169     return d->filterModel;
0170 }
0171 
0172 ImportFilterModel* ImportCategorizedView::importFilterModel() const
0173 {
0174     return d->filterModel->importFilterModel();
0175 }
0176 
0177 ImportThumbnailModel* ImportCategorizedView::importThumbnailModel() const
0178 {
0179     return qobject_cast<ImportThumbnailModel*>(d->model);
0180 }
0181 
0182 QSortFilterProxyModel* ImportCategorizedView::filterModel() const
0183 {
0184     return d->filterModel;
0185 }
0186 
0187 ImportDelegate* ImportCategorizedView::delegate() const
0188 {
0189     return d->delegate;
0190 }
0191 
0192 void ImportCategorizedView::setItemDelegate(ImportDelegate* delegate)
0193 {
0194     ThumbnailSize oldSize       = thumbnailSize();
0195     ImportDelegate* oldDelegate = d->delegate;
0196 
0197     if (oldDelegate)
0198     {
0199         hideIndexNotification();
0200         d->delegate->setAllOverlaysActive(false);
0201         d->delegate->setViewOnAllOverlays(nullptr);
0202 
0203         // Note: Be precise, no wildcard disconnect!
0204 
0205         disconnect(d->delegate, SIGNAL(requestNotification(QModelIndex,QString)),
0206                    this, SLOT(showIndexNotification(QModelIndex,QString)));
0207 
0208         disconnect(d->delegate, SIGNAL(hideNotification()),
0209                    this, SLOT(hideIndexNotification()));
0210     }
0211 
0212     d->delegate = delegate;
0213     d->delegate->setThumbnailSize(oldSize);
0214 
0215     if (oldDelegate)
0216     {
0217         d->delegate->setSpacing(oldDelegate->spacing());
0218     }
0219 
0220     ItemViewCategorized::setItemDelegate(d->delegate);
0221     setCategoryDrawer(d->delegate->categoryDrawer());
0222     updateDelegateSizes();
0223 
0224     d->delegate->setViewOnAllOverlays(this);
0225     d->delegate->setAllOverlaysActive(true);
0226 
0227     connect(d->delegate, SIGNAL(requestNotification(QModelIndex,QString)),
0228             this, SLOT(showIndexNotification(QModelIndex,QString)));
0229 
0230     connect(d->delegate, SIGNAL(hideNotification()),
0231             this, SLOT(hideIndexNotification()));
0232 }
0233 
0234 CamItemInfo ImportCategorizedView::currentInfo() const
0235 {
0236     return d->filterModel->camItemInfo(currentIndex());
0237 }
0238 
0239 QUrl ImportCategorizedView::currentUrl() const
0240 {
0241     return currentInfo().url();
0242 }
0243 
0244 QList<CamItemInfo> ImportCategorizedView::selectedCamItemInfos() const
0245 {
0246     return d->filterModel->camItemInfos(selectedIndexes());
0247 }
0248 
0249 QList<CamItemInfo> ImportCategorizedView::selectedCamItemInfosCurrentFirst() const
0250 {
0251     QList<QModelIndex> indexes = selectedIndexes();
0252     QModelIndex        current = currentIndex();
0253     QList<CamItemInfo> infos;
0254 
0255     Q_FOREACH (const QModelIndex& index, indexes)
0256     {
0257         CamItemInfo info = d->filterModel->camItemInfo(index);
0258 
0259         if (index == current)
0260         {
0261             infos.prepend(info);
0262         }
0263         else
0264         {
0265             infos.append(info);
0266         }
0267     }
0268 
0269     return infos;
0270 }
0271 
0272 QList<CamItemInfo> ImportCategorizedView::camItemInfos() const
0273 {
0274     return d->filterModel->camItemInfosSorted();
0275 }
0276 
0277 QList<QUrl> ImportCategorizedView::urls() const
0278 {
0279     QList<CamItemInfo> infos = camItemInfos();
0280     QList<QUrl>        urls;
0281 
0282     Q_FOREACH (const CamItemInfo& info, infos)
0283     {
0284         urls << info.url();
0285     }
0286 
0287     return urls;
0288 }
0289 
0290 QList<QUrl> ImportCategorizedView::selectedUrls() const
0291 {
0292     QList<CamItemInfo> infos = selectedCamItemInfos();
0293     QList<QUrl>        urls;
0294 
0295     Q_FOREACH (const CamItemInfo& info, infos)
0296     {
0297         urls << info.url();
0298     }
0299 
0300     return urls;
0301 }
0302 
0303 void ImportCategorizedView::toIndex(const QUrl& url)
0304 {
0305     ItemViewCategorized::toIndex(d->filterModel->indexForPath(url.toLocalFile()));
0306 }
0307 
0308 CamItemInfo ImportCategorizedView::nextInOrder(const CamItemInfo& startingPoint, int nth)
0309 {
0310     QModelIndex index = d->filterModel->indexForCamItemInfo(startingPoint);
0311 
0312     if (!index.isValid())
0313     {
0314         return CamItemInfo();
0315     }
0316 
0317     return d->filterModel->camItemInfo(d->filterModel->index(index.row() + nth, 0, QModelIndex()));
0318 }
0319 
0320 QModelIndex ImportCategorizedView::nextIndexHint(const QModelIndex& anchor, const QItemSelectionRange& removed) const
0321 {
0322     QModelIndex hint = ItemViewCategorized::nextIndexHint(anchor, removed);
0323     CamItemInfo info = d->filterModel->camItemInfo(anchor);
0324 /*
0325     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Having initial hint" << hint << "for" << anchor << d->model->numberOfIndexesForCamItemInfo(info);
0326 */
0327     // Fixes a special case of multiple (face) entries for the same image.
0328     // If one is removed, any entry of the same image shall be preferred.
0329 
0330     if (d->model->numberOfIndexesForCamItemInfo(info) > 1)
0331     {
0332         // The hint is for a different info, but we may have a hint for the same info
0333 
0334         if (info != d->filterModel->camItemInfo(hint))
0335         {
0336             int minDiff                              = d->filterModel->rowCount();
0337             QList<QModelIndex> indexesForCamItemInfo = d->filterModel->mapListFromSource(d->model->indexesForCamItemInfo(info));
0338 
0339             Q_FOREACH (const QModelIndex& index, indexesForCamItemInfo)
0340             {
0341                 if ((index == anchor) || !index.isValid() || removed.contains(index))
0342                 {
0343                     continue;
0344                 }
0345 
0346                 int distance = qAbs(index.row() - anchor.row());
0347 
0348                 if (distance < minDiff)
0349                 {
0350                     minDiff = distance;
0351                     hint    = index;
0352 /*
0353                     qCDebug(DIGIKAM_IMPORTUI_LOG) << "Chose index" << hint << "at distance" << minDiff << "to" << anchor;
0354 */
0355                 }
0356             }
0357         }
0358     }
0359 
0360     return hint;
0361 }
0362 
0363 ThumbnailSize ImportCategorizedView::thumbnailSize() const
0364 {
0365 /*
0366     ImportThumbnailModel *thumbModel = importThumbnailModel();
0367 
0368     if (thumbModel)
0369     {
0370         return thumbModel->thumbnailSize();
0371     }
0372 */
0373     if (d->delegate)
0374     {
0375         return d->delegate->thumbnailSize();
0376     }
0377 
0378     return ThumbnailSize();
0379 }
0380 
0381 void ImportCategorizedView::setThumbnailSize(int size)
0382 {
0383     setThumbnailSize(ThumbnailSize(size));
0384 }
0385 
0386 void ImportCategorizedView::setThumbnailSize(const ThumbnailSize& s)
0387 {
0388     // we abuse this pair of method calls to restore scroll position
0389     // TODO check if needed
0390 
0391     layoutAboutToBeChanged();
0392     d->delegate->setThumbnailSize(s);
0393     layoutWasChanged();
0394 }
0395 
0396 void ImportCategorizedView::setCurrentWhenAvailable(qlonglong camItemId)
0397 {
0398     d->scrollToItemId = camItemId;
0399 }
0400 
0401 void ImportCategorizedView::setCurrentUrl(const QUrl& url)
0402 {
0403     if (url.isEmpty())
0404     {
0405         clearSelection();
0406         setCurrentIndex(QModelIndex());
0407         return;
0408     }
0409 
0410     QString path      = url.toLocalFile();
0411     QModelIndex index = d->filterModel->indexForPath(path);
0412 
0413     if (!index.isValid())
0414     {
0415         return;
0416     }
0417 
0418     clearSelection();
0419     setCurrentIndex(index);
0420 }
0421 
0422 void ImportCategorizedView::setCurrentInfo(const CamItemInfo& info)
0423 {
0424     QModelIndex index = d->filterModel->indexForCamItemInfo(info);
0425     clearSelection();
0426     setCurrentIndex(index);
0427 }
0428 
0429 void ImportCategorizedView::setSelectedUrls(const QList<QUrl>& urlList)
0430 {
0431     QItemSelection mySelection;
0432 
0433     for (QList<QUrl>::const_iterator it = urlList.constBegin() ; it!=urlList.constEnd() ; ++it)
0434     {
0435         const QString path      = it->toLocalFile();
0436         const QModelIndex index = d->filterModel->indexForPath(path);
0437 
0438         if (!index.isValid())
0439         {
0440             qCWarning(DIGIKAM_IMPORTUI_LOG) << "no QModelIndex found for" << *it;
0441         }
0442         else
0443         {
0444             // TODO: is there a better way?
0445 
0446             mySelection.select(index, index);
0447         }
0448     }
0449 
0450     clearSelection();
0451     selectionModel()->select(mySelection, QItemSelectionModel::Select);
0452 }
0453 
0454 void ImportCategorizedView::setSelectedCamItemInfos(const QList<CamItemInfo>& infos)
0455 {
0456     QItemSelection mySelection;
0457 
0458     Q_FOREACH (const CamItemInfo& info, infos)
0459     {
0460         QModelIndex index = d->filterModel->indexForCamItemInfo(info);
0461         mySelection.select(index, index);
0462     }
0463 
0464     selectionModel()->select(mySelection, QItemSelectionModel::ClearAndSelect);
0465 }
0466 
0467 void ImportCategorizedView::hintAt(const CamItemInfo& info)
0468 {
0469     if (info.isNull())
0470     {
0471         return;
0472     }
0473 
0474     QModelIndex index = d->filterModel->indexForCamItemInfo(info);
0475 
0476     if (!index.isValid())
0477     {
0478         return;
0479     }
0480 
0481     selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
0482     scrollTo(index);
0483 }
0484 
0485 void ImportCategorizedView::addOverlay(ItemDelegateOverlay* overlay, ImportDelegate* delegate)
0486 {
0487     if (!delegate)
0488     {
0489         delegate = d->delegate;
0490     }
0491 
0492     delegate->installOverlay(overlay);
0493 
0494     if (delegate == d->delegate)
0495     {
0496         overlay->setView(this);
0497         overlay->setActive(true);
0498     }
0499 }
0500 
0501 void ImportCategorizedView::removeOverlay(ItemDelegateOverlay* overlay)
0502 {
0503     ImportDelegate* delegate = dynamic_cast<ImportDelegate*>(overlay->delegate());
0504 
0505     if (delegate)
0506     {
0507         delegate->removeOverlay(overlay);
0508     }
0509 
0510     overlay->setView(nullptr);
0511 }
0512 
0513 void ImportCategorizedView::updateGeometries()
0514 {
0515     ItemViewCategorized::updateGeometries();
0516     d->delayedEnterTimer->start();
0517 }
0518 
0519 void ImportCategorizedView::slotDelayedEnter()
0520 {
0521     // re-Q_EMIT entered() for index under mouse (after layout).
0522 
0523     QModelIndex mouseIndex = indexAt(mapFromGlobal(QCursor::pos()));
0524 
0525     if (mouseIndex.isValid())
0526     {
0527         Q_EMIT DCategorizedView::entered(mouseIndex);
0528     }
0529 }
0530 
0531 void ImportCategorizedView::addSelectionOverlay(ImportDelegate* delegate)
0532 {
0533     addOverlay(new ItemSelectionOverlay(this), delegate);
0534 }
0535 
0536 void ImportCategorizedView::scrollToStoredItem()
0537 {
0538     if (d->scrollToItemId)
0539     {
0540         if (d->model->hasImage(d->scrollToItemId))
0541         {
0542             QModelIndex index = d->filterModel->indexForCamItemId(d->scrollToItemId);
0543             setCurrentIndex(index);
0544             scrollToRelaxed(index, QAbstractItemView::PositionAtCenter);
0545             d->scrollToItemId = 0;
0546         }
0547     }
0548 }
0549 
0550 void ImportCategorizedView::slotCamItemInfosAdded()
0551 {
0552     if (d->scrollToItemId)
0553     {
0554         scrollToStoredItem();
0555     }
0556 }
0557 
0558 void ImportCategorizedView::slotFileChanged(const QString& filePath)
0559 {
0560     QModelIndex index = d->filterModel->indexForPath(filePath);
0561 
0562     if (index.isValid())
0563     {
0564         update(index);
0565     }
0566 }
0567 
0568 void ImportCategorizedView::indexActivated(const QModelIndex& index, Qt::KeyboardModifiers modifiers)
0569 {
0570     CamItemInfo info = d->filterModel->camItemInfo(index);
0571 
0572     if (!info.isNull())
0573     {
0574         activated(info, modifiers);
0575         Q_EMIT camItemInfoActivated(info);
0576     }
0577 }
0578 
0579 void ImportCategorizedView::currentChanged(const QModelIndex& index, const QModelIndex& previous)
0580 {
0581     ItemViewCategorized::currentChanged(index, previous);
0582 
0583     Q_EMIT currentChanged(d->filterModel->camItemInfo(index));
0584 }
0585 
0586 void ImportCategorizedView::selectionChanged(const QItemSelection& selectedItems, const QItemSelection& deselectedItems)
0587 {
0588     ItemViewCategorized::selectionChanged(selectedItems, deselectedItems);
0589 
0590     if (!selectedItems.isEmpty())
0591     {
0592         Q_EMIT selected(d->filterModel->camItemInfos(selectedItems.indexes()));
0593     }
0594 
0595     if (!deselectedItems.isEmpty())
0596     {
0597         Q_EMIT deselected(d->filterModel->camItemInfos(deselectedItems.indexes()));
0598     }
0599 }
0600 
0601 void ImportCategorizedView::activated(const CamItemInfo&, Qt::KeyboardModifiers)
0602 {
0603     // implemented in subclass
0604 }
0605 
0606 void ImportCategorizedView::showContextMenuOnIndex(QContextMenuEvent* event, const QModelIndex& index)
0607 {
0608     CamItemInfo info = d->filterModel->camItemInfo(index);
0609     showContextMenuOnInfo(event, info);
0610 }
0611 
0612 void ImportCategorizedView::showContextMenuOnInfo(QContextMenuEvent*, const CamItemInfo&)
0613 {
0614     // implemented in subclass
0615 }
0616 
0617 void ImportCategorizedView::paintEvent(QPaintEvent* e)
0618 {
0619     ItemViewCategorized::paintEvent(e);
0620 }
0621 
0622 QItemSelectionModel* ImportCategorizedView::getSelectionModel() const
0623 {
0624     return selectionModel();
0625 }
0626 
0627 AbstractItemDragDropHandler* ImportCategorizedView::dragDropHandler() const
0628 {
0629     return d->model->dragDropHandler();
0630 }
0631 
0632 void ImportCategorizedView::slotIccSettingsChanged(const ICCSettingsContainer&, const ICCSettingsContainer&)
0633 {
0634     viewport()->update();
0635 }
0636 
0637 } // namespace Digikam
0638 
0639 #include "importcategorizedview.moc"
0640 
0641 #include "moc_importcategorizedview.cpp"