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