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"