File indexing completed on 2025-03-09 03:58:45
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2007-04-11 0007 * Description : light table thumbs bar 0008 * 0009 * SPDX-FileCopyrightText: 2007-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * 0011 * SPDX-License-Identifier: GPL-2.0-or-later 0012 * 0013 * ============================================================ */ 0014 0015 #include "lighttablethumbbar.h" 0016 0017 // Qt includes 0018 0019 #include <QAction> 0020 #include <QList> 0021 #include <QPixmap> 0022 #include <QPainter> 0023 #include <QContextMenuEvent> 0024 #include <QApplication> 0025 #include <QMenu> 0026 #include <QIcon> 0027 0028 // KDE includes 0029 0030 #include <klocalizedstring.h> 0031 0032 // Local includes 0033 0034 #include "digikam_debug.h" 0035 #include "coredb.h" 0036 #include "applicationsettings.h" 0037 #include "contextmenuhelper.h" 0038 #include "itemfiltermodel.h" 0039 #include "itemdragdrop.h" 0040 #include "fileactionmngr.h" 0041 #include "thumbnailloadthread.h" 0042 0043 namespace Digikam 0044 { 0045 0046 template <typename T, class Container> 0047 void removeAnyInInterval(Container& list, const T& begin, const T& end) 0048 { 0049 typename Container::iterator it; 0050 0051 for (it = list.begin() ; it != list.end() ; ) 0052 { 0053 if (((*it) >= begin) && ((*it) <= end)) 0054 { 0055 it = list.erase(it); 0056 } 0057 else 0058 { 0059 ++it; 0060 } 0061 } 0062 } 0063 0064 class Q_DECL_HIDDEN LightTableItemListModel : public ItemListModel 0065 { 0066 Q_OBJECT 0067 0068 public: 0069 0070 explicit LightTableItemListModel(QWidget* const parent) 0071 : ItemListModel(parent), 0072 m_exclusive (false) 0073 { 0074 } 0075 0076 void clearLightTableState() 0077 { 0078 m_leftIndexes.clear(); 0079 m_rightIndexes.clear(); 0080 } 0081 0082 void setExclusiveLightTableState(bool exclusive) 0083 { 0084 m_exclusive = exclusive; 0085 } 0086 0087 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override 0088 { 0089 if (role == LTLeftPanelRole) 0090 { 0091 return m_leftIndexes.contains(index.row()); 0092 } 0093 else if (role == LTRightPanelRole) 0094 { 0095 return m_rightIndexes.contains(index.row()); 0096 } 0097 0098 return ItemListModel::data(index, role); 0099 } 0100 0101 bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::DisplayRole) override 0102 { 0103 if (!index.isValid()) 0104 { 0105 return false; 0106 } 0107 0108 if (role == LTLeftPanelRole) 0109 { 0110 if (m_exclusive) 0111 { 0112 m_leftIndexes.clear(); 0113 } 0114 0115 m_leftIndexes << index.row(); 0116 0117 return true; 0118 } 0119 else if (role == LTRightPanelRole) 0120 { 0121 if (m_exclusive) 0122 { 0123 m_rightIndexes.clear(); 0124 } 0125 0126 m_rightIndexes << index.row(); 0127 0128 return true; 0129 } 0130 0131 return ItemListModel::setData(index, value, role); 0132 } 0133 0134 void imageInfosAboutToBeRemoved(int begin, int end) override 0135 { 0136 removeAnyInInterval(m_leftIndexes, begin, end); 0137 removeAnyInInterval(m_rightIndexes, begin, end); 0138 } 0139 0140 void imageInfosCleared() override 0141 { 0142 clearLightTableState(); 0143 } 0144 0145 protected: 0146 0147 QSet<int> m_leftIndexes; 0148 QSet<int> m_rightIndexes; 0149 bool m_exclusive; 0150 }; 0151 0152 class Q_DECL_HIDDEN LightTableThumbBar::Private 0153 { 0154 0155 public: 0156 0157 explicit Private() 0158 : navigateByPair (false), 0159 imageInfoModel (nullptr), 0160 imageFilterModel(nullptr), 0161 dragDropHandler (nullptr) 0162 { 0163 } 0164 0165 bool navigateByPair; 0166 0167 LightTableItemListModel* imageInfoModel; 0168 ItemFilterModel* imageFilterModel; 0169 ItemDragDropHandler* dragDropHandler; 0170 }; 0171 0172 LightTableThumbBar::LightTableThumbBar(QWidget* const parent) 0173 : ItemThumbnailBar(parent), 0174 d (new Private) 0175 { 0176 d->imageInfoModel = new LightTableItemListModel(this); 0177 0178 // only one is left, only one is right at a time 0179 0180 d->imageInfoModel->setExclusiveLightTableState(true); 0181 0182 d->imageFilterModel = new ItemFilterModel(this); 0183 d->imageFilterModel->setSourceItemModel(d->imageInfoModel); 0184 0185 d->imageInfoModel->setWatchFlags(d->imageFilterModel->suggestedWatchFlags()); 0186 d->imageInfoModel->setThumbnailLoadThread(ThumbnailLoadThread::defaultIconViewThread()); 0187 0188 ApplicationSettings* const settings = ApplicationSettings::instance(); 0189 0190 d->imageFilterModel->setCategorizationMode(ItemSortSettings::NoCategories); 0191 d->imageFilterModel->setStringTypeNatural(settings->isStringTypeNatural()); 0192 d->imageFilterModel->setSortRole((ItemSortSettings::SortRole)settings->getImageSortOrder()); 0193 d->imageFilterModel->setSortOrder((ItemSortSettings::SortOrder)settings->getImageSorting()); 0194 d->imageFilterModel->setAllGroupsOpen(true); // disable filtering out by group, see bug #308948 0195 d->imageFilterModel->sort(0); // an initial sorting is necessary 0196 0197 d->dragDropHandler = new ItemDragDropHandler(d->imageInfoModel); 0198 d->dragDropHandler->setReadOnlyDrop(true); 0199 d->imageInfoModel->setDragDropHandler(d->dragDropHandler); 0200 0201 setModels(d->imageInfoModel, d->imageFilterModel); 0202 setSelectionMode(QAbstractItemView::SingleSelection); 0203 0204 connect(d->dragDropHandler, SIGNAL(itemInfosDropped(QList<ItemInfo>)), 0205 this, SIGNAL(signalDroppedItems(QList<ItemInfo>))); 0206 0207 connect(d->imageInfoModel, SIGNAL(imageInfosAdded(QList<ItemInfo>)), 0208 this, SIGNAL(signalContentChanged())); 0209 0210 connect(d->imageInfoModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), 0211 this, SIGNAL(signalContentChanged())); 0212 0213 connect(settings, SIGNAL(setupChanged()), 0214 this, SLOT(slotSetupChanged())); 0215 } 0216 0217 LightTableThumbBar::~LightTableThumbBar() 0218 { 0219 delete d; 0220 } 0221 0222 void LightTableThumbBar::setItems(const ItemInfoList& list) 0223 { 0224 Q_FOREACH (const ItemInfo& info, list) 0225 { 0226 if (!d->imageInfoModel->hasImage(info)) 0227 { 0228 d->imageInfoModel->addItemInfo(info); 0229 } 0230 } 0231 } 0232 0233 void LightTableThumbBar::slotDockLocationChanged(Qt::DockWidgetArea area) 0234 { 0235 if ((area == Qt::LeftDockWidgetArea) || (area == Qt::RightDockWidgetArea)) 0236 { 0237 setFlow(TopToBottom); 0238 } 0239 else 0240 { 0241 setFlow(LeftToRight); 0242 } 0243 0244 scrollTo(currentIndex()); 0245 } 0246 0247 void LightTableThumbBar::clear() 0248 { 0249 d->imageInfoModel->clearItemInfos(); 0250 Q_EMIT signalContentChanged(); 0251 } 0252 0253 void LightTableThumbBar::setNavigateByPair(bool b) 0254 { 0255 d->navigateByPair = b; 0256 } 0257 0258 void LightTableThumbBar::showContextMenuOnInfo(QContextMenuEvent* e, const ItemInfo& info) 0259 { 0260 // temporary actions ---------------------------------- 0261 0262 QAction* const leftPanelAction = new QAction(QIcon::fromTheme(QLatin1String("arrow-left")), i18n("Show on left panel"), this); 0263 QAction* const rightPanelAction = new QAction(QIcon::fromTheme(QLatin1String("arrow-right")), i18n("Show on right panel"), this); 0264 QAction* const editAction = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Edit"), this); 0265 QAction* const removeAction = new QAction(QIcon::fromTheme(QLatin1String("window-close")), i18n("Remove item"), this); 0266 QAction* const clearAllAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Clear all"), this); 0267 0268 leftPanelAction->setEnabled(d->navigateByPair ? false : true); 0269 rightPanelAction->setEnabled(d->navigateByPair ? false : true); 0270 clearAllAction->setEnabled(countItems() ? true : false); 0271 0272 // ---------------------------------------------------- 0273 0274 QMenu popmenu(this); 0275 ContextMenuHelper cmhelper(&popmenu); 0276 cmhelper.addAction(leftPanelAction, true); 0277 cmhelper.addAction(rightPanelAction, true); 0278 cmhelper.addSeparator(); 0279 cmhelper.addAction(editAction); 0280 cmhelper.addServicesMenu(QList<QUrl>() << info.fileUrl()); 0281 cmhelper.addSeparator(); 0282 cmhelper.addLabelsAction(); 0283 cmhelper.addSeparator(); 0284 cmhelper.addAction(removeAction); 0285 cmhelper.addAction(clearAllAction, true); 0286 0287 // special action handling -------------------------------- 0288 0289 connect(&cmhelper, SIGNAL(signalAssignPickLabel(int)), 0290 this, SLOT(slotAssignPickLabel(int))); 0291 0292 connect(&cmhelper, SIGNAL(signalAssignColorLabel(int)), 0293 this, SLOT(slotAssignColorLabel(int))); 0294 0295 connect(&cmhelper, SIGNAL(signalAssignRating(int)), 0296 this, SLOT(slotAssignRating(int))); 0297 0298 QAction* const choice = cmhelper.exec(e->globalPos()); 0299 0300 if (choice) 0301 { 0302 if (choice == leftPanelAction) 0303 { 0304 Q_EMIT signalSetItemOnLeftPanel(info); 0305 } 0306 else if (choice == rightPanelAction) 0307 { 0308 Q_EMIT signalSetItemOnRightPanel(info); 0309 } 0310 else if (choice == editAction) 0311 { 0312 Q_EMIT signalEditItem(info); 0313 } 0314 else if (choice == removeAction) 0315 { 0316 Q_EMIT signalRemoveItem(info); 0317 } 0318 else if (choice == clearAllAction) 0319 { 0320 Q_EMIT signalClearAll(); 0321 } 0322 } 0323 } 0324 0325 void LightTableThumbBar::slotColorLabelChanged(const QUrl& url, int color) 0326 { 0327 assignColorLabel(ItemInfo::fromUrl(url), color); 0328 } 0329 0330 void LightTableThumbBar::slotPickLabelChanged(const QUrl& url, int pick) 0331 { 0332 assignPickLabel(ItemInfo::fromUrl(url), pick); 0333 } 0334 0335 void LightTableThumbBar::slotAssignPickLabel(int pickId) 0336 { 0337 assignPickLabel(currentInfo(), pickId); 0338 } 0339 0340 void LightTableThumbBar::slotAssignColorLabel(int colorId) 0341 { 0342 assignColorLabel(currentInfo(), colorId); 0343 } 0344 0345 void LightTableThumbBar::slotRatingChanged(const QUrl& url, int rating) 0346 { 0347 assignRating(ItemInfo::fromUrl(url), rating); 0348 } 0349 0350 void LightTableThumbBar::slotAssignRating(int rating) 0351 { 0352 assignRating(currentInfo(), rating); 0353 } 0354 0355 void LightTableThumbBar::assignPickLabel(const ItemInfo& info, int pickId) 0356 { 0357 FileActionMngr::instance()->assignPickLabel(info, pickId); 0358 } 0359 0360 void LightTableThumbBar::assignRating(const ItemInfo& info, int rating) 0361 { 0362 rating = qMin(RatingMax, qMax(RatingMin, rating)); 0363 FileActionMngr::instance()->assignRating(info, rating); 0364 } 0365 0366 void LightTableThumbBar::assignColorLabel(const ItemInfo& info, int colorId) 0367 { 0368 FileActionMngr::instance()->assignColorLabel(info, colorId); 0369 } 0370 0371 void LightTableThumbBar::slotToggleTag(const QUrl& url, int tagID) 0372 { 0373 toggleTag(ItemInfo::fromUrl(url), tagID); 0374 } 0375 0376 void LightTableThumbBar::toggleTag(int tagID) 0377 { 0378 toggleTag(currentInfo(), tagID); 0379 } 0380 0381 void LightTableThumbBar::toggleTag(const ItemInfo& info, int tagID) 0382 { 0383 if (!info.isNull()) 0384 { 0385 if (!info.tagIds().contains(tagID)) 0386 { 0387 FileActionMngr::instance()->assignTag(info, tagID); 0388 } 0389 else 0390 { 0391 FileActionMngr::instance()->removeTag(info, tagID); 0392 } 0393 } 0394 } 0395 0396 void LightTableThumbBar::setOnLeftPanel(const ItemInfo& info) 0397 { 0398 QModelIndex index = d->imageInfoModel->indexForItemInfo(info); 0399 0400 // model has exclusiveLightTableState, so any previous index will be reset 0401 0402 d->imageInfoModel->setData(index, true, ItemModel::LTLeftPanelRole); 0403 viewport()->update(); 0404 } 0405 0406 void LightTableThumbBar::setOnRightPanel(const ItemInfo& info) 0407 { 0408 QModelIndex index = d->imageInfoModel->indexForItemInfo(info); 0409 0410 // model has exclusiveLightTableState, so any previous index will be reset 0411 0412 d->imageInfoModel->setData(index, true, ItemModel::LTRightPanelRole); 0413 viewport()->update(); 0414 } 0415 0416 bool LightTableThumbBar::isOnLeftPanel(const ItemInfo& info) const 0417 { 0418 return d->imageInfoModel->indexForItemInfo(info).data(ItemModel::LTLeftPanelRole).toBool(); 0419 } 0420 0421 bool LightTableThumbBar::isOnRightPanel(const ItemInfo& info) const 0422 { 0423 return d->imageInfoModel->indexForItemInfo(info).data(ItemModel::LTRightPanelRole).toBool(); 0424 } 0425 0426 QModelIndex LightTableThumbBar::findItemByInfo(const ItemInfo& info) const 0427 { 0428 if (!info.isNull()) 0429 { 0430 return d->imageInfoModel->indexForItemInfo(info); 0431 } 0432 0433 return QModelIndex(); 0434 } 0435 0436 ItemInfo LightTableThumbBar::findItemByIndex(const QModelIndex& index) const 0437 { 0438 if (index.isValid()) 0439 { 0440 return d->imageInfoModel->imageInfo(index); 0441 } 0442 0443 return ItemInfo(); 0444 } 0445 0446 void LightTableThumbBar::removeItemByInfo(const ItemInfo& info) 0447 { 0448 if (info.isNull()) 0449 { 0450 return; 0451 } 0452 0453 d->imageInfoModel->removeItemInfo(info); 0454 } 0455 0456 int LightTableThumbBar::countItems() const 0457 { 0458 return d->imageInfoModel->rowCount(); 0459 } 0460 0461 void LightTableThumbBar::paintEvent(QPaintEvent* e) 0462 { 0463 if (!countItems()) 0464 { 0465 QPainter p(viewport()); 0466 p.setPen(QPen(qApp->palette().color(QPalette::Text))); 0467 p.drawText(0, 0, width(), height(), 0468 Qt::AlignCenter | Qt::TextWordWrap, 0469 i18n("Drag and drop images here")); 0470 p.end(); 0471 0472 return; 0473 } 0474 0475 ItemThumbnailBar::paintEvent(e); 0476 } 0477 0478 void LightTableThumbBar::slotSetupChanged() 0479 { 0480 ApplicationSettings* const settings = ApplicationSettings::instance(); 0481 0482 d->imageFilterModel->setStringTypeNatural(settings->isStringTypeNatural()); 0483 d->imageFilterModel->setSortRole((ItemSortSettings::SortRole)settings->getImageSortOrder()); 0484 d->imageFilterModel->setSortOrder((ItemSortSettings::SortOrder)settings->getImageSorting()); 0485 } 0486 0487 } // namespace Digikam 0488 0489 #include "lighttablethumbbar.moc" 0490 0491 #include "moc_lighttablethumbbar.cpp"