1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2007-04-11
7 * Description : light table thumbs bar
8 *
9 * Copyright (C) 2007-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10 *
11 * This program is free software; you can redistribute it
12 * and/or modify it under the terms of the GNU General
13 * Public License as published by the Free Software Foundation;
14 * either version 2, or (at your option)
15 * any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * ============================================================ */
23
24 #include "lighttablethumbbar.h"
25
26 // Qt includes
27
28 #include <QAction>
29 #include <QList>
30 #include <QPixmap>
31 #include <QPainter>
32 #include <QContextMenuEvent>
33 #include <QApplication>
34 #include <QMenu>
35 #include <QIcon>
36
37 // KDE includes
38
39 #include <klocalizedstring.h>
40
41 // Local includes
42
43 #include "digikam_debug.h"
44 #include "coredb.h"
45 #include "applicationsettings.h"
46 #include "contextmenuhelper.h"
47 #include "itemfiltermodel.h"
48 #include "itemdragdrop.h"
49 #include "fileactionmngr.h"
50 #include "thumbnailloadthread.h"
51
52 namespace Digikam
53 {
54
55 template <typename T, class Container>
removeAnyInInterval(Container & list,const T & begin,const T & end)56 void removeAnyInInterval(Container& list, const T& begin, const T& end)
57 {
58 typename Container::iterator it;
59
60 for (it = list.begin() ; it != list.end() ; )
61 {
62 if (((*it) >= begin) && ((*it) <= end))
63 {
64 it = list.erase(it);
65 }
66 else
67 {
68 ++it;
69 }
70 }
71 }
72
73 class Q_DECL_HIDDEN LightTableItemListModel : public ItemListModel
74 {
75 Q_OBJECT
76
77 public:
78
LightTableItemListModel(QObject * const parent=nullptr)79 explicit LightTableItemListModel(QObject* const parent = nullptr)
80 : ItemListModel(parent),
81 m_exclusive (false)
82 {
83 }
84
clearLightTableState()85 void clearLightTableState()
86 {
87 m_leftIndexes.clear();
88 m_rightIndexes.clear();
89 }
90
setExclusiveLightTableState(bool exclusive)91 void setExclusiveLightTableState(bool exclusive)
92 {
93 m_exclusive = exclusive;
94 }
95
data(const QModelIndex & index,int role=Qt::DisplayRole) const96 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
97 {
98 if (role == LTLeftPanelRole)
99 {
100 return m_leftIndexes.contains(index.row());
101 }
102 else if (role == LTRightPanelRole)
103 {
104 return m_rightIndexes.contains(index.row());
105 }
106
107 return ItemListModel::data(index, role);
108 }
109
setData(const QModelIndex & index,const QVariant & value,int role=Qt::DisplayRole)110 bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::DisplayRole) override
111 {
112 if (!index.isValid())
113 {
114 return false;
115 }
116
117 if (role == LTLeftPanelRole)
118 {
119 if (m_exclusive)
120 {
121 m_leftIndexes.clear();
122 }
123
124 m_leftIndexes << index.row();
125
126 return true;
127 }
128 else if (role == LTRightPanelRole)
129 {
130 if (m_exclusive)
131 {
132 m_rightIndexes.clear();
133 }
134
135 m_rightIndexes << index.row();
136
137 return true;
138 }
139
140 return ItemListModel::setData(index, value, role);
141 }
142
imageInfosAboutToBeRemoved(int begin,int end)143 void imageInfosAboutToBeRemoved(int begin, int end) override
144 {
145 removeAnyInInterval(m_leftIndexes, begin, end);
146 removeAnyInInterval(m_rightIndexes, begin, end);
147 }
148
imageInfosCleared()149 void imageInfosCleared() override
150 {
151 clearLightTableState();
152 }
153
154 protected:
155
156 QSet<int> m_leftIndexes;
157 QSet<int> m_rightIndexes;
158 bool m_exclusive;
159 };
160
161 class Q_DECL_HIDDEN LightTableThumbBar::Private
162 {
163
164 public:
165
Private()166 explicit Private()
167 : navigateByPair (false),
168 imageInfoModel (nullptr),
169 imageFilterModel(nullptr),
170 dragDropHandler (nullptr)
171 {
172 }
173
174 bool navigateByPair;
175
176 LightTableItemListModel* imageInfoModel;
177 ItemFilterModel* imageFilterModel;
178 ItemDragDropHandler* dragDropHandler;
179 };
180
LightTableThumbBar(QWidget * const parent)181 LightTableThumbBar::LightTableThumbBar(QWidget* const parent)
182 : ItemThumbnailBar(parent),
183 d (new Private)
184 {
185 d->imageInfoModel = new LightTableItemListModel(this);
186
187 // only one is left, only one is right at a time
188
189 d->imageInfoModel->setExclusiveLightTableState(true);
190
191 d->imageFilterModel = new ItemFilterModel(this);
192 d->imageFilterModel->setSourceItemModel(d->imageInfoModel);
193
194 d->imageInfoModel->setWatchFlags(d->imageFilterModel->suggestedWatchFlags());
195 d->imageInfoModel->setThumbnailLoadThread(ThumbnailLoadThread::defaultIconViewThread());
196
197 d->imageFilterModel->setCategorizationMode(ItemSortSettings::NoCategories);
198 d->imageFilterModel->setStringTypeNatural(ApplicationSettings::instance()->isStringTypeNatural());
199 d->imageFilterModel->setSortRole((ItemSortSettings::SortRole)ApplicationSettings::instance()->getImageSortOrder());
200 d->imageFilterModel->setSortOrder((ItemSortSettings::SortOrder)ApplicationSettings::instance()->getImageSorting());
201 d->imageFilterModel->setAllGroupsOpen(true); // disable filtering out by group, see bug #308948
202 d->imageFilterModel->sort(0); // an initial sorting is necessary
203
204 d->dragDropHandler = new ItemDragDropHandler(d->imageInfoModel);
205 d->dragDropHandler->setReadOnlyDrop(true);
206 d->imageInfoModel->setDragDropHandler(d->dragDropHandler);
207
208 setModels(d->imageInfoModel, d->imageFilterModel);
209 setSelectionMode(QAbstractItemView::SingleSelection);
210
211 connect(d->dragDropHandler, SIGNAL(itemInfosDropped(QList<ItemInfo>)),
212 this, SIGNAL(signalDroppedItems(QList<ItemInfo>)));
213
214 connect(d->imageInfoModel, SIGNAL(imageInfosAdded(QList<ItemInfo>)),
215 this, SIGNAL(signalContentChanged()));
216
217 connect(d->imageInfoModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
218 this, SIGNAL(signalContentChanged()));
219
220 connect(ApplicationSettings::instance(), SIGNAL(setupChanged()),
221 this, SLOT(slotSetupChanged()));
222 }
223
~LightTableThumbBar()224 LightTableThumbBar::~LightTableThumbBar()
225 {
226 delete d;
227 }
228
setItems(const ItemInfoList & list)229 void LightTableThumbBar::setItems(const ItemInfoList& list)
230 {
231 foreach (const ItemInfo& info, list)
232 {
233 if (!d->imageInfoModel->hasImage(info))
234 {
235 d->imageInfoModel->addItemInfo(info);
236 }
237 }
238 }
239
slotDockLocationChanged(Qt::DockWidgetArea area)240 void LightTableThumbBar::slotDockLocationChanged(Qt::DockWidgetArea area)
241 {
242 if ((area == Qt::LeftDockWidgetArea) || (area == Qt::RightDockWidgetArea))
243 {
244 setFlow(TopToBottom);
245 }
246 else
247 {
248 setFlow(LeftToRight);
249 }
250
251 scrollTo(currentIndex());
252 }
253
clear()254 void LightTableThumbBar::clear()
255 {
256 d->imageInfoModel->clearItemInfos();
257 emit signalContentChanged();
258 }
259
setNavigateByPair(bool b)260 void LightTableThumbBar::setNavigateByPair(bool b)
261 {
262 d->navigateByPair = b;
263 }
264
showContextMenuOnInfo(QContextMenuEvent * e,const ItemInfo & info)265 void LightTableThumbBar::showContextMenuOnInfo(QContextMenuEvent* e, const ItemInfo& info)
266 {
267 // temporary actions ----------------------------------
268
269 QAction* const leftPanelAction = new QAction(QIcon::fromTheme(QLatin1String("go-previous")), i18n("Show on left panel"), this);
270 QAction* const rightPanelAction = new QAction(QIcon::fromTheme(QLatin1String("go-next")), i18n("Show on right panel"), this);
271 QAction* const editAction = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Edit"), this);
272 QAction* const removeAction = new QAction(QIcon::fromTheme(QLatin1String("window-close")), i18n("Remove item"), this);
273 QAction* const clearAllAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Clear all"), this);
274
275 leftPanelAction->setEnabled(d->navigateByPair ? false : true);
276 rightPanelAction->setEnabled(d->navigateByPair ? false : true);
277 clearAllAction->setEnabled(countItems() ? true : false);
278
279 // ----------------------------------------------------
280
281 QMenu popmenu(this);
282 ContextMenuHelper cmhelper(&popmenu);
283 cmhelper.addAction(leftPanelAction, true);
284 cmhelper.addAction(rightPanelAction, true);
285 cmhelper.addSeparator();
286 cmhelper.addAction(editAction);
287 cmhelper.addServicesMenu(QList<QUrl>() << info.fileUrl());
288 cmhelper.addSeparator();
289 cmhelper.addLabelsAction();
290 cmhelper.addSeparator();
291 cmhelper.addAction(removeAction);
292 cmhelper.addAction(clearAllAction, true);
293
294 // special action handling --------------------------------
295
296 connect(&cmhelper, SIGNAL(signalAssignPickLabel(int)),
297 this, SLOT(slotAssignPickLabel(int)));
298
299 connect(&cmhelper, SIGNAL(signalAssignColorLabel(int)),
300 this, SLOT(slotAssignColorLabel(int)));
301
302 connect(&cmhelper, SIGNAL(signalAssignRating(int)),
303 this, SLOT(slotAssignRating(int)));
304
305 QAction* const choice = cmhelper.exec(e->globalPos());
306
307 if (choice)
308 {
309 if (choice == leftPanelAction)
310 {
311 emit signalSetItemOnLeftPanel(info);
312 }
313 else if (choice == rightPanelAction)
314 {
315 emit signalSetItemOnRightPanel(info);
316 }
317 else if (choice == editAction)
318 {
319 emit signalEditItem(info);
320 }
321 else if (choice == removeAction)
322 {
323 emit signalRemoveItem(info);
324 }
325 else if (choice == clearAllAction)
326 {
327 emit signalClearAll();
328 }
329 }
330 }
331
slotColorLabelChanged(const QUrl & url,int color)332 void LightTableThumbBar::slotColorLabelChanged(const QUrl& url, int color)
333 {
334 assignColorLabel(ItemInfo::fromUrl(url), color);
335 }
336
slotPickLabelChanged(const QUrl & url,int pick)337 void LightTableThumbBar::slotPickLabelChanged(const QUrl& url, int pick)
338 {
339 assignPickLabel(ItemInfo::fromUrl(url), pick);
340 }
341
slotAssignPickLabel(int pickId)342 void LightTableThumbBar::slotAssignPickLabel(int pickId)
343 {
344 assignPickLabel(currentInfo(), pickId);
345 }
346
slotAssignColorLabel(int colorId)347 void LightTableThumbBar::slotAssignColorLabel(int colorId)
348 {
349 assignColorLabel(currentInfo(), colorId);
350 }
351
slotRatingChanged(const QUrl & url,int rating)352 void LightTableThumbBar::slotRatingChanged(const QUrl& url, int rating)
353 {
354 assignRating(ItemInfo::fromUrl(url), rating);
355 }
356
slotAssignRating(int rating)357 void LightTableThumbBar::slotAssignRating(int rating)
358 {
359 assignRating(currentInfo(), rating);
360 }
361
assignPickLabel(const ItemInfo & info,int pickId)362 void LightTableThumbBar::assignPickLabel(const ItemInfo& info, int pickId)
363 {
364 FileActionMngr::instance()->assignPickLabel(info, pickId);
365 }
366
assignRating(const ItemInfo & info,int rating)367 void LightTableThumbBar::assignRating(const ItemInfo& info, int rating)
368 {
369 rating = qMin(RatingMax, qMax(RatingMin, rating));
370 FileActionMngr::instance()->assignRating(info, rating);
371 }
372
assignColorLabel(const ItemInfo & info,int colorId)373 void LightTableThumbBar::assignColorLabel(const ItemInfo& info, int colorId)
374 {
375 FileActionMngr::instance()->assignColorLabel(info, colorId);
376 }
377
slotToggleTag(const QUrl & url,int tagID)378 void LightTableThumbBar::slotToggleTag(const QUrl& url, int tagID)
379 {
380 toggleTag(ItemInfo::fromUrl(url), tagID);
381 }
382
toggleTag(int tagID)383 void LightTableThumbBar::toggleTag(int tagID)
384 {
385 toggleTag(currentInfo(), tagID);
386 }
387
toggleTag(const ItemInfo & info,int tagID)388 void LightTableThumbBar::toggleTag(const ItemInfo& info, int tagID)
389 {
390 if (!info.isNull())
391 {
392 if (!info.tagIds().contains(tagID))
393 {
394 FileActionMngr::instance()->assignTag(info, tagID);
395 }
396 else
397 {
398 FileActionMngr::instance()->removeTag(info, tagID);
399 }
400 }
401 }
402
setOnLeftPanel(const ItemInfo & info)403 void LightTableThumbBar::setOnLeftPanel(const ItemInfo& info)
404 {
405 QModelIndex index = d->imageInfoModel->indexForItemInfo(info);
406
407 // model has exclusiveLightTableState, so any previous index will be reset
408
409 d->imageInfoModel->setData(index, true, ItemModel::LTLeftPanelRole);
410 viewport()->update();
411 }
412
setOnRightPanel(const ItemInfo & info)413 void LightTableThumbBar::setOnRightPanel(const ItemInfo& info)
414 {
415 QModelIndex index = d->imageInfoModel->indexForItemInfo(info);
416
417 // model has exclusiveLightTableState, so any previous index will be reset
418
419 d->imageInfoModel->setData(index, true, ItemModel::LTRightPanelRole);
420 viewport()->update();
421 }
422
isOnLeftPanel(const ItemInfo & info) const423 bool LightTableThumbBar::isOnLeftPanel(const ItemInfo& info) const
424 {
425 return d->imageInfoModel->indexForItemInfo(info).data(ItemModel::LTLeftPanelRole).toBool();
426 }
427
isOnRightPanel(const ItemInfo & info) const428 bool LightTableThumbBar::isOnRightPanel(const ItemInfo& info) const
429 {
430 return d->imageInfoModel->indexForItemInfo(info).data(ItemModel::LTRightPanelRole).toBool();
431 }
432
findItemByInfo(const ItemInfo & info) const433 QModelIndex LightTableThumbBar::findItemByInfo(const ItemInfo& info) const
434 {
435 if (!info.isNull())
436 {
437 return d->imageInfoModel->indexForItemInfo(info);
438 }
439
440 return QModelIndex();
441 }
442
findItemByIndex(const QModelIndex & index) const443 ItemInfo LightTableThumbBar::findItemByIndex(const QModelIndex& index) const
444 {
445 if (index.isValid())
446 {
447 return d->imageInfoModel->imageInfo(index);
448 }
449
450 return ItemInfo();
451 }
452
removeItemByInfo(const ItemInfo & info)453 void LightTableThumbBar::removeItemByInfo(const ItemInfo& info)
454 {
455 if (info.isNull())
456 {
457 return;
458 }
459
460 d->imageInfoModel->removeItemInfo(info);
461 }
462
countItems() const463 int LightTableThumbBar::countItems() const
464 {
465 return d->imageInfoModel->rowCount();
466 }
467
paintEvent(QPaintEvent * e)468 void LightTableThumbBar::paintEvent(QPaintEvent* e)
469 {
470 if (!countItems())
471 {
472 QPainter p(viewport());
473 p.setPen(QPen(qApp->palette().color(QPalette::Text)));
474 p.drawText(0, 0, width(), height(),
475 Qt::AlignCenter | Qt::TextWordWrap,
476 i18n("Drag and drop images here"));
477 p.end();
478
479 return;
480 }
481
482 ItemThumbnailBar::paintEvent(e);
483 }
484
slotSetupChanged()485 void LightTableThumbBar::slotSetupChanged()
486 {
487 d->imageFilterModel->setStringTypeNatural(ApplicationSettings::instance()->isStringTypeNatural());
488 }
489
490 } // namespace Digikam
491
492 #include "lighttablethumbbar.moc"
493