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