1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2010-01-16
7  * Description : Qt item view for images
8  *
9  * Copyright (C) 2009-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
10  * Copyright (C) 2011-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11  *
12  * This program is free software; you can redistribute it
13  * and/or modify it under the terms of the GNU General
14  * Public License as published by the Free Software Foundation;
15  * either version 2, or (at your option)
16  * any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * ============================================================ */
24 
25 #include "itemviewcategorized.h"
26 
27 // Qt includes
28 
29 #include <QClipboard>
30 #include <QHelpEvent>
31 #include <QScrollBar>
32 #include <QSortFilterProxyModel>
33 #include <QStyle>
34 #include <QApplication>
35 
36 // Local includes
37 
38 #include "digikam_debug.h"
39 #include "thememanager.h"
40 #include "ditemdelegate.h"
41 #include "abstractitemdragdrophandler.h"
42 #include "itemviewtooltip.h"
43 
44 namespace Digikam
45 {
46 
47 class Q_DECL_HIDDEN ItemViewCategorized::Private
48 {
49 public:
50 
Private(ItemViewCategorized * const q)51     explicit Private(ItemViewCategorized* const q)
52       : delegate                    (nullptr),
53         toolTip                     (nullptr),
54         notificationToolTip         (nullptr),
55         showToolTip                 (false),
56         usePointingHand             (true),
57         scrollStepFactor            (10),
58         currentMouseEvent           (nullptr),
59         ensureOneSelectedItem       (false),
60         ensureInitialSelectedItem   (false),
61         scrollCurrentToCenter       (false),
62         mouseButtonPressed          (Qt::NoButton),
63         hintAtSelectionRow          (-1),
64         q                           (q)
65     {
66     }
67 
68     QModelIndex scrollPositionHint() const;
69 
70 public:
71 
72     DItemDelegate*             delegate;
73     ItemViewToolTip*           toolTip;
74     ItemViewToolTip*           notificationToolTip;
75     bool                       showToolTip;
76     bool                       usePointingHand;
77     int                        scrollStepFactor;
78 
79     QMouseEvent*               currentMouseEvent;
80     bool                       ensureOneSelectedItem;
81     bool                       ensureInitialSelectedItem;
82     bool                       scrollCurrentToCenter;
83     Qt::MouseButton            mouseButtonPressed;
84     QPersistentModelIndex      hintAtSelectionIndex;
85     int                        hintAtSelectionRow;
86     QPersistentModelIndex      hintAtScrollPosition;
87 
88     ItemViewCategorized* const q;
89 };
90 
scrollPositionHint() const91 QModelIndex ItemViewCategorized::Private::scrollPositionHint() const
92 {
93     if (q->verticalScrollBar()->value() == q->verticalScrollBar()->minimum())
94     {
95         return QModelIndex();
96     }
97 
98     QModelIndex hint = q->currentIndex();
99 
100     // If the user scrolled, do not take current item, but first visible
101 
102     if (!hint.isValid() || !q->viewport()->rect().intersects(q->visualRect(hint)))
103     {
104         QList<QModelIndex> visibleIndexes = q->categorizedIndexesIn(q->viewport()->rect());
105 
106         if (!visibleIndexes.isEmpty())
107         {
108             hint = visibleIndexes.first();
109         }
110     }
111 
112     return hint;
113 }
114 
115 // -------------------------------------------------------------------------------
116 
ItemViewCategorized(QWidget * const parent)117 ItemViewCategorized::ItemViewCategorized(QWidget* const parent)
118     : DCategorizedView(parent),
119       d               (new Private(this))
120 {
121     setViewMode(QListView::IconMode);
122     setLayoutDirection(Qt::LeftToRight);
123     setFlow(QListView::LeftToRight);
124     setResizeMode(QListView::Adjust);
125     setMovement(QListView::Static);
126     setWrapping(true);
127 
128     // important optimization for layouting
129 
130     setUniformItemSizes(true);
131 
132     // disable "feature" from DCategorizedView
133 
134     setDrawDraggedItems(false);
135 
136     setSelectionMode(QAbstractItemView::ExtendedSelection);
137 
138     setDragEnabled(true);
139     setEditTriggers(QAbstractItemView::NoEditTriggers);
140     viewport()->setAcceptDrops(true);
141     setMouseTracking(true);
142 
143     connect(this, SIGNAL(clicked(QModelIndex)),
144             this, SLOT(slotClicked(QModelIndex)));
145 
146     connect(this, SIGNAL(entered(QModelIndex)),
147             this, SLOT(slotEntered(QModelIndex)));
148 
149     // --- NOTE: use dynamic binding as slots below are virtual methods which can be re-implemented in derived classes.
150 
151     connect(this, &ItemViewCategorized::activated,
152             this, &ItemViewCategorized::slotActivated);
153 
154     connect(ThemeManager::instance(), &ThemeManager::signalThemeChanged,
155             this, &ItemViewCategorized::slotThemeChanged);
156 }
157 
~ItemViewCategorized()158 ItemViewCategorized::~ItemViewCategorized()
159 {
160     delete d;
161 }
162 
setToolTip(ItemViewToolTip * tip)163 void ItemViewCategorized::setToolTip(ItemViewToolTip* tip)
164 {
165     d->toolTip = tip;
166 }
167 
setItemDelegate(DItemDelegate * delegate)168 void ItemViewCategorized::setItemDelegate(DItemDelegate* delegate)
169 {
170     if (d->delegate == delegate)
171     {
172         return;
173     }
174 
175     if (d->delegate)
176     {
177         disconnect(d->delegate, SIGNAL(gridSizeChanged(QSize)),
178                    this, SLOT(slotGridSizeChanged(QSize)));
179     }
180 
181     d->delegate = delegate;
182     DCategorizedView::setItemDelegate(d->delegate);
183 
184     connect(d->delegate, SIGNAL(gridSizeChanged(QSize)),
185             this, SLOT(slotGridSizeChanged(QSize)));
186 }
187 
setSpacing(int spacing)188 void ItemViewCategorized::setSpacing(int spacing)
189 {
190     d->delegate->setSpacing(spacing);
191 }
192 
setUsePointingHandCursor(bool useCursor)193 void ItemViewCategorized::setUsePointingHandCursor(bool useCursor)
194 {
195     d->usePointingHand = useCursor;
196 }
197 
setScrollStepGranularity(int factor)198 void ItemViewCategorized::setScrollStepGranularity(int factor)
199 {
200     d->scrollStepFactor = qMax(1, factor);
201 }
202 
delegate() const203 DItemDelegate* ItemViewCategorized::delegate() const
204 {
205     return d->delegate;
206 }
207 
numberOfSelectedIndexes() const208 int ItemViewCategorized::numberOfSelectedIndexes() const
209 {
210     return selectedIndexes().size();
211 }
212 
toFirstIndex()213 void ItemViewCategorized::toFirstIndex()
214 {
215     QModelIndex index = moveCursor(MoveHome, Qt::NoModifier);
216     clearSelection();
217     setCurrentIndex(index);
218     scrollToTop();
219 }
220 
toLastIndex()221 void ItemViewCategorized::toLastIndex()
222 {
223     QModelIndex index = moveCursor(MoveEnd, Qt::NoModifier);
224     clearSelection();
225     setCurrentIndex(index);
226     scrollToBottom();
227 }
228 
toNextIndex()229 void ItemViewCategorized::toNextIndex()
230 {
231     toIndex(moveCursor(MoveNext, Qt::NoModifier));
232 }
233 
toPreviousIndex()234 void ItemViewCategorized::toPreviousIndex()
235 {
236     toIndex(moveCursor(MovePrevious, Qt::NoModifier));
237 }
238 
toIndex(const QModelIndex & index)239 void ItemViewCategorized::toIndex(const QModelIndex& index)
240 {
241     if (!index.isValid())
242     {
243         return;
244     }
245 
246     clearSelection();
247     setCurrentIndex(index);
248     scrollTo(index);
249 }
250 
awayFromSelection()251 void ItemViewCategorized::awayFromSelection()
252 {
253     QItemSelection selection = selectionModel()->selection();
254 
255     if (selection.isEmpty())
256     {
257         return;
258     }
259 
260     const QModelIndex first = model()->index(0, 0);
261     const QModelIndex last  = model()->index(model()->rowCount() - 1, 0);
262 
263     if (selection.contains(first) && selection.contains(last))
264     {
265         QItemSelection remaining(first, last);
266         remaining.merge(selection, QItemSelectionModel::Toggle);
267         QList<QModelIndex> indexes = remaining.indexes();
268 
269         if (indexes.isEmpty())
270         {
271             clearSelection();
272             setCurrentIndex(QModelIndex());
273         }
274         else
275         {
276             toIndex(remaining.indexes().first());
277         }
278     }
279     else if (selection.contains(last))
280     {
281         setCurrentIndex(selection.indexes().first());
282         toPreviousIndex();
283     }
284     else
285     {
286         setCurrentIndex(selection.indexes().last());
287         toNextIndex();
288     }
289 }
290 
scrollToRelaxed(const QModelIndex & index,QAbstractItemView::ScrollHint hint)291 void ItemViewCategorized::scrollToRelaxed(const QModelIndex& index, QAbstractItemView::ScrollHint hint)
292 {
293     if (viewport()->rect().intersects(visualRect(index)))
294     {
295         return;
296     }
297 
298     scrollTo(index, hint);
299 }
300 
invertSelection()301 void ItemViewCategorized::invertSelection()
302 {
303     const QModelIndex topLeft     = model()->index(0, 0);
304     const QModelIndex bottomRight = model()->index(model()->rowCount() - 1, 0);
305 
306     const QItemSelection selection(topLeft, bottomRight);
307     selectionModel()->select(selection, QItemSelectionModel::Toggle);
308 }
309 
setSelectedIndexes(const QList<QModelIndex> & indexes)310 void ItemViewCategorized::setSelectedIndexes(const QList<QModelIndex>& indexes)
311 {
312     if (selectedIndexes() == indexes)
313     {
314         return;
315     }
316 
317     QItemSelection mySelection;
318 
319     foreach (const QModelIndex& index, indexes)
320     {
321         mySelection.select(index, index);
322     }
323 
324     selectionModel()->select(mySelection, QItemSelectionModel::ClearAndSelect);
325 }
326 
setToolTipEnabled(bool enable)327 void ItemViewCategorized::setToolTipEnabled(bool enable)
328 {
329     d->showToolTip = enable;
330 }
331 
isToolTipEnabled() const332 bool ItemViewCategorized::isToolTipEnabled() const
333 {
334     return d->showToolTip;
335 }
336 
slotThemeChanged()337 void ItemViewCategorized::slotThemeChanged()
338 {
339     viewport()->update();
340 }
341 
slotSetupChanged()342 void ItemViewCategorized::slotSetupChanged()
343 {
344     viewport()->update();
345 }
346 
slotGridSizeChanged(const QSize & gridSize)347 void ItemViewCategorized::slotGridSizeChanged(const QSize& gridSize)
348 {
349     setGridSize(gridSize);
350 
351     if (!gridSize.isNull())
352     {
353         horizontalScrollBar()->setSingleStep(gridSize.width() / d->scrollStepFactor);
354         verticalScrollBar()->setSingleStep(gridSize.height()  / d->scrollStepFactor);
355     }
356 }
357 
updateDelegateSizes()358 void ItemViewCategorized::updateDelegateSizes()
359 {
360     QStyleOptionViewItem option = viewOptions();
361 /*
362     int frameAroundContents = 0;
363 
364     if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents))
365     {
366         frameAroundContents = style()->pixelMetric(QStyle::PM_DefaultFrameWidth) * 2;
367     }
368 
369     const int contentWidth  = viewport()->width() - 1
370                               - frameAroundContents
371                               - style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, verticalScrollBar());
372     const int contentHeight = viewport()->height() - 1
373                               - frameAroundContents
374                               - style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, horizontalScrollBar());
375     option.rect             = QRect(0, 0, contentWidth, contentHeight);
376 */
377     option.rect = QRect(QPoint(0, 0), viewport()->size());
378     d->delegate->setDefaultViewOptions(option);
379 }
380 
slotActivated(const QModelIndex & index)381 void ItemViewCategorized::slotActivated(const QModelIndex& index)
382 {
383     Qt::KeyboardModifiers modifiers = Qt::NoModifier;
384     Qt::MouseButtons buttons        = Qt::NoButton;
385     (void)modifiers; // prevent cppcheck warning.
386     (void)buttons;   // prevent cppcheck warning.
387 
388     if (d->currentMouseEvent)
389     {
390         modifiers = d->currentMouseEvent->modifiers();
391         buttons   = d->currentMouseEvent->buttons();
392     }
393     else
394     {
395         modifiers = QApplication::queryKeyboardModifiers();
396         buttons   = QApplication::mouseButtons();
397     }
398 
399     // Ignore activation if Ctrl or Shift is pressed (for selection)
400 
401     const bool shiftKeyPressed   = modifiers & Qt::ShiftModifier;
402     const bool controlKeyPressed = modifiers & Qt::ControlModifier;
403     const bool rightClickPressed = buttons & Qt::RightButton;
404 
405     if (shiftKeyPressed || controlKeyPressed || rightClickPressed)
406     {
407         return;
408     }
409 
410     if (d->currentMouseEvent)
411     {
412         // if the activation is caused by mouse click (not keyboard)
413         // we need to check the hot area
414 
415         if (d->currentMouseEvent->isAccepted() &&
416             !d->delegate->acceptsActivation(d->currentMouseEvent->pos(), visualRect(index), index))
417         {
418             return;
419         }
420     }
421 
422     d->currentMouseEvent = nullptr;
423     indexActivated(index, modifiers);
424 }
425 
slotClicked(const QModelIndex & index)426 void ItemViewCategorized::slotClicked(const QModelIndex& index)
427 {
428     if (d->currentMouseEvent)
429     {
430         emit clicked(d->currentMouseEvent, index);
431     }
432 }
433 
slotEntered(const QModelIndex & index)434 void ItemViewCategorized::slotEntered(const QModelIndex& index)
435 {
436     if (d->currentMouseEvent)
437     {
438         emit entered(d->currentMouseEvent, index);
439     }
440 }
441 
reset()442 void ItemViewCategorized::reset()
443 {
444     DCategorizedView::reset();
445 
446     // FIXME : Emitting this causes a crash importstackedview, because the model is not yet set.
447     //         atm there's a check against null models though.
448 
449     emit selectionChanged();
450     emit selectionCleared();
451 
452     d->ensureInitialSelectedItem = true;
453     d->hintAtScrollPosition      = QModelIndex();
454     d->hintAtSelectionIndex      = QModelIndex();
455     d->hintAtSelectionRow        = -1;
456     verticalScrollBar()->setValue(verticalScrollBar()->minimum());
457     horizontalScrollBar()->setValue(horizontalScrollBar()->minimum());
458 }
459 
selectionChanged(const QItemSelection & selectedItems,const QItemSelection & deselectedItems)460 void ItemViewCategorized::selectionChanged(const QItemSelection& selectedItems, const QItemSelection& deselectedItems)
461 {
462     DCategorizedView::selectionChanged(selectedItems, deselectedItems);
463 
464     emit selectionChanged();
465 
466     if (!selectionModel()->hasSelection())
467     {
468         emit selectionCleared();
469     }
470 
471     userInteraction();
472 }
473 
rowsInserted(const QModelIndex & parent,int start,int end)474 void ItemViewCategorized::rowsInserted(const QModelIndex& parent, int start, int end)
475 {
476     DCategorizedView::rowsInserted(parent, start, end);
477 
478     if (start == 0)
479     {
480         ensureSelectionAfterChanges();
481     }
482 }
483 
rowsRemoved(const QModelIndex & parent,int start,int end)484 void ItemViewCategorized::rowsRemoved(const QModelIndex& parent, int start, int end)
485 {
486     DCategorizedView::rowsRemoved(parent, start, end);
487 
488     if (d->scrollCurrentToCenter)
489     {
490         scrollTo(currentIndex(), QAbstractItemView::PositionAtCenter);
491     }
492 }
493 
rowsAboutToBeRemoved(const QModelIndex & parent,int start,int end)494 void ItemViewCategorized::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
495 {
496     DCategorizedView::rowsAboutToBeRemoved(parent, start, end);
497 
498     // Ensure one selected item
499 
500     int totalToRemove  = end - start + 1;
501     bool remainingRows = model()->rowCount(parent) > totalToRemove;
502 
503     if (!remainingRows)
504     {
505         return;
506     }
507 
508     QItemSelection removed(model()->index(start, 0), model()->index(end, 0));
509 
510     if (selectionModel()->hasSelection())
511     {
512         // find out which selected indexes are left after rows are removed
513 
514         QItemSelection selected = selectionModel()->selection();
515         QModelIndex current     = currentIndex();
516         QModelIndex indexToAnchor;
517 
518         if      (selected.contains(current))
519         {
520             indexToAnchor = current;
521         }
522         else if (!selected.isEmpty())
523         {
524             indexToAnchor = selected.first().topLeft();
525         }
526 
527         selected.merge(removed, QItemSelectionModel::Deselect);
528 
529         if (selected.isEmpty())
530         {
531             QModelIndex newCurrent = nextIndexHint(indexToAnchor, removed.first() /*a range*/);
532             setCurrentIndex(newCurrent);
533         }
534     }
535 
536     QModelIndex hint = d->scrollPositionHint();
537 
538     if (removed.contains(hint))
539     {
540         d->hintAtScrollPosition = nextIndexHint(hint, removed.first() /*a range*/);
541     }
542 }
543 
layoutAboutToBeChanged()544 void ItemViewCategorized::layoutAboutToBeChanged()
545 {
546     if (selectionModel())
547     {
548         d->ensureOneSelectedItem = selectionModel()->hasSelection();
549     }
550     else
551     {
552         qCWarning(DIGIKAM_GENERAL_LOG) << "Called without selection model, check whether the models are ok..";
553     }
554 
555     QModelIndex current = currentIndex();
556 
557     // store some hints so that if all selected items were removed do not need to default to 0,0.
558 
559     if (d->ensureOneSelectedItem)
560     {
561         QItemSelection currentSelection = selectionModel()->selection();
562         QModelIndex indexToAnchor;
563 
564         if      (currentSelection.contains(current))
565         {
566             indexToAnchor = current;
567         }
568         else if (!currentSelection.isEmpty())
569         {
570             indexToAnchor = currentSelection.first().topLeft();
571         }
572 
573         if (indexToAnchor.isValid())
574         {
575             d->hintAtSelectionRow   = indexToAnchor.row();
576             d->hintAtSelectionIndex = nextIndexHint(indexToAnchor, QItemSelectionRange(indexToAnchor));
577         }
578     }
579 
580     // some precautions to keep current scroll position
581 
582     d->hintAtScrollPosition = d->scrollPositionHint();
583 }
584 
nextIndexHint(const QModelIndex & indexToAnchor,const QItemSelectionRange & removed) const585 QModelIndex ItemViewCategorized::nextIndexHint(const QModelIndex& indexToAnchor, const QItemSelectionRange& removed) const
586 {
587     Q_UNUSED(indexToAnchor);
588 
589     if (removed.bottomRight().row() == (model()->rowCount() - 1))
590     {
591         if (removed.topLeft().row() == 0)
592         {
593             return QModelIndex();
594         }
595 
596         return model()->index(removed.topLeft().row() - 1, 0);    // last remaining, no next one left
597     }
598     else
599     {
600         return model()->index(removed.bottomRight().row() + 1, 0);    // next remaining
601     }
602 }
603 
layoutWasChanged()604 void ItemViewCategorized::layoutWasChanged()
605 {
606     // connected queued to layoutChanged()
607 
608     ensureSelectionAfterChanges();
609 
610     if (d->hintAtScrollPosition.isValid())
611     {
612         scrollToRelaxed(d->hintAtScrollPosition);
613         d->hintAtScrollPosition = QModelIndex();
614     }
615     else
616     {
617         scrollToRelaxed(currentIndex());
618     }
619 }
620 
userInteraction()621 void ItemViewCategorized::userInteraction()
622 {
623     // as soon as the user did anything affecting selection, we don't interfere anymore
624 
625     d->ensureInitialSelectedItem = false;
626     d->hintAtSelectionIndex      = QModelIndex();
627 }
628 
ensureSelectionAfterChanges()629 void ItemViewCategorized::ensureSelectionAfterChanges()
630 {
631     if      (d->ensureInitialSelectedItem && model()->rowCount())
632     {
633         // Ensure the item (0,0) is selected, if the model was reset previously
634         // and the user did not change the selection since reset.
635         // Caveat: Item at (0,0) may have changed.
636 
637         bool hadInitial              = d->ensureInitialSelectedItem;
638         d->ensureInitialSelectedItem = false;
639         d->ensureOneSelectedItem     = false;
640         QModelIndex index            = model()->index(0, 0);
641 
642         if (index.isValid())
643         {
644             selectionModel()->select(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Clear);
645             setCurrentIndex(index);
646 
647             // we want ensureInitial set to false if and only if the selection
648             // is done from any other place than the previous line (i.e., by user action)
649             // Effect: we select whatever is the current index(0,0)
650 
651             if (hadInitial)
652             {
653                 d->ensureInitialSelectedItem = true;
654             }
655         }
656     }
657     else if (d->ensureOneSelectedItem)
658     {
659         // ensure we have a selection if there was one before
660 
661         d->ensureOneSelectedItem = false;
662 
663         if (model()->rowCount() && selectionModel()->selection().isEmpty())
664         {
665             QModelIndex index;
666 
667             if      (d->hintAtSelectionIndex.isValid())
668             {
669                 index = d->hintAtSelectionIndex;
670             }
671             else if (d->hintAtSelectionRow != -1)
672             {
673                 index = model()->index(qMin(model()->rowCount(), d->hintAtSelectionRow), 0);
674             }
675             else
676             {
677                 index = currentIndex();
678             }
679 
680             if (!index.isValid())
681             {
682                 index = model()->index(0, 0);
683             }
684 
685             d->hintAtSelectionRow   = -1;
686             d->hintAtSelectionIndex = QModelIndex();
687 
688             if (index.isValid())
689             {
690                 setCurrentIndex(index);
691                 selectionModel()->select(index, QItemSelectionModel::SelectCurrent);
692             }
693         }
694     }
695 }
696 
indexForCategoryAt(const QPoint & pos) const697 QModelIndex ItemViewCategorized::indexForCategoryAt(const QPoint& pos) const
698 {
699     return categoryAt(pos);
700 }
701 
moveCursor(CursorAction cursorAction,Qt::KeyboardModifiers modifiers)702 QModelIndex ItemViewCategorized::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
703 {
704     QModelIndex current = currentIndex();
705 
706     if (!current.isValid())
707     {
708         return DCategorizedView::moveCursor(cursorAction, modifiers);
709     }
710 
711     // We want a simple wrapping navigation.
712     // Default behavior we do not want: right/left does never change row; Next/Previous is equivalent to Down/Up
713 
714     switch (cursorAction)
715     {
716         case MoveNext:
717         case MoveRight:
718         {
719             QModelIndex next = model()->index(current.row() + 1, 0);
720 
721             if (next.isValid())
722             {
723                 return next;
724             }
725             else
726             {
727                 return current;
728             }
729 
730             break;
731         }
732 
733         case MovePrevious:
734         case MoveLeft:
735         {
736             QModelIndex previous = model()->index(current.row() - 1, 0);
737 
738             if (previous.isValid())
739             {
740                 return previous;
741             }
742             else
743             {
744                 return current;
745             }
746 
747             break;
748         }
749 
750         default:
751         {
752             break;
753         }
754     }
755 
756     return DCategorizedView::moveCursor(cursorAction, modifiers);
757 }
758 
759 
showContextMenuOnIndex(QContextMenuEvent *,const QModelIndex &)760 void ItemViewCategorized::showContextMenuOnIndex(QContextMenuEvent*, const QModelIndex&)
761 {
762     // implemented in subclass
763 }
764 
showContextMenu(QContextMenuEvent *)765 void ItemViewCategorized::showContextMenu(QContextMenuEvent*)
766 {
767     // implemented in subclass
768 }
769 
indexActivated(const QModelIndex &,Qt::KeyboardModifiers)770 void ItemViewCategorized::indexActivated(const QModelIndex&, Qt::KeyboardModifiers)
771 {
772 }
773 
showToolTip(const QModelIndex & index,QStyleOptionViewItem & option,QHelpEvent * he)774 bool ItemViewCategorized::showToolTip(const QModelIndex& index, QStyleOptionViewItem& option, QHelpEvent* he)
775 {
776     QRect  innerRect;
777     QPoint pos;
778 
779     if (he)
780     {
781         pos = he->pos();
782     }
783     else
784     {
785         pos = option.rect.center();
786     }
787 
788     if (d->delegate->acceptsToolTip(pos, option.rect, index, &innerRect))
789     {
790         if (!innerRect.isNull())
791         {
792             option.rect = innerRect;
793         }
794 
795         d->toolTip->show(option, index);
796 
797         return true;
798     }
799 
800     return false;
801 }
802 
contextMenuEvent(QContextMenuEvent * event)803 void ItemViewCategorized::contextMenuEvent(QContextMenuEvent* event)
804 {
805     userInteraction();
806     QModelIndex index = indexAt(event->pos());
807 
808     if (index.isValid())
809     {
810         showContextMenuOnIndex(event, index);
811     }
812     else
813     {
814         showContextMenu(event);
815     }
816 }
817 
leaveEvent(QEvent *)818 void ItemViewCategorized::leaveEvent(QEvent*)
819 {
820     hideIndexNotification();
821 
822     if (d->mouseButtonPressed != Qt::RightButton)
823     {
824         d->mouseButtonPressed = Qt::NoButton;
825     }
826 }
827 
mousePressEvent(QMouseEvent * event)828 void ItemViewCategorized::mousePressEvent(QMouseEvent* event)
829 {
830     userInteraction();
831 
832     const QModelIndex index         = indexAt(event->pos());
833 
834     // Clear selection on click on empty area. Standard behavior, but not done by QAbstractItemView for some reason.
835 
836     Qt::KeyboardModifiers modifiers = event->modifiers();
837     const Qt::MouseButton button    = event->button();
838     const bool rightButtonPressed   = button & Qt::RightButton;
839     const bool shiftKeyPressed      = modifiers & Qt::ShiftModifier;
840     const bool controlKeyPressed    = modifiers & Qt::ControlModifier;
841     d->mouseButtonPressed           = button;
842 
843     if (!index.isValid() && !rightButtonPressed && !shiftKeyPressed && !controlKeyPressed)
844     {
845         clearSelection();
846     }
847 
848     // store event for entered(), clicked(), activated() signal handlers
849 
850     if (!rightButtonPressed)
851     {
852         d->currentMouseEvent = event;
853     }
854     else
855     {
856         d->currentMouseEvent = nullptr;
857     }
858 
859     DCategorizedView::mousePressEvent(event);
860 
861     if (!index.isValid())
862     {
863         emit viewportClicked(event);
864     }
865 }
866 
mouseReleaseEvent(QMouseEvent * event)867 void ItemViewCategorized::mouseReleaseEvent(QMouseEvent* event)
868 {
869     userInteraction();
870 
871     if (d->scrollCurrentToCenter)
872     {
873         scrollTo(currentIndex(), QAbstractItemView::PositionAtCenter);
874     }
875 
876     DCategorizedView::mouseReleaseEvent(event);
877 }
878 
mouseMoveEvent(QMouseEvent * event)879 void ItemViewCategorized::mouseMoveEvent(QMouseEvent* event)
880 {
881     QModelIndex index = indexAt(event->pos());
882     QRect indexVisualRect;
883 
884     if (index.isValid())
885     {
886         indexVisualRect = visualRect(index);
887 
888         if (d->usePointingHand &&
889             d->delegate->acceptsActivation(event->pos(), indexVisualRect, index))
890         {
891             setCursor(Qt::PointingHandCursor);
892         }
893         else
894         {
895             unsetCursor();
896         }
897     }
898     else
899     {
900         unsetCursor();
901     }
902 
903     if (d->notificationToolTip && d->notificationToolTip->isVisible())
904     {
905         if (!d->notificationToolTip->rect().adjusted(-50, -50, 50, 50).contains(event->pos()))
906         {
907             hideIndexNotification();
908         }
909     }
910 
911     DCategorizedView::mouseMoveEvent(event);
912 
913     d->delegate->mouseMoved(event, indexVisualRect, index);
914 }
915 
wheelEvent(QWheelEvent * event)916 void ItemViewCategorized::wheelEvent(QWheelEvent* event)
917 {
918     // DCategorizedView updates the single step at some occasions in a private methody
919 
920     horizontalScrollBar()->setSingleStep(d->delegate->gridSize().height() / d->scrollStepFactor);
921     verticalScrollBar()->setSingleStep(d->delegate->gridSize().width()    / d->scrollStepFactor);
922 
923     if (event->modifiers() & Qt::ControlModifier)
924     {
925         const int delta = event->angleDelta().y();
926 
927         if      (delta > 0)
928         {
929             emit zoomInStep();
930         }
931         else if (delta < 0)
932         {
933             emit zoomOutStep();
934         }
935 
936         event->accept();
937         return;
938     }
939 
940     if (verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff && event->orientation() == Qt::Vertical)
941     {
942         QWheelEvent n(event->pos(), event->globalPos(), event->angleDelta().y(),
943                       event->buttons(), event->modifiers(), Qt::Horizontal);
944         QApplication::sendEvent(horizontalScrollBar(), &n);
945         event->setAccepted(n.isAccepted());
946     }
947     else
948     {
949         DCategorizedView::wheelEvent(event);
950     }
951 }
952 
keyPressEvent(QKeyEvent * event)953 void ItemViewCategorized::keyPressEvent(QKeyEvent* event)
954 {
955     userInteraction();
956 
957     if      (event == QKeySequence::Copy)
958     {
959         copy();
960         event->accept();
961         return;
962     }
963     else if (event == QKeySequence::Paste)
964     {
965         paste();
966         event->accept();
967         return;
968     }
969 
970 /*
971     // from dolphincontroller.cpp
972     const QItemSelectionModel* selModel = m_itemView->selectionModel();
973     const QModelIndex currentIndex      = selModel->currentIndex();
974     const bool trigger                  = currentIndex.isValid() &&
975                                           ((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Enter)) &&
976                                           (selModel->selectedIndexes().count() > 0);
977     if (trigger)
978     {
979         const QModelIndexList indexList = selModel->selectedIndexes();
980         foreach (const QModelIndex& index, indexList)
981         {
982             emit itemTriggered(itemForIndex(index));
983         }
984     }
985 */
986     DCategorizedView::keyPressEvent(event);
987 
988     emit keyPressed(event);
989 }
990 
resizeEvent(QResizeEvent * e)991 void ItemViewCategorized::resizeEvent(QResizeEvent* e)
992 {
993     QModelIndex oldPosition = d->scrollPositionHint();
994     DCategorizedView::resizeEvent(e);
995     updateDelegateSizes();
996     scrollToRelaxed(oldPosition, QAbstractItemView::PositionAtTop);
997 }
998 
viewportEvent(QEvent * event)999 bool ItemViewCategorized::viewportEvent(QEvent* event)
1000 {
1001     switch (event->type())
1002     {
1003         case QEvent::FontChange:
1004         {
1005             updateDelegateSizes();
1006             break;
1007         }
1008 
1009         case QEvent::ToolTip:
1010         {
1011             if (!d->showToolTip)
1012             {
1013                 return true;
1014             }
1015 
1016             QHelpEvent* he          = static_cast<QHelpEvent*>(event);
1017             const QModelIndex index = indexAt(he->pos());
1018 
1019             if (!index.isValid())
1020             {
1021                 break;
1022             }
1023 
1024             QStyleOptionViewItem option =  viewOptions();
1025             option.rect                 =  visualRect(index);
1026             option.state               |= ((index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None);
1027             showToolTip(index, option, he);
1028             return true;
1029         }
1030 
1031         default:
1032         {
1033             break;
1034         }
1035     }
1036 
1037     return DCategorizedView::viewportEvent(event);
1038 }
1039 
showIndexNotification(const QModelIndex & index,const QString & message)1040 void ItemViewCategorized::showIndexNotification(const QModelIndex& index, const QString& message)
1041 {
1042     hideIndexNotification();
1043 
1044     if (!index.isValid())
1045     {
1046         return;
1047     }
1048 
1049     if (!d->notificationToolTip)
1050     {
1051         d->notificationToolTip = new ItemViewToolTip(this);
1052     }
1053 
1054     d->notificationToolTip->setTipContents(message);
1055 
1056     QStyleOptionViewItem option = viewOptions();
1057     option.rect                 = visualRect(index);
1058     option.state               |= ((index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None);
1059     d->notificationToolTip->show(option, index);
1060 }
1061 
hideIndexNotification()1062 void ItemViewCategorized::hideIndexNotification()
1063 {
1064     if (d->notificationToolTip)
1065     {
1066         d->notificationToolTip->hide();
1067     }
1068 }
1069 
1070 /**
1071  * cut(), copy(), paste(), dragEnterEvent(), dragMoveEvent(), dropEvent(), startDrag()
1072  * are implemented by DragDropViewImplementation
1073  */
mapIndexForDragDrop(const QModelIndex & index) const1074 QModelIndex ItemViewCategorized::mapIndexForDragDrop(const QModelIndex& index) const
1075 {
1076     return filterModel()->mapToSource(index);
1077 }
1078 
pixmapForDrag(const QList<QModelIndex> & indexes) const1079 QPixmap ItemViewCategorized::pixmapForDrag(const QList<QModelIndex>& indexes) const
1080 {
1081     QStyleOptionViewItem option = viewOptions();
1082     option.rect                 = viewport()->rect();
1083 
1084     return d->delegate->pixmapForDrag(option, indexes);
1085 }
1086 
setScrollCurrentToCenter(bool enabled)1087 void ItemViewCategorized::setScrollCurrentToCenter(bool enabled)
1088 {
1089     d->scrollCurrentToCenter = enabled;
1090 }
1091 
scrollTo(const QModelIndex & index,ScrollHint hint)1092 void ItemViewCategorized::scrollTo(const QModelIndex& index, ScrollHint hint)
1093 {
1094     if (d->scrollCurrentToCenter && (d->mouseButtonPressed == Qt::NoButton))
1095     {
1096         hint = QAbstractItemView::PositionAtCenter;
1097     }
1098 
1099     d->mouseButtonPressed = Qt::NoButton;
1100     DCategorizedView::scrollTo(index, hint);
1101 }
1102 
1103 } // namespace Digikam
1104