1 /*
2  * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
3  * SPDX-FileCopyrightText: 2012 Frank Reininghaus <frank78ac@googlemail.com>
4  *
5  * Based on the Itemviews NG project from Trolltech Labs
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  */
9 
10 #include "kitemlistcontroller.h"
11 
12 #include "kitemlistselectionmanager.h"
13 #include "kitemlistview.h"
14 #include "private/kitemlistkeyboardsearchmanager.h"
15 #include "private/kitemlistrubberband.h"
16 #include "private/ktwofingerswipe.h"
17 #include "private/ktwofingertap.h"
18 #include "views/draganddrophelper.h"
19 
20 #include <QAccessible>
21 #include <QApplication>
22 #include <QDrag>
23 #include <QGesture>
24 #include <QGraphicsScene>
25 #include <QGraphicsSceneEvent>
26 #include <QGraphicsView>
27 #include <QMimeData>
28 #include <QTimer>
29 #include <QTouchEvent>
30 
KItemListController(KItemModelBase * model,KItemListView * view,QObject * parent)31 KItemListController::KItemListController(KItemModelBase* model, KItemListView* view, QObject* parent) :
32     QObject(parent),
33     m_singleClickActivationEnforced(false),
34     m_selectionTogglePressed(false),
35     m_clearSelectionIfItemsAreNotDragged(false),
36     m_isSwipeGesture(false),
37     m_dragActionOrRightClick(false),
38     m_scrollerIsScrolling(false),
39     m_pinchGestureInProgress(false),
40     m_mousePress(false),
41     m_isTouchEvent(false),
42     m_selectionBehavior(NoSelection),
43     m_autoActivationBehavior(ActivationAndExpansion),
44     m_mouseDoubleClickAction(ActivateItemOnly),
45     m_model(nullptr),
46     m_view(nullptr),
47     m_selectionManager(new KItemListSelectionManager(this)),
48     m_keyboardManager(new KItemListKeyboardSearchManager(this)),
49     m_pressedIndex(-1),
50     m_pressedMousePos(),
51     m_autoActivationTimer(nullptr),
52     m_swipeGesture(Qt::CustomGesture),
53     m_twoFingerTapGesture(Qt::CustomGesture),
54     m_oldSelection(),
55     m_keyboardAnchorIndex(-1),
56     m_keyboardAnchorPos(0)
57 {
58     connect(m_keyboardManager, &KItemListKeyboardSearchManager::changeCurrentItem,
59             this, &KItemListController::slotChangeCurrentItem);
60     connect(m_selectionManager, &KItemListSelectionManager::currentChanged,
61             m_keyboardManager, &KItemListKeyboardSearchManager::slotCurrentChanged);
62     connect(m_selectionManager, &KItemListSelectionManager::selectionChanged,
63             m_keyboardManager, &KItemListKeyboardSearchManager::slotSelectionChanged);
64 
65     m_autoActivationTimer = new QTimer(this);
66     m_autoActivationTimer->setSingleShot(true);
67     m_autoActivationTimer->setInterval(-1);
68     connect(m_autoActivationTimer, &QTimer::timeout, this, &KItemListController::slotAutoActivationTimeout);
69 
70     setModel(model);
71     setView(view);
72 
73     m_swipeGesture = QGestureRecognizer::registerRecognizer(new KTwoFingerSwipeRecognizer());
74     m_twoFingerTapGesture = QGestureRecognizer::registerRecognizer(new KTwoFingerTapRecognizer());
75     view->grabGesture(m_swipeGesture);
76     view->grabGesture(m_twoFingerTapGesture);
77     view->grabGesture(Qt::TapGesture);
78     view->grabGesture(Qt::TapAndHoldGesture);
79     view->grabGesture(Qt::PinchGesture);
80 }
81 
~KItemListController()82 KItemListController::~KItemListController()
83 {
84     setView(nullptr);
85     Q_ASSERT(!m_view);
86 
87     setModel(nullptr);
88     Q_ASSERT(!m_model);
89 }
90 
setModel(KItemModelBase * model)91 void KItemListController::setModel(KItemModelBase* model)
92 {
93     if (m_model == model) {
94         return;
95     }
96 
97     KItemModelBase* oldModel = m_model;
98     if (oldModel) {
99         oldModel->deleteLater();
100     }
101 
102     m_model = model;
103     if (m_model) {
104         m_model->setParent(this);
105     }
106 
107     if (m_view) {
108         m_view->setModel(m_model);
109     }
110 
111     m_selectionManager->setModel(m_model);
112 
113     Q_EMIT modelChanged(m_model, oldModel);
114 }
115 
model() const116 KItemModelBase* KItemListController::model() const
117 {
118     return m_model;
119 }
120 
selectionManager() const121 KItemListSelectionManager* KItemListController::selectionManager() const
122 {
123     return m_selectionManager;
124 }
125 
setView(KItemListView * view)126 void KItemListController::setView(KItemListView* view)
127 {
128     if (m_view == view) {
129         return;
130     }
131 
132     KItemListView* oldView = m_view;
133     if (oldView) {
134         disconnect(oldView, &KItemListView::scrollOffsetChanged, this, &KItemListController::slotViewScrollOffsetChanged);
135         oldView->deleteLater();
136     }
137 
138     m_view = view;
139 
140     if (m_view) {
141         m_view->setParent(this);
142         m_view->setController(this);
143         m_view->setModel(m_model);
144         connect(m_view, &KItemListView::scrollOffsetChanged, this, &KItemListController::slotViewScrollOffsetChanged);
145         updateExtendedSelectionRegion();
146     }
147 
148     Q_EMIT viewChanged(m_view, oldView);
149 }
150 
view() const151 KItemListView* KItemListController::view() const
152 {
153     return m_view;
154 }
155 
setSelectionBehavior(SelectionBehavior behavior)156 void KItemListController::setSelectionBehavior(SelectionBehavior behavior)
157 {
158     m_selectionBehavior = behavior;
159     updateExtendedSelectionRegion();
160 }
161 
selectionBehavior() const162 KItemListController::SelectionBehavior KItemListController::selectionBehavior() const
163 {
164     return m_selectionBehavior;
165 }
166 
setAutoActivationBehavior(AutoActivationBehavior behavior)167 void KItemListController::setAutoActivationBehavior(AutoActivationBehavior behavior)
168 {
169     m_autoActivationBehavior = behavior;
170 }
171 
autoActivationBehavior() const172 KItemListController::AutoActivationBehavior KItemListController::autoActivationBehavior() const
173 {
174     return m_autoActivationBehavior;
175 }
176 
setMouseDoubleClickAction(MouseDoubleClickAction action)177 void KItemListController::setMouseDoubleClickAction(MouseDoubleClickAction action)
178 {
179     m_mouseDoubleClickAction = action;
180 }
181 
mouseDoubleClickAction() const182 KItemListController::MouseDoubleClickAction KItemListController::mouseDoubleClickAction() const
183 {
184     return m_mouseDoubleClickAction;
185 }
186 
indexCloseToMousePressedPosition() const187 int KItemListController::indexCloseToMousePressedPosition() const
188 {
189     QHashIterator<KItemListWidget*, KItemListGroupHeader*> it(m_view->m_visibleGroups);
190     while (it.hasNext()) {
191         it.next();
192         KItemListGroupHeader *groupHeader = it.value();
193         const QPointF mappedToGroup = groupHeader->mapFromItem(nullptr, m_pressedMousePos);
194         if (groupHeader->contains(mappedToGroup)) {
195             return it.key()->index();
196         }
197     }
198     return -1;
199 }
200 
setAutoActivationDelay(int delay)201 void KItemListController::setAutoActivationDelay(int delay)
202 {
203     m_autoActivationTimer->setInterval(delay);
204 }
205 
autoActivationDelay() const206 int KItemListController::autoActivationDelay() const
207 {
208     return m_autoActivationTimer->interval();
209 }
210 
setSingleClickActivationEnforced(bool singleClick)211 void KItemListController::setSingleClickActivationEnforced(bool singleClick)
212 {
213     m_singleClickActivationEnforced = singleClick;
214 }
215 
singleClickActivationEnforced() const216 bool KItemListController::singleClickActivationEnforced() const
217 {
218     return m_singleClickActivationEnforced;
219 }
220 
keyPressEvent(QKeyEvent * event)221 bool KItemListController::keyPressEvent(QKeyEvent* event)
222 {
223     int index = m_selectionManager->currentItem();
224     int key = event->key();
225 
226     // Handle the expanding/collapsing of items
227     if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
228         if (key == Qt::Key_Right) {
229             if (m_model->setExpanded(index, true)) {
230                 return true;
231             }
232         } else if (key == Qt::Key_Left) {
233             if (m_model->setExpanded(index, false)) {
234                 return true;
235             }
236         }
237     }
238 
239     const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
240     const bool controlPressed = event->modifiers() & Qt::ControlModifier;
241     const bool shiftOrControlPressed = shiftPressed || controlPressed;
242     const bool navigationPressed = key == Qt::Key_Home || key == Qt::Key_End  ||
243                                    key == Qt::Key_PageUp || key == Qt::Key_PageDown ||
244                                    key == Qt::Key_Up || key == Qt::Key_Down ||
245                                    key == Qt::Key_Left || key == Qt::Key_Right;
246 
247     const int itemCount = m_model->count();
248 
249     // For horizontal scroll orientation, transform
250     // the arrow keys to simplify the event handling.
251     if (m_view->scrollOrientation() == Qt::Horizontal) {
252         switch (key) {
253         case Qt::Key_Up:    key = Qt::Key_Left; break;
254         case Qt::Key_Down:  key = Qt::Key_Right; break;
255         case Qt::Key_Left:  key = Qt::Key_Up; break;
256         case Qt::Key_Right: key = Qt::Key_Down; break;
257         default:            break;
258         }
259     }
260 
261     const bool selectSingleItem = m_selectionBehavior != NoSelection && itemCount == 1 && navigationPressed;
262 
263     if (selectSingleItem) {
264         const int current = m_selectionManager->currentItem();
265         m_selectionManager->setSelected(current);
266         return true;
267     }
268 
269     switch (key) {
270     case Qt::Key_Home:
271         index = 0;
272         m_keyboardAnchorIndex = index;
273         m_keyboardAnchorPos = keyboardAnchorPos(index);
274         break;
275 
276     case Qt::Key_End:
277         index = itemCount - 1;
278         m_keyboardAnchorIndex = index;
279         m_keyboardAnchorPos = keyboardAnchorPos(index);
280         break;
281 
282     case Qt::Key_Left:
283         if (index > 0) {
284             const int expandedParentsCount = m_model->expandedParentsCount(index);
285             if (expandedParentsCount == 0) {
286                 --index;
287             } else {
288                 // Go to the parent of the current item.
289                 do {
290                     --index;
291                 } while (index > 0 && m_model->expandedParentsCount(index) == expandedParentsCount);
292             }
293             m_keyboardAnchorIndex = index;
294             m_keyboardAnchorPos = keyboardAnchorPos(index);
295         }
296         break;
297 
298     case Qt::Key_Right:
299         if (index < itemCount - 1) {
300             ++index;
301             m_keyboardAnchorIndex = index;
302             m_keyboardAnchorPos = keyboardAnchorPos(index);
303         }
304         break;
305 
306     case Qt::Key_Up:
307         updateKeyboardAnchor();
308         index = previousRowIndex(index);
309         break;
310 
311     case Qt::Key_Down:
312         updateKeyboardAnchor();
313         index = nextRowIndex(index);
314         break;
315 
316     case Qt::Key_PageUp:
317         if (m_view->scrollOrientation() == Qt::Horizontal) {
318             // The new current index should correspond to the first item in the current column.
319             int newIndex = qMax(index - 1, 0);
320             while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() < m_view->itemRect(index).topLeft().y()) {
321                 index = newIndex;
322                 newIndex = qMax(index - 1, 0);
323             }
324             m_keyboardAnchorIndex = index;
325             m_keyboardAnchorPos = keyboardAnchorPos(index);
326         } else {
327             const qreal currentItemBottom = m_view->itemRect(index).bottomLeft().y();
328             const qreal height = m_view->geometry().height();
329 
330             // The new current item should be the first item in the current
331             // column whose itemRect's top coordinate is larger than targetY.
332             const qreal targetY = currentItemBottom - height;
333 
334             updateKeyboardAnchor();
335             int newIndex = previousRowIndex(index);
336             do {
337                 index = newIndex;
338                 updateKeyboardAnchor();
339                 newIndex = previousRowIndex(index);
340             } while (m_view->itemRect(newIndex).topLeft().y() > targetY && newIndex != index);
341         }
342         break;
343 
344     case Qt::Key_PageDown:
345         if (m_view->scrollOrientation() == Qt::Horizontal) {
346             // The new current index should correspond to the last item in the current column.
347             int newIndex = qMin(index + 1, m_model->count() - 1);
348             while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() > m_view->itemRect(index).topLeft().y()) {
349                 index = newIndex;
350                 newIndex = qMin(index + 1, m_model->count() - 1);
351             }
352             m_keyboardAnchorIndex = index;
353             m_keyboardAnchorPos = keyboardAnchorPos(index);
354         } else {
355             const qreal currentItemTop = m_view->itemRect(index).topLeft().y();
356             const qreal height = m_view->geometry().height();
357 
358             // The new current item should be the last item in the current
359             // column whose itemRect's bottom coordinate is smaller than targetY.
360             const qreal targetY = currentItemTop + height;
361 
362             updateKeyboardAnchor();
363             int newIndex = nextRowIndex(index);
364             do {
365                 index = newIndex;
366                 updateKeyboardAnchor();
367                 newIndex = nextRowIndex(index);
368             } while (m_view->itemRect(newIndex).bottomLeft().y() < targetY && newIndex != index);
369         }
370         break;
371 
372     case Qt::Key_Enter:
373     case Qt::Key_Return: {
374         const KItemSet selectedItems = m_selectionManager->selectedItems();
375         if (selectedItems.count() >= 2) {
376             Q_EMIT itemsActivated(selectedItems);
377         } else if (selectedItems.count() == 1) {
378             Q_EMIT itemActivated(selectedItems.first());
379         } else {
380             Q_EMIT itemActivated(index);
381         }
382         break;
383     }
384 
385     case Qt::Key_Menu: {
386         // Emit the signal itemContextMenuRequested() in case if at least one
387         // item is selected. Otherwise the signal viewContextMenuRequested() will be emitted.
388         const KItemSet selectedItems = m_selectionManager->selectedItems();
389         int index = -1;
390         if (selectedItems.count() >= 2) {
391             const int currentItemIndex = m_selectionManager->currentItem();
392             index = selectedItems.contains(currentItemIndex)
393                     ? currentItemIndex : selectedItems.first();
394         } else if (selectedItems.count() == 1) {
395             index = selectedItems.first();
396         }
397 
398         if (index >= 0) {
399             const QRectF contextRect = m_view->itemContextRect(index);
400             const QPointF pos(m_view->scene()->views().first()->mapToGlobal(contextRect.bottomRight().toPoint()));
401             Q_EMIT itemContextMenuRequested(index, pos);
402         } else {
403             Q_EMIT viewContextMenuRequested(QCursor::pos());
404         }
405         break;
406     }
407 
408     case Qt::Key_Escape:
409         if (m_selectionBehavior != SingleSelection) {
410             m_selectionManager->clearSelection();
411         }
412         m_keyboardManager->cancelSearch();
413         Q_EMIT escapePressed();
414         break;
415 
416     case Qt::Key_Space:
417         if (m_selectionBehavior == MultiSelection) {
418             if (controlPressed) {
419                 // Toggle the selection state of the current item.
420                 m_selectionManager->endAnchoredSelection();
421                 m_selectionManager->setSelected(index, 1, KItemListSelectionManager::Toggle);
422                 m_selectionManager->beginAnchoredSelection(index);
423                 break;
424             } else {
425                 // Select the current item if it is not selected yet.
426                 const int current = m_selectionManager->currentItem();
427                 if (!m_selectionManager->isSelected(current)) {
428                     m_selectionManager->setSelected(current);
429                     break;
430                 }
431             }
432         }
433         Q_FALLTHROUGH();  // fall through to the default case and add the Space to the current search string.
434     default:
435         m_keyboardManager->addKeys(event->text());
436         // Make sure unconsumed events get propagated up the chain. #302329
437         event->ignore();
438         return false;
439     }
440 
441     if (m_selectionManager->currentItem() != index) {
442         switch (m_selectionBehavior) {
443         case NoSelection:
444             m_selectionManager->setCurrentItem(index);
445             break;
446 
447         case SingleSelection:
448             m_selectionManager->setCurrentItem(index);
449             m_selectionManager->clearSelection();
450             m_selectionManager->setSelected(index, 1);
451             break;
452 
453         case MultiSelection:
454             if (controlPressed) {
455                 m_selectionManager->endAnchoredSelection();
456             }
457 
458             m_selectionManager->setCurrentItem(index);
459 
460             if (!shiftOrControlPressed) {
461                 m_selectionManager->clearSelection();
462                 m_selectionManager->setSelected(index, 1);
463             }
464 
465             if (!shiftPressed) {
466                 m_selectionManager->beginAnchoredSelection(index);
467             }
468             break;
469         }
470     }
471 
472     if (navigationPressed) {
473         m_view->scrollToItem(index);
474     }
475     return true;
476 }
477 
slotChangeCurrentItem(const QString & text,bool searchFromNextItem)478 void KItemListController::slotChangeCurrentItem(const QString& text, bool searchFromNextItem)
479 {
480     if (!m_model || m_model->count() == 0) {
481         return;
482     }
483     int index;
484     if (searchFromNextItem) {
485         const int currentIndex = m_selectionManager->currentItem();
486         index = m_model->indexForKeyboardSearch(text, (currentIndex + 1) % m_model->count());
487     } else {
488         index = m_model->indexForKeyboardSearch(text, 0);
489     }
490     if (index >= 0) {
491         m_selectionManager->setCurrentItem(index);
492 
493         if (m_selectionBehavior != NoSelection) {
494             m_selectionManager->replaceSelection(index);
495             m_selectionManager->beginAnchoredSelection(index);
496         }
497 
498         m_view->scrollToItem(index);
499     }
500 }
501 
slotAutoActivationTimeout()502 void KItemListController::slotAutoActivationTimeout()
503 {
504     if (!m_model || !m_view) {
505         return;
506     }
507 
508     const int index = m_autoActivationTimer->property("index").toInt();
509     if (index < 0 || index >= m_model->count()) {
510         return;
511     }
512 
513     /* m_view->isUnderMouse() fixes a bug in the Folder-View-Panel and in the
514      * Places-Panel.
515      *
516      * Bug: When you drag a file onto a Folder-View-Item or a Places-Item and
517      * then move away before the auto-activation timeout triggers, than the
518      * item still becomes activated/expanded.
519      *
520      * See Bug 293200 and 305783
521      */
522     if (m_model->supportsDropping(index) && m_view->isUnderMouse()) {
523         if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
524             const bool expanded = m_model->isExpanded(index);
525             m_model->setExpanded(index, !expanded);
526         } else if (m_autoActivationBehavior != ExpansionOnly) {
527             Q_EMIT itemActivated(index);
528         }
529     }
530 }
531 
inputMethodEvent(QInputMethodEvent * event)532 bool KItemListController::inputMethodEvent(QInputMethodEvent* event)
533 {
534     Q_UNUSED(event)
535     return false;
536 }
537 
mousePressEvent(QGraphicsSceneMouseEvent * event,const QTransform & transform)538 bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
539 {
540     m_mousePress = true;
541 
542     if (event->source() == Qt::MouseEventSynthesizedByQt && m_isTouchEvent) {
543         return false;
544     }
545 
546     if (!m_view) {
547         return false;
548     }
549 
550     m_pressedMousePos = transform.map(event->pos());
551     m_pressedIndex = m_view->itemAt(m_pressedMousePos);
552 
553     const Qt::MouseButtons buttons = event->buttons();
554 
555     if (!onPress(event->screenPos(), event->pos(), event->modifiers(), buttons)) {
556         startRubberBand();
557         return false;
558     }
559 
560     return true;
561 }
562 
mouseMoveEvent(QGraphicsSceneMouseEvent * event,const QTransform & transform)563 bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
564 {
565     if (!m_view) {
566         return false;
567     }
568 
569     if (m_view->m_tapAndHoldIndicator->isActive()) {
570         m_view->m_tapAndHoldIndicator->setActive(false);
571     }
572 
573     if (event->source() == Qt::MouseEventSynthesizedByQt && !m_dragActionOrRightClick && m_isTouchEvent) {
574         return false;
575     }
576 
577     if (m_pressedIndex >= 0) {
578         // Check whether a dragging should be started
579         if (event->buttons() & Qt::LeftButton) {
580             const QPointF pos = transform.map(event->pos());
581             if ((pos - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) {
582                 if (!m_selectionManager->isSelected(m_pressedIndex)) {
583                     // Always assure that the dragged item gets selected. Usually this is already
584                     // done on the mouse-press event, but when using the selection-toggle on a
585                     // selected item the dragged item is not selected yet.
586                     m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
587                 } else {
588                     // A selected item has been clicked to drag all selected items
589                     // -> the selection should not be cleared when the mouse button is released.
590                     m_clearSelectionIfItemsAreNotDragged = false;
591                 }
592                 startDragging();
593                 m_mousePress = false;
594             }
595         }
596     } else {
597         KItemListRubberBand* rubberBand = m_view->rubberBand();
598         if (rubberBand->isActive()) {
599             QPointF endPos = transform.map(event->pos());
600 
601             // Update the current item.
602             const int newCurrent = m_view->itemAt(endPos);
603             if (newCurrent >= 0) {
604                 // It's expected that the new current index is also the new anchor (bug 163451).
605                 m_selectionManager->endAnchoredSelection();
606                 m_selectionManager->setCurrentItem(newCurrent);
607                 m_selectionManager->beginAnchoredSelection(newCurrent);
608             }
609 
610             if (m_view->scrollOrientation() == Qt::Vertical) {
611                 endPos.ry() += m_view->scrollOffset();
612                 if (m_view->itemSize().width() < 0) {
613                     // Use a special rubberband for views that have only one column and
614                     // expand the rubberband to use the whole width of the view.
615                     endPos.setX(m_view->size().width());
616                 }
617             } else {
618                 endPos.rx() += m_view->scrollOffset();
619             }
620             rubberBand->setEndPosition(endPos);
621         }
622     }
623 
624     return false;
625 }
626 
mouseReleaseEvent(QGraphicsSceneMouseEvent * event,const QTransform & transform)627 bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
628 {
629     m_mousePress = false;
630     m_isTouchEvent = false;
631 
632     if (!m_view) {
633         return false;
634     }
635 
636     if (m_view->m_tapAndHoldIndicator->isActive()) {
637         m_view->m_tapAndHoldIndicator->setActive(false);
638     }
639 
640     KItemListRubberBand* rubberBand = m_view->rubberBand();
641     if (event->source() == Qt::MouseEventSynthesizedByQt && !rubberBand->isActive() && m_isTouchEvent) {
642         return false;
643     }
644 
645     Q_EMIT mouseButtonReleased(m_pressedIndex, event->buttons());
646 
647     return onRelease(transform.map(event->pos()), event->modifiers(), event->button(), false);
648 }
649 
mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event,const QTransform & transform)650 bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
651 {
652     const QPointF pos = transform.map(event->pos());
653     const int index = m_view->itemAt(pos);
654 
655     // Expand item if desired - See Bug 295573
656     if (m_mouseDoubleClickAction != ActivateItemOnly) {
657         if (m_view && m_model && m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
658             const bool expanded = m_model->isExpanded(index);
659             m_model->setExpanded(index, !expanded);
660         }
661     }
662 
663     if (event->button() & Qt::RightButton) {
664         m_selectionManager->clearSelection();
665         if (index >= 0) {
666             m_selectionManager->setSelected(index);
667             Q_EMIT itemContextMenuRequested(index, event->screenPos());
668         } else {
669             const QRectF headerBounds = m_view->headerBoundaries();
670             if (headerBounds.contains(event->pos())) {
671                 Q_EMIT headerContextMenuRequested(event->screenPos());
672             } else {
673                 Q_EMIT viewContextMenuRequested(event->screenPos());
674             }
675         }
676         return true;
677     }
678 
679     bool emitItemActivated = !(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) &&
680                              (event->button() & Qt::LeftButton) &&
681                              index >= 0 && index < m_model->count();
682     if (emitItemActivated) {
683         Q_EMIT itemActivated(index);
684     }
685     return false;
686 }
687 
dragEnterEvent(QGraphicsSceneDragDropEvent * event,const QTransform & transform)688 bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
689 {
690     Q_UNUSED(event)
691     Q_UNUSED(transform)
692 
693     DragAndDropHelper::clearUrlListMatchesUrlCache();
694 
695     return false;
696 }
697 
dragLeaveEvent(QGraphicsSceneDragDropEvent * event,const QTransform & transform)698 bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
699 {
700     Q_UNUSED(event)
701     Q_UNUSED(transform)
702 
703     m_autoActivationTimer->stop();
704     m_view->setAutoScroll(false);
705     m_view->hideDropIndicator();
706 
707     KItemListWidget* widget = hoveredWidget();
708     if (widget) {
709         widget->setHovered(false);
710         Q_EMIT itemUnhovered(widget->index());
711     }
712     return false;
713 }
714 
dragMoveEvent(QGraphicsSceneDragDropEvent * event,const QTransform & transform)715 bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
716 {
717     if (!m_model || !m_view) {
718         return false;
719     }
720 
721 
722     QUrl hoveredDir = m_model->directory();
723     KItemListWidget* oldHoveredWidget = hoveredWidget();
724 
725     const QPointF pos = transform.map(event->pos());
726     KItemListWidget* newHoveredWidget = widgetForPos(pos);
727 
728     if (oldHoveredWidget != newHoveredWidget) {
729         m_autoActivationTimer->stop();
730 
731         if (oldHoveredWidget) {
732             oldHoveredWidget->setHovered(false);
733             Q_EMIT itemUnhovered(oldHoveredWidget->index());
734         }
735     }
736 
737     if (newHoveredWidget) {
738         bool droppingBetweenItems = false;
739         if (m_model->sortRole().isEmpty()) {
740             // The model supports inserting items between other items.
741             droppingBetweenItems = (m_view->showDropIndicator(pos) >= 0);
742         }
743 
744         const int index = newHoveredWidget->index();
745 
746         if (m_model->isDir(index)) {
747             hoveredDir = m_model->url(index);
748         }
749 
750         if (!droppingBetweenItems) {
751             if (m_model->supportsDropping(index)) {
752                 // Something has been dragged on an item.
753                 m_view->hideDropIndicator();
754                 if (!newHoveredWidget->isHovered()) {
755                     newHoveredWidget->setHovered(true);
756                     Q_EMIT itemHovered(index);
757                 }
758 
759                 if (!m_autoActivationTimer->isActive() && m_autoActivationTimer->interval() >= 0) {
760                     m_autoActivationTimer->setProperty("index", index);
761                     m_autoActivationTimer->start();
762                 }
763             }
764         } else {
765             m_autoActivationTimer->stop();
766             if (newHoveredWidget && newHoveredWidget->isHovered()) {
767                 newHoveredWidget->setHovered(false);
768                 Q_EMIT itemUnhovered(index);
769             }
770         }
771     } else {
772         m_view->hideDropIndicator();
773     }
774 
775     if (DragAndDropHelper::urlListMatchesUrl(event->mimeData()->urls(), hoveredDir)) {
776         event->setDropAction(Qt::IgnoreAction);
777         event->ignore();
778     } else {
779         event->setDropAction(event->proposedAction());
780         event->accept();
781     }
782     return false;
783 }
784 
dropEvent(QGraphicsSceneDragDropEvent * event,const QTransform & transform)785 bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
786 {
787     if (!m_view) {
788         return false;
789     }
790 
791     m_autoActivationTimer->stop();
792     m_view->setAutoScroll(false);
793 
794     const QPointF pos = transform.map(event->pos());
795 
796     int dropAboveIndex = -1;
797     if (m_model->sortRole().isEmpty()) {
798         // The model supports inserting of items between other items.
799         dropAboveIndex = m_view->showDropIndicator(pos);
800     }
801 
802     if (dropAboveIndex >= 0) {
803         // Something has been dropped between two items.
804         m_view->hideDropIndicator();
805         Q_EMIT aboveItemDropEvent(dropAboveIndex, event);
806     } else if (!event->mimeData()->hasFormat(m_model->blacklistItemDropEventMimeType())) {
807         // Something has been dropped on an item or on an empty part of the view.
808         Q_EMIT itemDropEvent(m_view->itemAt(pos), event);
809     }
810 
811     QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropEnd);
812     QAccessible::updateAccessibility(&accessibilityEvent);
813 
814     return true;
815 }
816 
hoverEnterEvent(QGraphicsSceneHoverEvent * event,const QTransform & transform)817 bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
818 {
819     Q_UNUSED(event)
820     Q_UNUSED(transform)
821     return false;
822 }
823 
hoverMoveEvent(QGraphicsSceneHoverEvent * event,const QTransform & transform)824 bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
825 {
826     Q_UNUSED(transform)
827     if (!m_model || !m_view) {
828         return false;
829     }
830 
831     KItemListWidget* oldHoveredWidget = hoveredWidget();
832     const QPointF pos = transform.map(event->pos());
833     KItemListWidget* newHoveredWidget = widgetForPos(pos);
834 
835     if (oldHoveredWidget != newHoveredWidget) {
836         if (oldHoveredWidget) {
837             oldHoveredWidget->setHovered(false);
838             Q_EMIT itemUnhovered(oldHoveredWidget->index());
839         }
840 
841         if (newHoveredWidget) {
842             newHoveredWidget->setHovered(true);
843             const QPointF mappedPos = newHoveredWidget->mapFromItem(m_view, pos);
844             newHoveredWidget->setHoverPosition(mappedPos);
845             Q_EMIT itemHovered(newHoveredWidget->index());
846         }
847     } else if (oldHoveredWidget) {
848         const QPointF mappedPos = oldHoveredWidget->mapFromItem(m_view, pos);
849         oldHoveredWidget->setHoverPosition(mappedPos);
850     }
851 
852     return false;
853 }
854 
hoverLeaveEvent(QGraphicsSceneHoverEvent * event,const QTransform & transform)855 bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
856 {
857     Q_UNUSED(event)
858     Q_UNUSED(transform)
859 
860     m_mousePress = false;
861     m_isTouchEvent = false;
862 
863     if (!m_model || !m_view) {
864         return false;
865     }
866 
867     const auto widgets = m_view->visibleItemListWidgets();
868     for (KItemListWidget* widget : widgets) {
869         if (widget->isHovered()) {
870             widget->setHovered(false);
871             Q_EMIT itemUnhovered(widget->index());
872         }
873     }
874     return false;
875 }
876 
wheelEvent(QGraphicsSceneWheelEvent * event,const QTransform & transform)877 bool KItemListController::wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform)
878 {
879     Q_UNUSED(event)
880     Q_UNUSED(transform)
881     return false;
882 }
883 
resizeEvent(QGraphicsSceneResizeEvent * event,const QTransform & transform)884 bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent* event, const QTransform& transform)
885 {
886     Q_UNUSED(event)
887     Q_UNUSED(transform)
888     return false;
889 }
890 
gestureEvent(QGestureEvent * event,const QTransform & transform)891 bool KItemListController::gestureEvent(QGestureEvent* event, const QTransform& transform)
892 {
893     if (!m_view) {
894         return false;
895     }
896 
897     //you can touch on different views at the same time, but only one QWidget gets a mousePressEvent
898     //we use this to get the right QWidget
899     //the only exception is a tap gesture with state GestureStarted, we need to reset some variable
900     if (!m_mousePress) {
901         if (QGesture* tap = event->gesture(Qt::TapGesture)) {
902             QTapGesture* tapGesture = static_cast<QTapGesture*>(tap);
903             if (tapGesture->state() == Qt::GestureStarted) {
904                 tapTriggered(tapGesture, transform);
905             }
906         }
907         return false;
908     }
909 
910     bool accepted = false;
911 
912     if (QGesture* tap = event->gesture(Qt::TapGesture)) {
913         tapTriggered(static_cast<QTapGesture*>(tap), transform);
914         accepted = true;
915     }
916     if (event->gesture(Qt::TapAndHoldGesture)) {
917         tapAndHoldTriggered(event, transform);
918         accepted = true;
919     }
920     if (event->gesture(Qt::PinchGesture)) {
921         pinchTriggered(event, transform);
922         accepted = true;
923     }
924     if (event->gesture(m_swipeGesture)) {
925         swipeTriggered(event, transform);
926         accepted = true;
927     }
928     if (event->gesture(m_twoFingerTapGesture)) {
929         twoFingerTapTriggered(event, transform);
930         accepted = true;
931     }
932     return accepted;
933 }
934 
touchBeginEvent(QTouchEvent * event,const QTransform & transform)935 bool KItemListController::touchBeginEvent(QTouchEvent* event, const QTransform& transform)
936 {
937     Q_UNUSED(event)
938     Q_UNUSED(transform)
939 
940     m_isTouchEvent = true;
941     return false;
942 }
943 
tapTriggered(QTapGesture * tap,const QTransform & transform)944 void KItemListController::tapTriggered(QTapGesture* tap, const QTransform& transform)
945 {
946     static bool scrollerWasActive = false;
947 
948     if (tap->state() == Qt::GestureStarted) {
949         m_dragActionOrRightClick = false;
950         m_isSwipeGesture = false;
951         m_pinchGestureInProgress = false;
952         scrollerWasActive = m_scrollerIsScrolling;
953     }
954 
955     if (tap->state() == Qt::GestureFinished) {
956         m_mousePress = false;
957 
958         //if at the moment of the gesture start the QScroller was active, the user made the tap
959         //to stop the QScroller and not to tap on an item
960         if (scrollerWasActive) {
961             return;
962         }
963 
964         if (m_view->m_tapAndHoldIndicator->isActive()) {
965             m_view->m_tapAndHoldIndicator->setActive(false);
966         }
967 
968         m_pressedMousePos = transform.map(tap->position());
969         m_pressedIndex = m_view->itemAt(m_pressedMousePos);
970 
971         if (m_dragActionOrRightClick) {
972             onPress(tap->hotSpot().toPoint(), tap->position().toPoint(), Qt::NoModifier, Qt::RightButton);
973             onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::RightButton, false);
974             m_dragActionOrRightClick = false;
975         }
976         else {
977             onPress(tap->hotSpot().toPoint(), tap->position().toPoint(), Qt::NoModifier, Qt::LeftButton);
978             onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::LeftButton, true);
979         }
980         m_isTouchEvent = false;
981     }
982 }
983 
tapAndHoldTriggered(QGestureEvent * event,const QTransform & transform)984 void KItemListController::tapAndHoldTriggered(QGestureEvent* event, const QTransform& transform)
985 {
986 
987     //the Qt TabAndHold gesture is triggerable with a mouse click, we don't want this
988     if (!m_isTouchEvent) {
989         return;
990     }
991 
992     const QTapAndHoldGesture* tap = static_cast<QTapAndHoldGesture*>(event->gesture(Qt::TapAndHoldGesture));
993     if (tap->state() == Qt::GestureFinished) {
994         //if a pinch gesture is in progress we don't want a TabAndHold gesture
995         if (m_pinchGestureInProgress) {
996             return;
997         }
998         m_pressedMousePos = transform.map(event->mapToGraphicsScene(tap->position()));
999         m_pressedIndex = m_view->itemAt(m_pressedMousePos);
1000 
1001         if (m_pressedIndex >= 0 && !m_selectionManager->isSelected(m_pressedIndex)) {
1002             m_selectionManager->clearSelection();
1003             m_selectionManager->setSelected(m_pressedIndex);
1004         } else if (m_pressedIndex == -1) {
1005             m_selectionManager->clearSelection();
1006             startRubberBand();
1007         }
1008 
1009         Q_EMIT scrollerStop();
1010 
1011         m_view->m_tapAndHoldIndicator->setStartPosition(m_pressedMousePos);
1012         m_view->m_tapAndHoldIndicator->setActive(true);
1013 
1014         m_dragActionOrRightClick = true;
1015     }
1016 }
1017 
pinchTriggered(QGestureEvent * event,const QTransform & transform)1018 void KItemListController::pinchTriggered(QGestureEvent* event, const QTransform& transform)
1019 {
1020     Q_UNUSED(transform)
1021 
1022     const QPinchGesture* pinch = static_cast<QPinchGesture*>(event->gesture(Qt::PinchGesture));
1023     const qreal sensitivityModifier = 0.2;
1024     static qreal counter = 0;
1025 
1026     if (pinch->state() == Qt::GestureStarted) {
1027         m_pinchGestureInProgress = true;
1028         counter = 0;
1029     }
1030     if (pinch->state() == Qt::GestureUpdated) {
1031         //if a swipe gesture was recognized or in progress, we don't want a pinch gesture to change the zoom
1032         if (m_isSwipeGesture) {
1033             return;
1034         }
1035         counter = counter + (pinch->scaleFactor() - 1);
1036         if (counter >= sensitivityModifier) {
1037             Q_EMIT increaseZoom();
1038             counter = 0;
1039         } else if (counter <= -sensitivityModifier) {
1040             Q_EMIT decreaseZoom();
1041             counter = 0;
1042         }
1043     }
1044 }
1045 
swipeTriggered(QGestureEvent * event,const QTransform & transform)1046 void KItemListController::swipeTriggered(QGestureEvent* event, const QTransform& transform)
1047 {
1048     Q_UNUSED(transform)
1049 
1050     const KTwoFingerSwipe* swipe = static_cast<KTwoFingerSwipe*>(event->gesture(m_swipeGesture));
1051 
1052     if (!swipe) {
1053         return;
1054     }
1055     if (swipe->state() == Qt::GestureStarted) {
1056         m_isSwipeGesture = true;
1057     }
1058 
1059     if (swipe->state() == Qt::GestureCanceled) {
1060         m_isSwipeGesture = false;
1061     }
1062 
1063     if (swipe->state() == Qt::GestureFinished) {
1064         Q_EMIT scrollerStop();
1065 
1066         if (swipe->swipeAngle() <= 20 || swipe->swipeAngle() >= 340) {
1067             Q_EMIT mouseButtonPressed(m_pressedIndex, Qt::BackButton);
1068         } else if (swipe->swipeAngle() <= 200 && swipe->swipeAngle() >= 160) {
1069             Q_EMIT mouseButtonPressed(m_pressedIndex, Qt::ForwardButton);
1070         } else if (swipe->swipeAngle() <= 110 && swipe->swipeAngle() >= 60) {
1071             Q_EMIT swipeUp();
1072         }
1073         m_isSwipeGesture = true;
1074     }
1075 }
1076 
twoFingerTapTriggered(QGestureEvent * event,const QTransform & transform)1077 void KItemListController::twoFingerTapTriggered(QGestureEvent* event, const QTransform& transform)
1078 {
1079     const KTwoFingerTap* twoTap = static_cast<KTwoFingerTap*>(event->gesture(m_twoFingerTapGesture));
1080 
1081     if (!twoTap) {
1082         return;
1083     }
1084 
1085     if (twoTap->state() == Qt::GestureStarted) {
1086         m_pressedMousePos = transform.map(twoTap->pos());
1087         m_pressedIndex = m_view->itemAt(m_pressedMousePos);
1088         if (m_pressedIndex >= 0) {
1089             onPress(twoTap->screenPos().toPoint(), twoTap->pos().toPoint(), Qt::ControlModifier, Qt::LeftButton);
1090             onRelease(transform.map(twoTap->pos()), Qt::ControlModifier, Qt::LeftButton, false);
1091         }
1092 
1093     }
1094 }
1095 
processEvent(QEvent * event,const QTransform & transform)1096 bool KItemListController::processEvent(QEvent* event, const QTransform& transform)
1097 {
1098     if (!event) {
1099         return false;
1100     }
1101 
1102     switch (event->type()) {
1103     case QEvent::KeyPress:
1104         return keyPressEvent(static_cast<QKeyEvent*>(event));
1105     case QEvent::InputMethod:
1106         return inputMethodEvent(static_cast<QInputMethodEvent*>(event));
1107     case QEvent::GraphicsSceneMousePress:
1108         return mousePressEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1109     case QEvent::GraphicsSceneMouseMove:
1110         return mouseMoveEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1111     case QEvent::GraphicsSceneMouseRelease:
1112         return mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1113     case QEvent::GraphicsSceneMouseDoubleClick:
1114         return mouseDoubleClickEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1115     case QEvent::GraphicsSceneWheel:
1116          return wheelEvent(static_cast<QGraphicsSceneWheelEvent*>(event), QTransform());
1117     case QEvent::GraphicsSceneDragEnter:
1118         return dragEnterEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1119     case QEvent::GraphicsSceneDragLeave:
1120         return dragLeaveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1121     case QEvent::GraphicsSceneDragMove:
1122         return dragMoveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1123     case QEvent::GraphicsSceneDrop:
1124         return dropEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1125     case QEvent::GraphicsSceneHoverEnter:
1126         return hoverEnterEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
1127     case QEvent::GraphicsSceneHoverMove:
1128         return hoverMoveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
1129     case QEvent::GraphicsSceneHoverLeave:
1130         return hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
1131     case QEvent::GraphicsSceneResize:
1132         return resizeEvent(static_cast<QGraphicsSceneResizeEvent*>(event), transform);
1133     case QEvent::Gesture:
1134         return gestureEvent(static_cast<QGestureEvent*>(event), transform);
1135     case QEvent::TouchBegin:
1136         return touchBeginEvent(static_cast<QTouchEvent*>(event), transform);
1137     default:
1138         break;
1139     }
1140 
1141     return false;
1142 }
1143 
slotViewScrollOffsetChanged(qreal current,qreal previous)1144 void KItemListController::slotViewScrollOffsetChanged(qreal current, qreal previous)
1145 {
1146     if (!m_view) {
1147         return;
1148     }
1149 
1150     KItemListRubberBand* rubberBand = m_view->rubberBand();
1151     if (rubberBand->isActive()) {
1152         const qreal diff = current - previous;
1153         // TODO: Ideally just QCursor::pos() should be used as
1154         // new end-position but it seems there is no easy way
1155         // to have something like QWidget::mapFromGlobal() for QGraphicsWidget
1156         // (... or I just missed an easy way to do the mapping)
1157         QPointF endPos = rubberBand->endPosition();
1158         if (m_view->scrollOrientation() == Qt::Vertical) {
1159             endPos.ry() += diff;
1160         } else {
1161             endPos.rx() += diff;
1162         }
1163 
1164         rubberBand->setEndPosition(endPos);
1165     }
1166 }
1167 
slotRubberBandChanged()1168 void KItemListController::slotRubberBandChanged()
1169 {
1170     if (!m_view || !m_model || m_model->count() <= 0) {
1171         return;
1172     }
1173 
1174     const KItemListRubberBand* rubberBand = m_view->rubberBand();
1175     const QPointF startPos = rubberBand->startPosition();
1176     const QPointF endPos = rubberBand->endPosition();
1177     QRectF rubberBandRect = QRectF(startPos, endPos).normalized();
1178 
1179     const bool scrollVertical = (m_view->scrollOrientation() == Qt::Vertical);
1180     if (scrollVertical) {
1181         rubberBandRect.translate(0, -m_view->scrollOffset());
1182     } else {
1183         rubberBandRect.translate(-m_view->scrollOffset(), 0);
1184     }
1185 
1186     if (!m_oldSelection.isEmpty()) {
1187         // Clear the old selection that was available before the rubberband has
1188         // been activated in case if no Shift- or Control-key are pressed
1189         const bool shiftOrControlPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier ||
1190                                            QApplication::keyboardModifiers() & Qt::ControlModifier;
1191         if (!shiftOrControlPressed) {
1192             m_oldSelection.clear();
1193         }
1194     }
1195 
1196     KItemSet selectedItems;
1197 
1198     // Select all visible items that intersect with the rubberband
1199     const auto widgets = m_view->visibleItemListWidgets();
1200     for (const KItemListWidget* widget : widgets) {
1201         const int index = widget->index();
1202 
1203         const QRectF widgetRect = m_view->itemRect(index);
1204         if (widgetRect.intersects(rubberBandRect)) {
1205             const QRectF iconRect = widget->iconRect().translated(widgetRect.topLeft());
1206             const QRectF textRect = widget->textRect().translated(widgetRect.topLeft());
1207             if (iconRect.intersects(rubberBandRect) || textRect.intersects(rubberBandRect)) {
1208                 selectedItems.insert(index);
1209             }
1210         }
1211     }
1212 
1213     // Select all invisible items that intersect with the rubberband. Instead of
1214     // iterating all items only the area which might be touched by the rubberband
1215     // will be checked.
1216     const bool increaseIndex = scrollVertical ?
1217                                startPos.y() > endPos.y(): startPos.x() > endPos.x();
1218 
1219     int index = increaseIndex ? m_view->lastVisibleIndex() + 1 : m_view->firstVisibleIndex() - 1;
1220     bool selectionFinished = false;
1221     do {
1222         const QRectF widgetRect = m_view->itemRect(index);
1223         if (widgetRect.intersects(rubberBandRect)) {
1224             selectedItems.insert(index);
1225         }
1226 
1227         if (increaseIndex) {
1228             ++index;
1229             selectionFinished = (index >= m_model->count()) ||
1230                                 ( scrollVertical && widgetRect.top()  > rubberBandRect.bottom()) ||
1231                                 (!scrollVertical && widgetRect.left() > rubberBandRect.right());
1232         } else {
1233             --index;
1234             selectionFinished = (index < 0) ||
1235                                 ( scrollVertical && widgetRect.bottom() < rubberBandRect.top()) ||
1236                                 (!scrollVertical && widgetRect.right()  < rubberBandRect.left());
1237         }
1238     } while (!selectionFinished);
1239 
1240     if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
1241         // If Control is pressed, the selection state of all items in the rubberband is toggled.
1242         // Therefore, the new selection contains:
1243         // 1. All previously selected items which are not inside the rubberband, and
1244         // 2. all items inside the rubberband which have not been selected previously.
1245         m_selectionManager->setSelectedItems(m_oldSelection ^ selectedItems);
1246     }
1247     else {
1248         m_selectionManager->setSelectedItems(selectedItems + m_oldSelection);
1249     }
1250 }
1251 
startDragging()1252 void KItemListController::startDragging()
1253 {
1254     if (!m_view || !m_model) {
1255         return;
1256     }
1257 
1258     const KItemSet selectedItems = m_selectionManager->selectedItems();
1259     if (selectedItems.isEmpty()) {
1260         return;
1261     }
1262 
1263     QMimeData* data = m_model->createMimeData(selectedItems);
1264     if (!data) {
1265         return;
1266     }
1267 
1268     // The created drag object will be owned and deleted
1269     // by QApplication::activeWindow().
1270     QDrag* drag = new QDrag(QApplication::activeWindow());
1271     drag->setMimeData(data);
1272 
1273     const QPixmap pixmap = m_view->createDragPixmap(selectedItems);
1274     drag->setPixmap(pixmap);
1275 
1276     const QPoint hotSpot((pixmap.width() / pixmap.devicePixelRatio()) / 2, 0);
1277     drag->setHotSpot(hotSpot);
1278 
1279     drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction);
1280 
1281     QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropStart);
1282     QAccessible::updateAccessibility(&accessibilityEvent);
1283 }
1284 
hoveredWidget() const1285 KItemListWidget* KItemListController::hoveredWidget() const
1286 {
1287     Q_ASSERT(m_view);
1288 
1289     const auto widgets = m_view->visibleItemListWidgets();
1290     for (KItemListWidget* widget : widgets) {
1291         if (widget->isHovered()) {
1292             return widget;
1293         }
1294     }
1295 
1296     return nullptr;
1297 }
1298 
widgetForPos(const QPointF & pos) const1299 KItemListWidget* KItemListController::widgetForPos(const QPointF& pos) const
1300 {
1301     Q_ASSERT(m_view);
1302 
1303     const auto widgets = m_view->visibleItemListWidgets();
1304     for (KItemListWidget* widget : widgets) {
1305         const QPointF mappedPos = widget->mapFromItem(m_view, pos);
1306 
1307         const bool hovered = widget->contains(mappedPos) &&
1308                              !widget->expansionToggleRect().contains(mappedPos);
1309         if (hovered) {
1310             return widget;
1311         }
1312     }
1313 
1314     return nullptr;
1315 }
1316 
updateKeyboardAnchor()1317 void KItemListController::updateKeyboardAnchor()
1318 {
1319     const bool validAnchor = m_keyboardAnchorIndex >= 0 &&
1320                              m_keyboardAnchorIndex < m_model->count() &&
1321                              keyboardAnchorPos(m_keyboardAnchorIndex) == m_keyboardAnchorPos;
1322     if (!validAnchor) {
1323         const int index = m_selectionManager->currentItem();
1324         m_keyboardAnchorIndex = index;
1325         m_keyboardAnchorPos = keyboardAnchorPos(index);
1326     }
1327 }
1328 
nextRowIndex(int index) const1329 int KItemListController::nextRowIndex(int index) const
1330 {
1331     if (m_keyboardAnchorIndex < 0) {
1332         return index;
1333     }
1334 
1335     const int maxIndex = m_model->count() - 1;
1336     if (index == maxIndex) {
1337         return index;
1338     }
1339 
1340     // Calculate the index of the last column inside the row of the current index
1341     int lastColumnIndex = index;
1342     while (keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex)) {
1343         ++lastColumnIndex;
1344         if (lastColumnIndex >= maxIndex) {
1345             return index;
1346         }
1347     }
1348 
1349     // Based on the last column index go to the next row and calculate the nearest index
1350     // that is below the current index
1351     int nextRowIndex = lastColumnIndex + 1;
1352     int searchIndex = nextRowIndex;
1353     qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(nextRowIndex));
1354     while (searchIndex < maxIndex && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex)) {
1355         ++searchIndex;
1356         const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1357         if (searchDiff < minDiff) {
1358             minDiff = searchDiff;
1359             nextRowIndex = searchIndex;
1360         }
1361     }
1362 
1363     return nextRowIndex;
1364 }
1365 
previousRowIndex(int index) const1366 int KItemListController::previousRowIndex(int index) const
1367 {
1368     if (m_keyboardAnchorIndex < 0 || index == 0) {
1369         return index;
1370     }
1371 
1372     // Calculate the index of the first column inside the row of the current index
1373     int firstColumnIndex = index;
1374     while (keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex)) {
1375         --firstColumnIndex;
1376         if (firstColumnIndex <= 0) {
1377             return index;
1378         }
1379     }
1380 
1381     // Based on the first column index go to the previous row and calculate the nearest index
1382     // that is above the current index
1383     int previousRowIndex = firstColumnIndex - 1;
1384     int searchIndex = previousRowIndex;
1385     qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(previousRowIndex));
1386     while (searchIndex > 0 && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex)) {
1387         --searchIndex;
1388         const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1389         if (searchDiff < minDiff) {
1390             minDiff = searchDiff;
1391             previousRowIndex = searchIndex;
1392         }
1393     }
1394 
1395     return previousRowIndex;
1396 }
1397 
keyboardAnchorPos(int index) const1398 qreal KItemListController::keyboardAnchorPos(int index) const
1399 {
1400     const QRectF itemRect = m_view->itemRect(index);
1401     if (!itemRect.isEmpty()) {
1402         return (m_view->scrollOrientation() == Qt::Vertical) ? itemRect.x() : itemRect.y();
1403     }
1404 
1405     return 0;
1406 }
1407 
updateExtendedSelectionRegion()1408 void KItemListController::updateExtendedSelectionRegion()
1409 {
1410     if (m_view) {
1411         const bool extend = (m_selectionBehavior != MultiSelection);
1412         KItemListStyleOption option = m_view->styleOption();
1413         if (option.extendedSelectionRegion != extend) {
1414             option.extendedSelectionRegion = extend;
1415             m_view->setStyleOption(option);
1416         }
1417     }
1418 }
1419 
onPress(const QPoint & screenPos,const QPointF & pos,const Qt::KeyboardModifiers modifiers,const Qt::MouseButtons buttons)1420 bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons)
1421 {
1422     Q_EMIT mouseButtonPressed(m_pressedIndex, buttons);
1423 
1424     if (buttons & (Qt::BackButton | Qt::ForwardButton)) {
1425         // Do not select items when clicking the back/forward buttons, see
1426         // https://bugs.kde.org/show_bug.cgi?id=327412.
1427         return true;
1428     }
1429 
1430     if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) {
1431         m_selectionManager->endAnchoredSelection();
1432         m_selectionManager->setCurrentItem(m_pressedIndex);
1433         m_selectionManager->beginAnchoredSelection(m_pressedIndex);
1434         return true;
1435     }
1436 
1437     m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
1438     if (m_selectionTogglePressed) {
1439         m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
1440         // The previous anchored selection has been finished already in
1441         // KItemListSelectionManager::setSelected(). We can safely change
1442         // the current item and start a new anchored selection now.
1443         m_selectionManager->setCurrentItem(m_pressedIndex);
1444         m_selectionManager->beginAnchoredSelection(m_pressedIndex);
1445         return true;
1446     }
1447 
1448     const bool shiftPressed = modifiers & Qt::ShiftModifier;
1449     const bool controlPressed = modifiers & Qt::ControlModifier;
1450 
1451     // The previous selection is cleared if either
1452     // 1. The selection mode is SingleSelection, or
1453     // 2. the selection mode is MultiSelection, and *none* of the following conditions are met:
1454     //    a) Shift or Control are pressed.
1455     //    b) The clicked item is selected already. In that case, the user might want to:
1456     //       - start dragging multiple items, or
1457     //       - open the context menu and perform an action for all selected items.
1458     const bool shiftOrControlPressed = shiftPressed || controlPressed;
1459     const bool pressedItemAlreadySelected = m_pressedIndex >= 0 && m_selectionManager->isSelected(m_pressedIndex);
1460     const bool clearSelection = m_selectionBehavior == SingleSelection ||
1461                                 (!shiftOrControlPressed && !pressedItemAlreadySelected);
1462     if (clearSelection) {
1463         m_selectionManager->clearSelection();
1464     } else if (pressedItemAlreadySelected && !shiftOrControlPressed && (buttons & Qt::LeftButton)) {
1465         // The user might want to start dragging multiple items, but if he clicks the item
1466         // in order to trigger it instead, the other selected items must be deselected.
1467         // However, we do not know yet what the user is going to do.
1468         // -> remember that the user pressed an item which had been selected already and
1469         //    clear the selection in mouseReleaseEvent(), unless the items are dragged.
1470         m_clearSelectionIfItemsAreNotDragged = true;
1471 
1472         if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex, m_pressedMousePos)) {
1473             Q_EMIT selectedItemTextPressed(m_pressedIndex);
1474         }
1475     }
1476 
1477     if (!shiftPressed) {
1478         // Finish the anchored selection before the current index is changed
1479         m_selectionManager->endAnchoredSelection();
1480     }
1481 
1482     if (buttons & Qt::RightButton) {
1483         // Stop rubber band from persisting after right-clicks
1484         KItemListRubberBand* rubberBand = m_view->rubberBand();
1485         if (rubberBand->isActive()) {
1486             disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1487             rubberBand->setActive(false);
1488             m_view->setAutoScroll(false);
1489         }
1490     }
1491 
1492     if (m_pressedIndex >= 0) {
1493         m_selectionManager->setCurrentItem(m_pressedIndex);
1494 
1495         switch (m_selectionBehavior) {
1496         case NoSelection:
1497             break;
1498 
1499         case SingleSelection:
1500             m_selectionManager->setSelected(m_pressedIndex);
1501             break;
1502 
1503         case MultiSelection:
1504             if (controlPressed && !shiftPressed) {
1505                 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
1506                 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
1507             } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
1508                 // Select the pressed item and start a new anchored selection
1509                 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
1510                 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
1511             }
1512             break;
1513 
1514         default:
1515             Q_ASSERT(false);
1516             break;
1517         }
1518 
1519         if (buttons & Qt::RightButton) {
1520             Q_EMIT itemContextMenuRequested(m_pressedIndex, screenPos);
1521         }
1522 
1523         return true;
1524     }
1525 
1526     if (buttons & Qt::RightButton) {
1527         const QRectF headerBounds = m_view->headerBoundaries();
1528         if (headerBounds.contains(pos)) {
1529             Q_EMIT headerContextMenuRequested(screenPos);
1530         } else {
1531             Q_EMIT viewContextMenuRequested(screenPos);
1532         }
1533         return true;
1534     }
1535 
1536     return false;
1537 }
1538 
onRelease(const QPointF & pos,const Qt::KeyboardModifiers modifiers,const Qt::MouseButtons buttons,bool touch)1539 bool KItemListController::onRelease(const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch)
1540 {
1541     const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
1542     if (isAboveSelectionToggle) {
1543         m_selectionTogglePressed = false;
1544         return true;
1545     }
1546 
1547     if (!isAboveSelectionToggle && m_selectionTogglePressed) {
1548         m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
1549         m_selectionTogglePressed = false;
1550         return true;
1551     }
1552 
1553     const bool controlPressed = modifiers & Qt::ControlModifier;
1554     const bool shiftOrControlPressed = modifiers & Qt::ShiftModifier ||
1555                                        controlPressed;
1556 
1557     KItemListRubberBand* rubberBand = m_view->rubberBand();
1558     if (rubberBand->isActive()) {
1559         disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1560         rubberBand->setActive(false);
1561         m_oldSelection.clear();
1562         m_view->setAutoScroll(false);
1563     }
1564 
1565     const int index = m_view->itemAt(pos);
1566 
1567     if (index >= 0 && index == m_pressedIndex) {
1568         // The release event is done above the same item as the press event
1569 
1570         if (m_clearSelectionIfItemsAreNotDragged) {
1571             // A selected item has been clicked, but no drag operation has been started
1572             // -> clear the rest of the selection.
1573             m_selectionManager->clearSelection();
1574             m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
1575             m_selectionManager->beginAnchoredSelection(m_pressedIndex);
1576         }
1577 
1578         if (buttons & Qt::LeftButton) {
1579             bool emitItemActivated = true;
1580             if (m_view->isAboveExpansionToggle(index, pos)) {
1581                 const bool expanded = m_model->isExpanded(index);
1582                 m_model->setExpanded(index, !expanded);
1583 
1584                 Q_EMIT itemExpansionToggleClicked(index);
1585                 emitItemActivated = false;
1586             } else if (shiftOrControlPressed) {
1587                 // The mouse click should only update the selection, not trigger the item
1588                 emitItemActivated = false;
1589                 // When Ctrl-clicking an item when in single selection mode
1590                 // i.e. where Ctrl won't change the selection, pretend it was middle clicked
1591                 if (controlPressed && m_selectionBehavior == SingleSelection) {
1592                     Q_EMIT itemMiddleClicked(index);
1593                 }
1594             } else if (!(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced)) {
1595                 if (touch) {
1596                 emitItemActivated = true;
1597                 } else {
1598                 emitItemActivated = false;
1599                 }
1600             }
1601             if (emitItemActivated) {
1602                 Q_EMIT itemActivated(index);
1603             }
1604         } else if (buttons & Qt::MiddleButton) {
1605             Q_EMIT itemMiddleClicked(index);
1606         }
1607     }
1608 
1609     m_pressedMousePos = QPointF();
1610     m_pressedIndex = -1;
1611     m_clearSelectionIfItemsAreNotDragged = false;
1612     return false;
1613 }
1614 
startRubberBand()1615 void KItemListController::startRubberBand()
1616 {
1617     if (m_selectionBehavior == MultiSelection) {
1618         QPointF startPos = m_pressedMousePos;
1619         if (m_view->scrollOrientation() == Qt::Vertical) {
1620             startPos.ry() += m_view->scrollOffset();
1621             if (m_view->itemSize().width() < 0) {
1622                 // Use a special rubberband for views that have only one column and
1623                 // expand the rubberband to use the whole width of the view.
1624                 startPos.setX(0);
1625             }
1626         } else {
1627             startPos.rx() += m_view->scrollOffset();
1628         }
1629 
1630         m_oldSelection = m_selectionManager->selectedItems();
1631         KItemListRubberBand* rubberBand = m_view->rubberBand();
1632         rubberBand->setStartPosition(startPos);
1633         rubberBand->setEndPosition(startPos);
1634         rubberBand->setActive(true);
1635         connect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1636         m_view->setAutoScroll(true);
1637     }
1638 }
1639 
slotStateChanged(QScroller::State newState)1640 void KItemListController::slotStateChanged(QScroller::State newState)
1641 {
1642     if (newState == QScroller::Scrolling) {
1643         m_scrollerIsScrolling = true;
1644     } else if (newState == QScroller::Inactive) {
1645         m_scrollerIsScrolling = false;
1646     }
1647 }
1648