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"