1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2021-09-27
7 * Description : List-view for the Showfoto stack view.
8 *
9 * Copyright (C) 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 "showfotostackviewlist.h"
25
26 // Qt includes
27
28 #include <QApplication>
29 #include <QStyle>
30 #include <QIcon>
31 #include <QFileInfo>
32 #include <QHeaderView>
33 #include <QDir>
34 #include <QTimer>
35 #include <QMenu>
36 #include <QModelIndex>
37 #include <QMimeData>
38 #include <QScrollBar>
39 #include <QContextMenuEvent>
40
41 // KDE includes
42
43 #include <klocalizedstring.h>
44
45 // Local includes
46
47 #include "digikam_debug.h"
48 #include "digikam_globals.h"
49 #include "showfotothumbnailbar.h"
50 #include "showfotoiteminfo.h"
51 #include "showfotostackviewtooltip.h"
52 #include "showfotostackviewitem.h"
53 #include "showfotosettings.h"
54 #include "dfileoperations.h"
55
56 namespace ShowFoto
57 {
58
59 class Q_DECL_HIDDEN ShowfotoStackViewList::Private
60 {
61
62 public:
63
Private()64 explicit Private()
65 : view (nullptr),
66 thumbbar (nullptr),
67 toolTipTimer(nullptr),
68 toolTip (nullptr)
69 {
70 }
71
72 ShowfotoStackViewSideBar* view;
73 ShowfotoThumbnailBar* thumbbar;
74 QTimer* toolTipTimer;
75 ShowfotoStackViewToolTip* toolTip;
76 QModelIndex toolTipIndex;
77 };
78
ShowfotoStackViewList(ShowfotoStackViewSideBar * const view)79 ShowfotoStackViewList::ShowfotoStackViewList(ShowfotoStackViewSideBar* const view)
80 : QTreeWidget(view),
81 d (new Private)
82 {
83 d->view = view;
84
85 setObjectName(QLatin1String("ShowfotoStackViewList"));
86 setRootIsDecorated(false);
87 setItemsExpandable(false);
88 setExpandsOnDoubleClick(false);
89 setAlternatingRowColors(true);
90 setIconSize(QSize(SizeSmall, SizeSmall));
91 setSelectionMode(QAbstractItemView::SingleSelection);
92 setSelectionBehavior(QAbstractItemView::SelectRows);
93 setAllColumnsShowFocus(true);
94 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
95
96 setColumnCount(4);
97 QStringList titles;
98 titles.append(i18nc("@title: item file name", "Name"));
99 titles.append(i18nc("@title: item file size", "Size"));
100 titles.append(i18nc("@title: item file type", "Type"));
101 titles.append(i18nc("@title: item date from metadata", "Date"));
102 setHeaderLabels(titles);
103 header()->setSectionResizeMode(FileName, QHeaderView::ResizeToContents);
104 header()->setSectionResizeMode(FileSize, QHeaderView::Stretch);
105 header()->setSectionResizeMode(FileType, QHeaderView::Stretch);
106 header()->setSectionResizeMode(FileDate, QHeaderView::Stretch);
107
108 setDragEnabled(true);
109 setDragDropMode(QAbstractItemView::DragOnly);
110 viewport()->setMouseTracking(true);
111
112 d->toolTip = new ShowfotoStackViewToolTip(this);
113 d->toolTipTimer = new QTimer(this);
114
115 connect(d->toolTipTimer, SIGNAL(timeout()),
116 this, SLOT(slotToolTip()));
117 }
118
~ShowfotoStackViewList()119 ShowfotoStackViewList::~ShowfotoStackViewList()
120 {
121 delete d->toolTip;
122 delete d;
123 }
124
sortOrder() const125 int ShowfotoStackViewList::sortOrder() const
126 {
127 return d->view->sortOrder();
128 }
129
sortRole() const130 int ShowfotoStackViewList::sortRole() const
131 {
132 return d->view->sortRole();
133 }
134
setThumbbar(ShowfotoThumbnailBar * const thumbbar)135 void ShowfotoStackViewList::setThumbbar(ShowfotoThumbnailBar* const thumbbar)
136 {
137 d->thumbbar = thumbbar;
138
139 connect(d->thumbbar->showfotoItemModel(), SIGNAL(itemInfosAboutToBeAdded(QList<ShowfotoItemInfo>)),
140 this, SLOT(slotItemsAdded(QList<ShowfotoItemInfo>)));
141
142 connect(d->thumbbar->showfotoItemModel(), SIGNAL(itemInfosAboutToBeRemoved(QList<ShowfotoItemInfo>)),
143 this, SLOT(slotItemsRemoved(QList<ShowfotoItemInfo>)));
144
145 connect(d->thumbbar->showfotoItemModel(), SIGNAL(itemInfosAdded(QList<ShowfotoItemInfo>)),
146 this, SLOT(slotItemsListChanged()));
147
148 connect(d->thumbbar->showfotoItemModel(), SIGNAL(itemInfosRemoved(QList<ShowfotoItemInfo>)),
149 this, SLOT(slotItemsListChanged()));
150
151 connect(d->thumbbar->showfotoThumbnailModel(), SIGNAL(signalItemThumbnail(ShowfotoItemInfo,QPixmap)),
152 this, SLOT(slotItemThumbnail(ShowfotoItemInfo,QPixmap)));
153
154 connect(d->thumbbar, SIGNAL(selected(QList<ShowfotoItemInfo>)),
155 this, SLOT(slotItemsSelected(QList<ShowfotoItemInfo>)));
156
157 connect(d->thumbbar, SIGNAL(deselected(QList<ShowfotoItemInfo>)),
158 this, SLOT(slotItemsDeselected(QList<ShowfotoItemInfo>)));
159
160 connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
161 this, SLOT(slotSelectionChanged(QTreeWidgetItem*)));
162
163 connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)),
164 this, SLOT(slotItemDoubleClicked(QTreeWidgetItem*)));
165 }
166
slotItemsAdded(const QList<ShowfotoItemInfo> & items)167 void ShowfotoStackViewList::slotItemsAdded(const QList<ShowfotoItemInfo>& items)
168 {
169 foreach (const ShowfotoItemInfo& info, items)
170 {
171 ShowfotoStackViewItem* const it = new ShowfotoStackViewItem(this);
172 it->setInfo(info);
173 }
174 }
175
slotItemsListChanged()176 void ShowfotoStackViewList::slotItemsListChanged()
177 {
178 emit signalItemListChanged(topLevelItemCount());
179 }
180
slotItemsRemoved(const QList<ShowfotoItemInfo> & items)181 void ShowfotoStackViewList::slotItemsRemoved(const QList<ShowfotoItemInfo>& items)
182 {
183 QTreeWidgetItemIterator iter(this);
184 ShowfotoStackViewItem* sitem = nullptr;
185 QList<ShowfotoStackViewItem*> list;
186
187 while (*iter)
188 {
189 sitem = dynamic_cast<ShowfotoStackViewItem*>(*iter);
190
191 if (sitem && (items.contains(sitem->info())))
192 {
193 list << sitem;
194 }
195
196 ++iter;
197 }
198
199 foreach (ShowfotoStackViewItem* const it, list)
200 {
201 delete it;
202 }
203 }
204
slotItemsSelected(const QList<ShowfotoItemInfo> & items)205 void ShowfotoStackViewList::slotItemsSelected(const QList<ShowfotoItemInfo>& items)
206 {
207 blockSignals(true);
208 QTreeWidgetItemIterator iter(this);
209 ShowfotoStackViewItem* sitem = nullptr;
210
211 while (*iter)
212 {
213 sitem = dynamic_cast<ShowfotoStackViewItem*>(*iter);
214
215 if (sitem && items.contains(sitem->info()))
216 {
217 sitem->setSelected(true);
218 }
219
220 ++iter;
221 }
222
223 blockSignals(false);
224 }
225
slotItemsDeselected(const QList<ShowfotoItemInfo> & items)226 void ShowfotoStackViewList::slotItemsDeselected(const QList<ShowfotoItemInfo>& items)
227 {
228 blockSignals(true);
229
230 QTreeWidgetItemIterator iter(this);
231 ShowfotoStackViewItem* sitem = nullptr;
232
233 while (*iter)
234 {
235 sitem = dynamic_cast<ShowfotoStackViewItem*>(*iter);
236
237 if (sitem && items.contains(sitem->info()))
238 {
239 sitem->setSelected(false);
240 }
241
242 ++iter;
243 }
244
245 blockSignals(false);
246 }
247
slotSelectionChanged(QTreeWidgetItem * item)248 void ShowfotoStackViewList::slotSelectionChanged(QTreeWidgetItem* item)
249 {
250 ShowfotoStackViewItem* const sitem = dynamic_cast<ShowfotoStackViewItem*>(item);
251
252 if (sitem)
253 {
254 d->thumbbar->setCurrentInfo(sitem->info());
255 }
256 }
257
slotItemDoubleClicked(QTreeWidgetItem * item)258 void ShowfotoStackViewList::slotItemDoubleClicked(QTreeWidgetItem* item)
259 {
260 ShowfotoStackViewItem* const sitem = dynamic_cast<ShowfotoStackViewItem*>(item);
261
262 if (sitem)
263 {
264 emit signalShowfotoItemInfoActivated(sitem->info());
265 }
266 }
267
slotItemThumbnail(const ShowfotoItemInfo & info,const QPixmap & pix)268 void ShowfotoStackViewList::slotItemThumbnail(const ShowfotoItemInfo& info, const QPixmap& pix)
269 {
270 QTreeWidgetItemIterator iter(this);
271 ShowfotoStackViewItem* sitem = nullptr;
272
273 while (*iter)
274 {
275 sitem = dynamic_cast<ShowfotoStackViewItem*>(*iter);
276
277 if (sitem && (sitem->info() == info))
278 {
279 sitem->setThumbnail(pix);
280 return;
281 }
282
283 ++iter;
284 }
285 }
286
drawRow(QPainter * p,const QStyleOptionViewItem & opt,const QModelIndex & index) const287 void ShowfotoStackViewList::drawRow(QPainter* p, const QStyleOptionViewItem& opt, const QModelIndex& index) const
288 {
289 ShowfotoItemInfo info = infoFromIndex(index);
290
291 if (!info.isNull() && index.isValid())
292 {
293 ShowfotoStackViewItem* const sitem = dynamic_cast<ShowfotoStackViewItem*>(itemFromIndex(index));
294
295 if (
296 sitem &&
297 (
298 sitem->icon(FileName).isNull() ||
299 (sitem->icon(FileName).actualSize(iconSize()) != iconSize())
300 )
301 )
302 {
303 d->thumbbar->showfotoThumbnailModel()->thumbnailLoadThread()->find(ThumbnailIdentifier(info.url.toLocalFile()));
304 }
305 }
306
307 QTreeWidget::drawRow(p, opt, index);
308 }
309
infoFromIndex(const QModelIndex & index) const310 ShowfotoItemInfo ShowfotoStackViewList::infoFromIndex(const QModelIndex& index) const
311 {
312 ShowfotoItemInfo info;
313
314 if (index.isValid())
315 {
316 ShowfotoStackViewItem* const sitem = dynamic_cast<ShowfotoStackViewItem*>(itemFromIndex(index));
317
318 if (sitem)
319 {
320 info = sitem->info();
321 }
322 }
323
324 return info;
325 }
326
slotOpenInFileManager()327 void ShowfotoStackViewList::slotOpenInFileManager()
328 {
329 QModelIndex index = currentIndex();
330 QList<QUrl> urls;
331
332 if (index.isValid())
333 {
334 ShowfotoStackViewItem* const sitem = dynamic_cast<ShowfotoStackViewItem*>(itemFromIndex(index));
335
336 if (sitem)
337 {
338 urls << QUrl::fromLocalFile(sitem->info().url.toLocalFile());
339
340 DFileOperations::openInFileManager(urls);
341 }
342 }
343 }
344
slotIconSizeChanged(int size)345 void ShowfotoStackViewList::slotIconSizeChanged(int size)
346 {
347 setIconSize(QSize(size, size));
348 }
349
contextMenuEvent(QContextMenuEvent * e)350 void ShowfotoStackViewList::contextMenuEvent(QContextMenuEvent* e)
351 {
352 QMenu* const ctxmenu = new QMenu(this);
353 ctxmenu->setTitle(i18nc("@title", "Stack-View"));
354
355 QMenu* const iconMenu = new QMenu(i18nc("@title:menu", "Icon Size"), ctxmenu);
356 iconMenu->setIcon(QIcon::fromTheme(QLatin1String("file-zoom-in")));
357
358 QActionGroup* const sizeGrp = new QActionGroup(iconMenu);
359
360 QAction* const sizeSmall = iconMenu->addAction(i18nc("@action:inmenu", "Small (%1x%2)", SizeSmall, SizeSmall));
361 sizeSmall->setCheckable(true);
362 sizeGrp->addAction(sizeSmall);
363
364 QAction* const sizeMedium = iconMenu->addAction(i18nc("@action:inmenu", "Medium (%1x%2)", SizeMedium, SizeMedium));
365 sizeMedium->setCheckable(true);
366 sizeGrp->addAction(sizeMedium);
367
368 QAction* const sizeLarge = iconMenu->addAction(i18nc("@action:inmenu", "Large (%1x%2)", SizeLarge, SizeLarge));
369 sizeLarge->setCheckable(true);
370 sizeGrp->addAction(sizeLarge);
371
372 QAction* const sizeHuge = iconMenu->addAction(i18nc("@action:inmenu", "Huge (%1x%2)", SizeHuge, SizeHuge));
373 sizeHuge->setCheckable(true);
374 sizeGrp->addAction(sizeHuge);
375
376 sizeGrp->setExclusive(true);
377
378 switch (iconSize().width())
379 {
380 case SizeMedium:
381 {
382 sizeMedium->setChecked(true);
383 break;
384 }
385
386 case SizeLarge:
387 {
388 sizeLarge->setChecked(true);
389 break;
390 }
391
392 case SizeHuge:
393 {
394 sizeHuge->setChecked(true);
395 break;
396 }
397
398 default:
399 {
400 sizeSmall->setChecked(true);
401 break;
402 }
403 }
404
405 ctxmenu->addMenu(iconMenu);
406 ctxmenu->addSeparator();
407 ctxmenu->addActions(d->view->pluginActions());
408 ctxmenu->addSeparator();
409
410 QAction* const addFavorite = new QAction(QIcon::fromTheme(QLatin1String("list-add")),
411 i18nc("@action: context menu", "Add as Favorite"), ctxmenu);
412 ctxmenu->addAction(addFavorite);
413
414 connect(addFavorite, SIGNAL(triggered()),
415 this, SIGNAL(signalAddFavorite()));
416
417 QAction* const openFileMngr = new QAction(QIcon::fromTheme(QLatin1String("folder-open")),
418 i18nc("@action: context menu", "Open in File Manager"), ctxmenu);
419 ctxmenu->addAction(openFileMngr);
420
421 connect(openFileMngr, SIGNAL(triggered()),
422 this, SLOT(slotOpenInFileManager()));
423
424 QAction* const clearListAction = new QAction(QIcon::fromTheme(QLatin1String("edit-clear")),
425 i18nc("@action: context menu", "Clear All Items"), this);
426
427 ctxmenu->addAction(clearListAction);
428
429 connect(clearListAction, SIGNAL(triggered()),
430 this, SIGNAL(signalClearItemsList()));
431
432 QAction* const removeItemsAction = new QAction(QIcon::fromTheme(QLatin1String("edit-clear")),
433 i18nc("@action: context menu", "Remove Items"), this);
434
435 ctxmenu->addAction(removeItemsAction);
436
437 connect(removeItemsAction, SIGNAL(triggered()),
438 this, SLOT(slotRemoveItems()));
439
440 ctxmenu->exec(e->globalPos());
441
442 if (sizeSmall->isChecked())
443 {
444 setIconSize(QSize(SizeSmall, SizeSmall));
445 }
446 else if (sizeMedium->isChecked())
447 {
448 setIconSize(QSize(SizeMedium, SizeMedium));
449 }
450 else if (sizeLarge->isChecked())
451 {
452 setIconSize(QSize(SizeLarge, SizeLarge));
453 }
454 else if (sizeHuge->isChecked())
455 {
456 setIconSize(QSize(SizeHuge, SizeHuge));
457 }
458
459 delete ctxmenu;
460
461 QTreeView::contextMenuEvent(e);
462 }
463
slotRemoveItems()464 void ShowfotoStackViewList::slotRemoveItems()
465 {
466 QList<QTreeWidgetItem*> sel = selectedItems();
467
468 if (sel.isEmpty())
469 {
470 return;
471 }
472
473 QList<ShowfotoItemInfo> infos;
474
475 foreach (QTreeWidgetItem* const item, sel)
476 {
477 ShowfotoStackViewItem* const sitem = dynamic_cast<ShowfotoStackViewItem*>(item);
478
479 if (sitem)
480 {
481 infos << sitem->info();
482 delete sitem;
483 }
484 }
485
486 emit signalRemoveItemInfos(infos);
487 }
488
hideToolTip()489 void ShowfotoStackViewList::hideToolTip()
490 {
491 d->toolTipIndex = QModelIndex();
492 d->toolTipTimer->stop();
493 slotToolTip();
494 }
495
slotToolTip()496 void ShowfotoStackViewList::slotToolTip()
497 {
498 d->toolTip->setIndex(d->toolTipIndex);
499 }
500
acceptToolTip(const QModelIndex & index) const501 bool ShowfotoStackViewList::acceptToolTip(const QModelIndex& index) const
502 {
503 ShowfotoItemInfo info = infoFromIndex(index);
504
505 if (!info.isNull())
506 {
507 return true;
508 }
509
510 return false;
511 }
512
mouseMoveEvent(QMouseEvent * e)513 void ShowfotoStackViewList::mouseMoveEvent(QMouseEvent* e)
514 {
515 if (e->buttons() == Qt::NoButton)
516 {
517 QModelIndex index = indexAt(e->pos());
518
519 if (ShowfotoSettings::instance()->getShowToolTip())
520 {
521 if (!isActiveWindow())
522 {
523 hideToolTip();
524 return;
525 }
526
527 if (index != d->toolTipIndex)
528 {
529 hideToolTip();
530
531 if (acceptToolTip(index))
532 {
533 d->toolTipIndex = index;
534 d->toolTipTimer->setSingleShot(true);
535 d->toolTipTimer->start(500);
536 }
537 }
538
539 if ((index == d->toolTipIndex) && !acceptToolTip(index))
540 {
541 hideToolTip();
542 }
543 }
544
545 return;
546 }
547
548 hideToolTip();
549 QTreeView::mouseMoveEvent(e);
550 }
551
wheelEvent(QWheelEvent * e)552 void ShowfotoStackViewList::wheelEvent(QWheelEvent* e)
553 {
554 hideToolTip();
555 QTreeView::wheelEvent(e);
556 }
557
keyPressEvent(QKeyEvent * e)558 void ShowfotoStackViewList::keyPressEvent(QKeyEvent* e)
559 {
560 hideToolTip();
561 QTreeView::keyPressEvent(e);
562 }
563
focusOutEvent(QFocusEvent * e)564 void ShowfotoStackViewList::focusOutEvent(QFocusEvent* e)
565 {
566 hideToolTip();
567 QTreeView::focusOutEvent(e);
568 }
569
leaveEvent(QEvent * e)570 void ShowfotoStackViewList::leaveEvent(QEvent* e)
571 {
572 hideToolTip();
573 QTreeView::leaveEvent(e);
574 }
575
urls()576 QList<QUrl> ShowfotoStackViewList::urls()
577 {
578 QTreeWidgetItemIterator iter(this);
579 ShowfotoStackViewItem* sitem = nullptr;
580 QList<QUrl> list;
581
582 while (*iter)
583 {
584 sitem = dynamic_cast<ShowfotoStackViewItem*>(*iter);
585
586 if (sitem)
587 {
588 list << sitem->info().url;
589 }
590
591 ++iter;
592 }
593
594 return list;
595 }
596
currentUrl() const597 QUrl ShowfotoStackViewList::currentUrl() const
598 {
599 ShowfotoStackViewItem* sitem = dynamic_cast<ShowfotoStackViewItem*>(currentItem());
600
601 if (sitem)
602 {
603 return (sitem->info().url);
604 }
605
606 return QUrl();
607 }
608
609 } // namespace ShowFoto
610