File indexing completed on 2025-01-19 03:50:40
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2009-04-22 0007 * Description : Qt model-view for items 0008 * 0009 * SPDX-FileCopyrightText: 2009-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0010 * SPDX-FileCopyrightText: 2017 by Simon Frei <freisim93 at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "itemcategorizedview.h" 0017 0018 // Qt includes 0019 0020 #include <QApplication> 0021 #include <QTimer> 0022 0023 // Local includes 0024 0025 #include "digikam_debug.h" 0026 #include "album.h" 0027 #include "albummanager.h" 0028 #include "coredbfields.h" 0029 #include "iccsettings.h" 0030 #include "itemalbummodel.h" 0031 #include "itemalbumfiltermodel.h" 0032 #include "itemcategorydrawer.h" 0033 #include "itemdelegate.h" 0034 #include "itemdelegateoverlay.h" 0035 #include "itemthumbnailmodel.h" 0036 #include "itemselectionoverlay.h" 0037 #include "itemviewtooltip.h" 0038 #include "loadingcacheinterface.h" 0039 #include "thumbnailloadthread.h" 0040 #include "tooltipfiller.h" 0041 #include "itemfacedelegate.h" 0042 0043 namespace Digikam 0044 { 0045 0046 class Q_DECL_HIDDEN ItemCategorizedViewToolTip : public ItemViewToolTip 0047 { 0048 Q_OBJECT 0049 0050 public: 0051 0052 explicit ItemCategorizedViewToolTip(ItemCategorizedView* const view) 0053 : ItemViewToolTip(view) 0054 { 0055 } 0056 0057 ItemCategorizedView* view() const 0058 { 0059 return static_cast<ItemCategorizedView*>(ItemViewToolTip::view()); 0060 } 0061 0062 protected: 0063 0064 QString tipContents() override 0065 { 0066 ItemInfo info = ItemModel::retrieveItemInfo(currentIndex()); 0067 0068 return ToolTipFiller::imageInfoTipContents(info); 0069 } 0070 }; 0071 0072 // ------------------------------------------------------------------------------- 0073 0074 class Q_DECL_HIDDEN ItemCategorizedView::Private 0075 { 0076 public: 0077 0078 explicit Private() 0079 : model (nullptr), 0080 filterModel (nullptr), 0081 delegate (nullptr), 0082 showToolTip (false), 0083 scrollToItemId (0), 0084 delayedEnterTimer(nullptr) 0085 { 0086 } 0087 0088 ItemModel* model; 0089 ImageSortFilterModel* filterModel; 0090 0091 ItemDelegate* delegate; 0092 bool showToolTip; 0093 0094 qlonglong scrollToItemId; 0095 0096 QUrl unknownCurrentUrl; 0097 0098 QTimer* delayedEnterTimer; 0099 }; 0100 0101 // ------------------------------------------------------------------------------- 0102 0103 ItemCategorizedView::ItemCategorizedView(QWidget* const parent) 0104 : ItemViewCategorized(parent), 0105 d (new Private) 0106 { 0107 setToolTip(new ItemCategorizedViewToolTip(this)); 0108 0109 LoadingCacheInterface::connectToSignalFileChanged(this, 0110 SLOT(slotFileChanged(QString))); 0111 0112 connect(IccSettings::instance(), SIGNAL(signalICCSettingsChanged(ICCSettingsContainer,ICCSettingsContainer)), 0113 this, SLOT(slotIccSettingsChanged(ICCSettingsContainer,ICCSettingsContainer))); 0114 0115 d->delayedEnterTimer = new QTimer(this); 0116 d->delayedEnterTimer->setInterval(10); 0117 d->delayedEnterTimer->setSingleShot(true); 0118 0119 connect(d->delayedEnterTimer, SIGNAL(timeout()), 0120 this, SLOT(slotDelayedEnter())); 0121 } 0122 0123 ItemCategorizedView::~ItemCategorizedView() 0124 { 0125 d->delegate->removeAllOverlays(); 0126 delete d; 0127 } 0128 0129 void ItemCategorizedView::installDefaultModels() 0130 { 0131 ItemAlbumModel* model = new ItemAlbumModel(this); 0132 ItemAlbumFilterModel* filterModel = new ItemAlbumFilterModel(this); 0133 0134 filterModel->setSourceItemModel(model); 0135 0136 filterModel->setSortRole(ItemSortSettings::SortByFileName); 0137 filterModel->setCategorizationMode(ItemSortSettings::CategoryByAlbum); 0138 filterModel->sort(0); // an initial sorting is necessary 0139 0140 // set flags that we want to get dataChanged() signals for 0141 0142 model->setWatchFlags(filterModel->suggestedWatchFlags()); 0143 0144 setModels(model, filterModel); 0145 } 0146 0147 void ItemCategorizedView::setModels(ItemModel* model, ImageSortFilterModel* filterModel) 0148 { 0149 if (d->delegate) 0150 { 0151 d->delegate->setAllOverlaysActive(false); 0152 } 0153 0154 if (d->filterModel) 0155 { 0156 disconnect(d->filterModel, SIGNAL(layoutAboutToBeChanged()), 0157 this, SLOT(layoutAboutToBeChanged())); 0158 0159 disconnect(d->filterModel, SIGNAL(layoutChanged()), 0160 this, SLOT(layoutWasChanged())); 0161 } 0162 0163 if (d->model) 0164 { 0165 disconnect(d->model, SIGNAL(imageInfosAdded(QList<ItemInfo>)), 0166 this, SLOT(slotItemInfosAdded())); 0167 } 0168 0169 d->model = model; 0170 d->filterModel = filterModel; 0171 0172 setModel(d->filterModel); 0173 0174 connect(d->filterModel, SIGNAL(layoutAboutToBeChanged()), 0175 this, SLOT(layoutAboutToBeChanged())); 0176 0177 connect(d->filterModel, SIGNAL(layoutChanged()), 0178 this, SLOT(layoutWasChanged()), 0179 Qt::QueuedConnection); 0180 0181 connect(d->model, SIGNAL(imageInfosAdded(QList<ItemInfo>)), 0182 this, SLOT(slotItemInfosAdded())); 0183 0184 Q_EMIT modelChanged(); 0185 0186 if (d->delegate) 0187 { 0188 d->delegate->setAllOverlaysActive(true); 0189 } 0190 } 0191 0192 ItemModel* ItemCategorizedView::imageModel() const 0193 { 0194 return d->model; 0195 } 0196 0197 ImageSortFilterModel* ItemCategorizedView::imageSortFilterModel() const 0198 { 0199 return d->filterModel; 0200 } 0201 0202 ItemFilterModel* ItemCategorizedView::imageFilterModel() const 0203 { 0204 return d->filterModel->imageFilterModel(); 0205 } 0206 0207 ItemThumbnailModel* ItemCategorizedView::imageThumbnailModel() const 0208 { 0209 return qobject_cast<ItemThumbnailModel*>(d->model); 0210 } 0211 0212 ItemAlbumModel* ItemCategorizedView::imageAlbumModel() const 0213 { 0214 return qobject_cast<ItemAlbumModel*>(d->model); 0215 } 0216 0217 ItemAlbumFilterModel* ItemCategorizedView::imageAlbumFilterModel() const 0218 { 0219 return qobject_cast<ItemAlbumFilterModel*>(d->filterModel->imageFilterModel()); 0220 } 0221 0222 QSortFilterProxyModel* ItemCategorizedView::filterModel() const 0223 { 0224 return d->filterModel; 0225 } 0226 0227 ItemDelegate* ItemCategorizedView::delegate() const 0228 { 0229 return d->delegate; 0230 } 0231 0232 void ItemCategorizedView::setItemDelegate(ItemDelegate* delegate) 0233 { 0234 ThumbnailSize oldSize = thumbnailSize(); 0235 ItemDelegate* oldDelegate = d->delegate; 0236 0237 if (oldDelegate) 0238 { 0239 hideIndexNotification(); 0240 d->delegate->setAllOverlaysActive(false); 0241 d->delegate->setViewOnAllOverlays(nullptr); 0242 0243 // Note: Be precise, no wildcard disconnect! 0244 0245 disconnect(d->delegate, SIGNAL(requestNotification(QModelIndex,QString)), 0246 this, SLOT(showIndexNotification(QModelIndex,QString))); 0247 0248 disconnect(d->delegate, SIGNAL(hideNotification()), 0249 this, SLOT(hideIndexNotification())); 0250 } 0251 0252 d->delegate = delegate; 0253 d->delegate->setThumbnailSize(oldSize); 0254 0255 if (oldDelegate) 0256 { 0257 d->delegate->setSpacing(oldDelegate->spacing()); 0258 } 0259 0260 ItemViewCategorized::setItemDelegate(d->delegate); 0261 setCategoryDrawer(d->delegate->categoryDrawer()); 0262 updateDelegateSizes(); 0263 0264 d->delegate->setViewOnAllOverlays(this); 0265 d->delegate->setAllOverlaysActive(true); 0266 0267 connect(d->delegate, SIGNAL(requestNotification(QModelIndex,QString)), 0268 this, SLOT(showIndexNotification(QModelIndex,QString))); 0269 0270 connect(d->delegate, SIGNAL(hideNotification()), 0271 this, SLOT(hideIndexNotification())); 0272 } 0273 0274 Album* ItemCategorizedView::currentAlbum() const 0275 { 0276 ItemAlbumModel* const albumModel = imageAlbumModel(); 0277 0278 // TODO: Change to QList return type 0279 0280 if (albumModel && !(albumModel->currentAlbums().isEmpty())) 0281 { 0282 return albumModel->currentAlbums().constFirst(); 0283 } 0284 0285 return nullptr; 0286 } 0287 0288 ItemInfo ItemCategorizedView::currentInfo() const 0289 { 0290 return imageInfo(currentIndex()); 0291 } 0292 0293 QUrl ItemCategorizedView::currentUrl() const 0294 { 0295 return currentInfo().fileUrl(); 0296 } 0297 0298 ItemInfo ItemCategorizedView::imageInfo(const QModelIndex& index) const 0299 { 0300 return d->filterModel->imageInfo(index); 0301 } 0302 0303 ItemInfoList ItemCategorizedView::imageInfos(const QList<QModelIndex>& indexes) const 0304 { 0305 return ItemInfoList(d->filterModel->imageInfos(indexes)); 0306 } 0307 0308 ItemInfoList ItemCategorizedView::allItemInfos() const 0309 { 0310 return ItemInfoList(d->filterModel->imageInfosSorted()); 0311 } 0312 0313 QList<QUrl> ItemCategorizedView::allUrls() const 0314 { 0315 return allItemInfos().toImageUrlList(); 0316 } 0317 0318 ItemInfoList ItemCategorizedView::selectedItemInfos() const 0319 { 0320 return imageInfos(selectedIndexes()); 0321 } 0322 0323 ItemInfoList ItemCategorizedView::selectedItemInfosCurrentFirst() const 0324 { 0325 QModelIndexList indexes = selectedIndexes(); 0326 const QModelIndex current = currentIndex(); 0327 0328 if (!indexes.isEmpty()) 0329 { 0330 if (indexes.first() != current) 0331 { 0332 if (indexes.removeOne(current)) 0333 { 0334 indexes.prepend(current); 0335 } 0336 } 0337 } 0338 0339 return imageInfos(indexes); 0340 } 0341 0342 void ItemCategorizedView::toIndex(const QUrl& url) 0343 { 0344 ItemViewCategorized::toIndex(d->filterModel->indexForPath(url.toLocalFile())); 0345 } 0346 0347 QModelIndex ItemCategorizedView::indexForInfo(const ItemInfo& info) const 0348 { 0349 return d->filterModel->indexForItemInfo(info); 0350 } 0351 0352 ItemInfo ItemCategorizedView::nextInOrder(const ItemInfo& startingPoint, int nth) 0353 { 0354 QModelIndex index = d->filterModel->indexForItemInfo(startingPoint); 0355 0356 if (!index.isValid()) 0357 { 0358 return ItemInfo(); 0359 } 0360 0361 return imageInfo(d->filterModel->index(index.row() + nth, 0, QModelIndex())); 0362 } 0363 0364 QModelIndex ItemCategorizedView::nextIndexHint(const QModelIndex& anchor, const QItemSelectionRange& removed) const 0365 { 0366 QModelIndex hint = ItemViewCategorized::nextIndexHint(anchor, removed); 0367 ItemInfo info = imageInfo(anchor); 0368 /* 0369 qCDebug(DIGIKAM_GENERAL_LOG) << "Having initial hint" << hint << "for" << anchor << d->model->numberOfIndexesForItemInfo(info); 0370 */ 0371 // Fixes a special case of multiple (face) entries for the same image. 0372 // If one is removed, any entry of the same image shall be preferred. 0373 0374 if (d->model->numberOfIndexesForItemInfo(info) > 1) 0375 { 0376 // The hint is for a different info, but we may have a hint for the same info 0377 0378 if (info != imageInfo(hint)) 0379 { 0380 int minDiff = d->filterModel->rowCount(); 0381 QList<QModelIndex> indexesForItemInfo = d->filterModel->mapListFromSource(d->model->indexesForItemInfo(info)); 0382 0383 Q_FOREACH (const QModelIndex& index, indexesForItemInfo) 0384 { 0385 if ((index == anchor) || !index.isValid() || removed.contains(index)) 0386 { 0387 continue; 0388 } 0389 0390 int distance = qAbs(index.row() - anchor.row()); 0391 0392 if (distance < minDiff) 0393 { 0394 minDiff = distance; 0395 hint = index; 0396 0397 /* qCDebug(DIGIKAM_GENERAL_LOG) << "Chose index" << hint << "at distance" << minDiff << "to" << anchor; 0398 0399 */ } 0400 } 0401 } 0402 } 0403 0404 return hint; 0405 } 0406 0407 void ItemCategorizedView::openAlbum(const QList<Album*>& albums) 0408 { 0409 ItemAlbumModel* const albumModel = imageAlbumModel(); 0410 0411 if (albumModel) 0412 { 0413 albumModel->openAlbum(albums); 0414 } 0415 } 0416 0417 ThumbnailSize ItemCategorizedView::thumbnailSize() const 0418 { 0419 /* 0420 ItemThumbnailModel* const thumbModel = imageThumbnailModel(); 0421 0422 if (thumbModel) 0423 { 0424 return thumbModel->thumbnailSize(); 0425 } 0426 */ 0427 if (d->delegate) 0428 { 0429 return d->delegate->thumbnailSize(); 0430 } 0431 0432 return ThumbnailSize(); 0433 } 0434 0435 void ItemCategorizedView::setThumbnailSize(int size) 0436 { 0437 setThumbnailSize(ThumbnailSize(size)); 0438 } 0439 0440 void ItemCategorizedView::setThumbnailSize(const ThumbnailSize& s) 0441 { 0442 // we abuse this pair of method calls to restore scroll position 0443 0444 layoutAboutToBeChanged(); 0445 d->delegate->setThumbnailSize(s); 0446 layoutWasChanged(); 0447 } 0448 0449 void ItemCategorizedView::setCurrentWhenAvailable(qlonglong imageId) 0450 { 0451 d->scrollToItemId = imageId; 0452 } 0453 0454 void ItemCategorizedView::setCurrentUrlWhenAvailable(const QUrl& url) 0455 { 0456 if (url.isEmpty()) 0457 { 0458 clearSelection(); 0459 setCurrentIndex(QModelIndex()); 0460 return; 0461 } 0462 0463 QString path = url.toLocalFile(); 0464 QModelIndex index = d->filterModel->indexForPath(path); 0465 0466 if (!index.isValid()) 0467 { 0468 d->unknownCurrentUrl = url; 0469 return; 0470 } 0471 0472 clearSelection(); 0473 setCurrentIndex(index); 0474 d->unknownCurrentUrl.clear(); 0475 } 0476 0477 void ItemCategorizedView::setCurrentUrl(const QUrl& url) 0478 { 0479 if (url.isEmpty()) 0480 { 0481 clearSelection(); 0482 setCurrentIndex(QModelIndex()); 0483 return; 0484 } 0485 0486 QString path = url.toLocalFile(); 0487 QModelIndex index = d->filterModel->indexForPath(path); 0488 0489 if (!index.isValid()) 0490 { 0491 return; 0492 } 0493 0494 clearSelection(); 0495 setCurrentIndex(index); 0496 } 0497 0498 void ItemCategorizedView::setCurrentInfo(const ItemInfo& info) 0499 { 0500 QModelIndex index = d->filterModel->indexForItemInfo(info); 0501 clearSelection(); 0502 setCurrentIndex(index); 0503 } 0504 0505 void ItemCategorizedView::setSelectedUrls(const QList<QUrl>& urlList) 0506 { 0507 QItemSelection mySelection; 0508 0509 for (QList<QUrl>::const_iterator it = urlList.constBegin() ; it!=urlList.constEnd() ; ++it) 0510 { 0511 const QString path = it->toLocalFile(); 0512 const QModelIndex index = d->filterModel->indexForPath(path); 0513 0514 if (!index.isValid()) 0515 { 0516 qCWarning(DIGIKAM_GENERAL_LOG) << "no QModelIndex found for" << *it; 0517 } 0518 else 0519 { 0520 // TODO: is there a better way? 0521 0522 mySelection.select(index, index); 0523 } 0524 } 0525 0526 clearSelection(); 0527 selectionModel()->select(mySelection, QItemSelectionModel::Select); 0528 } 0529 0530 void ItemCategorizedView::setSelectedItemInfos(const QList<ItemInfo>& infos) 0531 { 0532 QItemSelection mySelection; 0533 0534 Q_FOREACH (const ItemInfo& info, infos) 0535 { 0536 QModelIndex index = d->filterModel->indexForItemInfo(info); 0537 mySelection.select(index, index); 0538 } 0539 0540 selectionModel()->select(mySelection, QItemSelectionModel::ClearAndSelect); 0541 } 0542 0543 void ItemCategorizedView::hintAt(const ItemInfo& info) 0544 { 0545 if (info.isNull()) 0546 { 0547 return; 0548 } 0549 0550 QModelIndex index = d->filterModel->indexForItemInfo(info); 0551 0552 if (!index.isValid()) 0553 { 0554 return; 0555 } 0556 0557 selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); 0558 scrollTo(index); 0559 } 0560 0561 void ItemCategorizedView::addOverlay(ItemDelegateOverlay* overlay, ItemDelegate* delegate) 0562 { 0563 if (!delegate) 0564 { 0565 delegate = d->delegate; 0566 } 0567 0568 delegate->installOverlay(overlay); 0569 0570 if (delegate == d->delegate) 0571 { 0572 overlay->setView(this); 0573 overlay->setActive(true); 0574 } 0575 } 0576 0577 void ItemCategorizedView::removeOverlay(ItemDelegateOverlay* overlay) 0578 { 0579 ItemDelegate* const delegate = dynamic_cast<ItemDelegate*>(overlay->delegate()); 0580 0581 if (delegate) 0582 { 0583 delegate->removeOverlay(overlay); 0584 } 0585 0586 overlay->setView(nullptr); 0587 } 0588 0589 void ItemCategorizedView::updateGeometries() 0590 { 0591 ItemViewCategorized::updateGeometries(); 0592 d->delayedEnterTimer->start(); 0593 } 0594 0595 void ItemCategorizedView::slotDelayedEnter() 0596 { 0597 // re-Q_EMIT entered() for index under mouse (after layout). 0598 0599 QModelIndex mouseIndex = indexAt(mapFromGlobal(QCursor::pos())); 0600 0601 if (mouseIndex.isValid()) 0602 { 0603 Q_EMIT DCategorizedView::entered(mouseIndex); 0604 } 0605 } 0606 0607 void ItemCategorizedView::addSelectionOverlay(ItemDelegate* delegate) 0608 { 0609 addOverlay(new ItemSelectionOverlay(this), delegate); 0610 } 0611 0612 void ItemCategorizedView::scrollToStoredItem() 0613 { 0614 if (d->scrollToItemId) 0615 { 0616 if (d->model->hasImage(d->scrollToItemId)) 0617 { 0618 QModelIndex index = d->filterModel->indexForImageId(d->scrollToItemId); 0619 setCurrentIndex(index); 0620 scrollToRelaxed(index, QAbstractItemView::PositionAtCenter); 0621 d->scrollToItemId = 0; 0622 } 0623 } 0624 } 0625 0626 void ItemCategorizedView::slotItemInfosAdded() 0627 { 0628 if (d->scrollToItemId) 0629 { 0630 scrollToStoredItem(); 0631 } 0632 else if (!d->unknownCurrentUrl.isEmpty()) 0633 { 0634 QTimer::singleShot(100, this, SLOT(slotCurrentUrlTimer())); 0635 } 0636 } 0637 0638 void ItemCategorizedView::slotCurrentUrlTimer() 0639 { 0640 setCurrentUrl(d->unknownCurrentUrl); 0641 d->unknownCurrentUrl.clear(); 0642 } 0643 0644 void ItemCategorizedView::slotFileChanged(const QString& filePath) 0645 { 0646 QModelIndex index = d->filterModel->indexForPath(filePath); 0647 0648 if (index.isValid()) 0649 { 0650 update(index); 0651 } 0652 } 0653 0654 void ItemCategorizedView::indexActivated(const QModelIndex& index, Qt::KeyboardModifiers modifiers) 0655 { 0656 ItemInfo info = imageInfo(index); 0657 0658 if (!info.isNull()) 0659 { 0660 activated(info, modifiers); 0661 Q_EMIT imageActivated(info); 0662 } 0663 } 0664 0665 void ItemCategorizedView::currentChanged(const QModelIndex& index, const QModelIndex& previous) 0666 { 0667 ItemViewCategorized::currentChanged(index, previous); 0668 0669 Q_EMIT currentChanged(imageInfo(index)); 0670 } 0671 0672 void ItemCategorizedView::selectionChanged(const QItemSelection& selectedItems, const QItemSelection& deselectedItems) 0673 { 0674 ItemViewCategorized::selectionChanged(selectedItems, deselectedItems); 0675 0676 if (!selectedItems.isEmpty()) 0677 { 0678 Q_EMIT selected(imageInfos(selectedItems.indexes())); 0679 } 0680 0681 if (!deselectedItems.isEmpty()) 0682 { 0683 Q_EMIT deselected(imageInfos(deselectedItems.indexes())); 0684 } 0685 } 0686 0687 Album* ItemCategorizedView::albumAt(const QPoint& pos) const 0688 { 0689 if (imageFilterModel()->imageSortSettings().categorizationMode == ItemSortSettings::CategoryByAlbum) 0690 { 0691 QModelIndex categoryIndex = indexForCategoryAt(pos); 0692 0693 if (categoryIndex.isValid()) 0694 { 0695 int albumId = categoryIndex.data(ItemFilterModel::CategoryAlbumIdRole).toInt(); 0696 0697 return AlbumManager::instance()->findPAlbum(albumId); 0698 } 0699 } 0700 0701 return currentAlbum(); 0702 } 0703 0704 void ItemCategorizedView::activated(const ItemInfo&, Qt::KeyboardModifiers) 0705 { 0706 // implemented in subclass 0707 } 0708 0709 void ItemCategorizedView::showContextMenuOnIndex(QContextMenuEvent* event, const QModelIndex& index) 0710 { 0711 showContextMenuOnInfo(event, imageInfo(index)); 0712 } 0713 0714 void ItemCategorizedView::showContextMenuOnInfo(QContextMenuEvent*, const ItemInfo&) 0715 { 0716 // implemented in subclass 0717 } 0718 0719 QItemSelectionModel* ItemCategorizedView::getSelectionModel() const 0720 { 0721 return selectionModel(); 0722 } 0723 0724 AbstractItemDragDropHandler* ItemCategorizedView::dragDropHandler() const 0725 { 0726 return d->model->dragDropHandler(); 0727 } 0728 0729 void ItemCategorizedView::slotIccSettingsChanged(const ICCSettingsContainer&, const ICCSettingsContainer&) 0730 { 0731 viewport()->update(); 0732 } 0733 0734 } // namespace Digikam 0735 0736 #include "itemcategorizedview.moc" 0737 0738 #include "moc_itemcategorizedview.cpp"