]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistcontroller.cpp
SVN_SILENT made messages (.desktop file) - always resolve ours
[dolphin.git] / src / kitemviews / kitemlistcontroller.cpp
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 "views/draganddrophelper.h"
17
18 #include <KTwoFingerSwipe>
19 #include <KTwoFingerTap>
20 #include <KUrlMimeData>
21
22 #include <QAccessible>
23 #include <QApplication>
24 #include <QDrag>
25 #include <QGesture>
26 #include <QGraphicsScene>
27 #include <QGraphicsSceneEvent>
28 #include <QGraphicsView>
29 #include <QMimeData>
30 #include <QTimer>
31 #include <QTouchEvent>
32
33 KItemListController::KItemListController(KItemModelBase *model, KItemListView *view, QObject *parent)
34 : QObject(parent)
35 , m_singleClickActivationEnforced(false)
36 , m_selectionMode(false)
37 , m_selectionTogglePressed(false)
38 , m_clearSelectionIfItemsAreNotDragged(false)
39 , m_isSwipeGesture(false)
40 , m_dragActionOrRightClick(false)
41 , m_scrollerIsScrolling(false)
42 , m_pinchGestureInProgress(false)
43 , m_mousePress(false)
44 , m_isTouchEvent(false)
45 , m_selectionBehavior(NoSelection)
46 , m_autoActivationBehavior(ActivationAndExpansion)
47 , m_mouseDoubleClickAction(ActivateItemOnly)
48 , m_model(nullptr)
49 , m_view(nullptr)
50 , m_selectionManager(new KItemListSelectionManager(this))
51 , m_keyboardManager(new KItemListKeyboardSearchManager(this))
52 , m_pressedIndex(std::nullopt)
53 , m_pressedMouseGlobalPos()
54 , m_autoActivationTimer(nullptr)
55 , m_swipeGesture(Qt::CustomGesture)
56 , m_twoFingerTapGesture(Qt::CustomGesture)
57 , m_oldSelection()
58 , m_keyboardAnchorIndex(-1)
59 , m_keyboardAnchorPos(0)
60 {
61 connect(m_keyboardManager, &KItemListKeyboardSearchManager::changeCurrentItem, this, &KItemListController::slotChangeCurrentItem);
62 connect(m_selectionManager, &KItemListSelectionManager::currentChanged, m_keyboardManager, &KItemListKeyboardSearchManager::slotCurrentChanged);
63 connect(m_selectionManager, &KItemListSelectionManager::selectionChanged, m_keyboardManager, &KItemListKeyboardSearchManager::slotSelectionChanged);
64
65 m_autoActivationTimer = new QTimer(this);
66 m_autoActivationTimer->setSingleShot(true);
67 m_autoActivationTimer->setInterval(750);
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
82 KItemListController::~KItemListController()
83 {
84 setView(nullptr);
85 Q_ASSERT(!m_view);
86
87 setModel(nullptr);
88 Q_ASSERT(!m_model);
89 }
90
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
116 KItemModelBase *KItemListController::model() const
117 {
118 return m_model;
119 }
120
121 KItemListSelectionManager *KItemListController::selectionManager() const
122 {
123 return m_selectionManager;
124 }
125
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
151 KItemListView *KItemListController::view() const
152 {
153 return m_view;
154 }
155
156 void KItemListController::setSelectionBehavior(SelectionBehavior behavior)
157 {
158 m_selectionBehavior = behavior;
159 updateExtendedSelectionRegion();
160 }
161
162 KItemListController::SelectionBehavior KItemListController::selectionBehavior() const
163 {
164 return m_selectionBehavior;
165 }
166
167 void KItemListController::setAutoActivationBehavior(AutoActivationBehavior behavior)
168 {
169 m_autoActivationBehavior = behavior;
170 }
171
172 KItemListController::AutoActivationBehavior KItemListController::autoActivationBehavior() const
173 {
174 return m_autoActivationBehavior;
175 }
176
177 void KItemListController::setMouseDoubleClickAction(MouseDoubleClickAction action)
178 {
179 m_mouseDoubleClickAction = action;
180 }
181
182 KItemListController::MouseDoubleClickAction KItemListController::mouseDoubleClickAction() const
183 {
184 return m_mouseDoubleClickAction;
185 }
186
187 int KItemListController::indexCloseToMousePressedPosition() const
188 {
189 const QPointF pressedMousePos = m_view->transform().map(m_view->scene()->views().first()->mapFromGlobal(m_pressedMouseGlobalPos.toPoint()));
190
191 QHashIterator<KItemListWidget *, KItemListGroupHeader *> it(m_view->m_visibleGroups);
192 while (it.hasNext()) {
193 it.next();
194 KItemListGroupHeader *groupHeader = it.value();
195 const QPointF mappedToGroup = groupHeader->mapFromItem(nullptr, pressedMousePos);
196 if (groupHeader->contains(mappedToGroup)) {
197 return it.key()->index();
198 }
199 }
200 return -1;
201 }
202
203 void KItemListController::setAutoActivationEnabled(bool enabled)
204 {
205 m_autoActivationEnabled = enabled;
206 }
207
208 bool KItemListController::isAutoActivationEnabled() const
209 {
210 return m_autoActivationEnabled;
211 }
212
213 void KItemListController::setSingleClickActivationEnforced(bool singleClick)
214 {
215 m_singleClickActivationEnforced = singleClick;
216 }
217
218 bool KItemListController::singleClickActivationEnforced() const
219 {
220 return m_singleClickActivationEnforced;
221 }
222
223 void KItemListController::setSelectionModeEnabled(bool enabled)
224 {
225 m_selectionMode = enabled;
226 }
227
228 bool KItemListController::selectionMode() const
229 {
230 return m_selectionMode;
231 }
232
233 bool KItemListController::isSearchAsYouTypeActive() const
234 {
235 return m_keyboardManager->isSearchAsYouTypeActive();
236 }
237
238 bool KItemListController::keyPressEvent(QKeyEvent *event)
239 {
240 int index = m_selectionManager->currentItem();
241 int key = event->key();
242 const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
243
244 const bool horizontalScrolling = m_view->scrollOrientation() == Qt::Horizontal;
245
246 if (m_view->layoutDirection() == Qt::RightToLeft) {
247 // swap left and right arrow keys
248 switch (key) {
249 case Qt::Key_Left:
250 key = Qt::Key_Right;
251 break;
252 case Qt::Key_Right:
253 key = Qt::Key_Left;
254 break;
255 default:
256 break;
257 }
258 }
259
260 // Handle the expanding/collapsing of items
261 // expand / collapse all selected directories
262 if (m_view->supportsItemExpanding() && m_model->isExpandable(index) && (key == Qt::Key_Right || key == Qt::Key_Left)) {
263 const bool expandOrCollapse = key == Qt::Key_Right ? true : false;
264 bool shouldReturn = m_model->setExpanded(index, expandOrCollapse);
265
266 // edit in reverse to preserve index of the first handled items
267 const auto selectedItems = m_selectionManager->selectedItems();
268 for (auto it = selectedItems.rbegin(); it != selectedItems.rend(); ++it) {
269 shouldReturn |= m_model->setExpanded(*it, expandOrCollapse);
270 if (!shiftPressed) {
271 m_selectionManager->setSelected(*it);
272 }
273 }
274 if (shouldReturn) {
275 // update keyboard anchors
276 if (shiftPressed) {
277 m_keyboardAnchorIndex = selectedItems.count() > 0 ? qMin(index, selectedItems.last()) : index;
278 m_keyboardAnchorPos = keyboardAnchorPos(m_keyboardAnchorIndex);
279 }
280
281 event->ignore();
282 return true;
283 }
284 }
285
286 const bool controlPressed = event->modifiers() & Qt::ControlModifier;
287 if (m_selectionMode && !controlPressed && !shiftPressed && (key == Qt::Key_Enter || key == Qt::Key_Return)) {
288 key = Qt::Key_Space; // In selection mode one moves around with arrow keys and toggles selection with Enter.
289 }
290 const bool navigationPressed = key == Qt::Key_Home || key == Qt::Key_End || key == Qt::Key_PageUp || key == Qt::Key_PageDown || key == Qt::Key_Up
291 || key == Qt::Key_Down || key == Qt::Key_Left || key == Qt::Key_Right;
292
293 const int itemCount = m_model->count();
294
295 // For horizontal scroll orientation, transform
296 // the arrow keys to simplify the event handling.
297 if (horizontalScrolling) {
298 switch (key) {
299 case Qt::Key_Up:
300 key = Qt::Key_Left;
301 break;
302 case Qt::Key_Down:
303 key = Qt::Key_Right;
304 break;
305 case Qt::Key_Left:
306 key = Qt::Key_Up;
307 break;
308 case Qt::Key_Right:
309 key = Qt::Key_Down;
310 break;
311 default:
312 break;
313 }
314 }
315
316 const bool selectSingleItem = m_selectionBehavior != NoSelection && itemCount == 1 && navigationPressed;
317
318 if (selectSingleItem) {
319 const int current = m_selectionManager->currentItem();
320 m_selectionManager->setSelected(current);
321 return true;
322 }
323
324 switch (key) {
325 case Qt::Key_Home:
326 index = 0;
327 m_keyboardAnchorIndex = index;
328 m_keyboardAnchorPos = keyboardAnchorPos(index);
329 break;
330
331 case Qt::Key_End:
332 index = itemCount - 1;
333 m_keyboardAnchorIndex = index;
334 m_keyboardAnchorPos = keyboardAnchorPos(index);
335 break;
336
337 case Qt::Key_Left:
338 if (index > 0) {
339 const int expandedParentsCount = m_model->expandedParentsCount(index);
340 if (expandedParentsCount == 0) {
341 --index;
342 } else {
343 // Go to the parent of the current item.
344 do {
345 --index;
346 } while (index > 0 && m_model->expandedParentsCount(index) == expandedParentsCount);
347 }
348 m_keyboardAnchorIndex = index;
349 m_keyboardAnchorPos = keyboardAnchorPos(index);
350 }
351 break;
352
353 case Qt::Key_Right:
354 if (index < itemCount - 1) {
355 ++index;
356 m_keyboardAnchorIndex = index;
357 m_keyboardAnchorPos = keyboardAnchorPos(index);
358 }
359 break;
360
361 case Qt::Key_Up:
362 updateKeyboardAnchor();
363 if (shiftPressed && !m_selectionManager->isAnchoredSelectionActive() && m_selectionManager->isSelected(index)) {
364 m_selectionManager->beginAnchoredSelection(index);
365 }
366 index = previousRowIndex(index);
367 break;
368
369 case Qt::Key_Down:
370 updateKeyboardAnchor();
371 if (shiftPressed && !m_selectionManager->isAnchoredSelectionActive() && m_selectionManager->isSelected(index)) {
372 m_selectionManager->beginAnchoredSelection(index);
373 }
374 index = nextRowIndex(index);
375 break;
376
377 case Qt::Key_PageUp:
378 if (horizontalScrolling) {
379 // The new current index should correspond to the first item in the current column.
380 int newIndex = qMax(index - 1, 0);
381 while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() < m_view->itemRect(index).topLeft().y()) {
382 index = newIndex;
383 newIndex = qMax(index - 1, 0);
384 }
385 m_keyboardAnchorIndex = index;
386 m_keyboardAnchorPos = keyboardAnchorPos(index);
387 } else {
388 const qreal currentItemBottom = m_view->itemRect(index).bottomLeft().y();
389 const qreal height = m_view->geometry().height();
390
391 // The new current item should be the first item in the current
392 // column whose itemRect's top coordinate is larger than targetY.
393 const qreal targetY = currentItemBottom - height;
394
395 updateKeyboardAnchor();
396 int newIndex = previousRowIndex(index);
397 do {
398 index = newIndex;
399 updateKeyboardAnchor();
400 newIndex = previousRowIndex(index);
401 } while (m_view->itemRect(newIndex).topLeft().y() > targetY && newIndex != index);
402 }
403 break;
404
405 case Qt::Key_PageDown:
406 if (horizontalScrolling) {
407 // The new current index should correspond to the last item in the current column.
408 int newIndex = qMin(index + 1, m_model->count() - 1);
409 while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() > m_view->itemRect(index).topLeft().y()) {
410 index = newIndex;
411 newIndex = qMin(index + 1, m_model->count() - 1);
412 }
413 m_keyboardAnchorIndex = index;
414 m_keyboardAnchorPos = keyboardAnchorPos(index);
415 } else {
416 const qreal currentItemTop = m_view->itemRect(index).topLeft().y();
417 const qreal height = m_view->geometry().height();
418
419 // The new current item should be the last item in the current
420 // column whose itemRect's bottom coordinate is smaller than targetY.
421 const qreal targetY = currentItemTop + height;
422
423 updateKeyboardAnchor();
424 int newIndex = nextRowIndex(index);
425 do {
426 index = newIndex;
427 updateKeyboardAnchor();
428 newIndex = nextRowIndex(index);
429 } while (m_view->itemRect(newIndex).bottomLeft().y() < targetY && newIndex != index);
430 }
431 break;
432
433 case Qt::Key_Enter:
434 case Qt::Key_Return: {
435 const KItemSet selectedItems = m_selectionManager->selectedItems();
436 if (selectedItems.count() >= 2) {
437 Q_EMIT itemsActivated(selectedItems);
438 } else if (selectedItems.count() == 1) {
439 Q_EMIT itemActivated(selectedItems.first());
440 } else {
441 Q_EMIT itemActivated(index);
442 }
443 break;
444 }
445
446 case Qt::Key_Escape:
447 if (m_selectionMode) {
448 Q_EMIT selectionModeChangeRequested(false);
449 } else if (m_selectionBehavior != SingleSelection) {
450 m_selectionManager->clearSelection();
451 }
452 m_keyboardManager->cancelSearch();
453 Q_EMIT escapePressed();
454 break;
455
456 case Qt::Key_Space:
457 if (m_selectionBehavior == MultiSelection) {
458 if (controlPressed || m_selectionMode) {
459 // Toggle the selection state of the current item.
460 m_selectionManager->endAnchoredSelection();
461 m_selectionManager->setSelected(index, 1, KItemListSelectionManager::Toggle);
462 m_selectionManager->beginAnchoredSelection(index);
463 break;
464 } else {
465 // Select the current item if it is not selected yet.
466 const int current = m_selectionManager->currentItem();
467 if (!m_selectionManager->isSelected(current)) {
468 m_selectionManager->setSelected(current);
469 break;
470 }
471 }
472 }
473 Q_FALLTHROUGH(); // fall through to the default case and add the Space to the current search string.
474 default:
475 m_keyboardManager->addKeys(event->text());
476 // Make sure unconsumed events get propagated up the chain. #302329
477 event->ignore();
478 return false;
479 }
480
481 if (m_selectionManager->currentItem() != index) {
482 switch (m_selectionBehavior) {
483 case NoSelection:
484 m_selectionManager->setCurrentItem(index);
485 break;
486
487 case SingleSelection:
488 m_selectionManager->setCurrentItem(index);
489 m_selectionManager->clearSelection();
490 m_selectionManager->setSelected(index, 1);
491 break;
492
493 case MultiSelection:
494 if (controlPressed || (m_selectionMode && !shiftPressed)) {
495 m_selectionManager->endAnchoredSelection();
496 }
497
498 m_selectionManager->setCurrentItem(index);
499
500 if (!shiftPressed && !controlPressed && !m_selectionMode) {
501 m_selectionManager->clearSelection();
502 m_selectionManager->setSelected(index, 1);
503 }
504
505 if (!shiftPressed) {
506 m_selectionManager->beginAnchoredSelection(index);
507 }
508 break;
509 }
510 }
511
512 if (navigationPressed) {
513 m_view->scrollToItem(index);
514 }
515 return true;
516 }
517
518 void KItemListController::slotChangeCurrentItem(const QString &text, bool searchFromNextItem)
519 {
520 if (!m_model || m_model->count() == 0) {
521 return;
522 }
523 int index;
524 // In selection mode, always use the current (underlined) item, or the next item, for search start position.
525 if (m_selectionBehavior == NoSelection || m_selectionMode || m_selectionManager->hasSelection()) {
526 index = m_model->indexForKeyboardSearch(text, searchFromNextItem ? m_selectionManager->currentItem() + 1 : m_selectionManager->currentItem());
527 } else {
528 index = m_model->indexForKeyboardSearch(text, 0);
529 }
530 if (index >= 0) {
531 if (m_selectionMode) {
532 m_selectionManager->endAnchoredSelection();
533 }
534
535 m_selectionManager->setCurrentItem(index);
536
537 if (m_selectionBehavior != NoSelection) {
538 if (!m_selectionMode) { // Don't clear the selection in selection mode.
539 m_selectionManager->replaceSelection(index);
540 }
541 m_selectionManager->beginAnchoredSelection(index);
542 }
543
544 m_view->scrollToItem(index, KItemListView::ViewItemPosition::Beginning);
545 }
546 }
547
548 void KItemListController::slotAutoActivationTimeout()
549 {
550 if (!m_model || !m_view) {
551 return;
552 }
553
554 const int index = m_autoActivationTimer->property("index").toInt();
555 if (index < 0 || index >= m_model->count()) {
556 return;
557 }
558
559 /* m_view->isUnderMouse() fixes a bug in the Folder-View-Panel and in the
560 * Places-Panel.
561 *
562 * Bug: When you drag a file onto a Folder-View-Item or a Places-Item and
563 * then move away before the auto-activation timeout triggers, than the
564 * item still becomes activated/expanded.
565 *
566 * See Bug 293200 and 305783
567 */
568 if (m_view->isUnderMouse()) {
569 if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
570 const bool expanded = m_model->isExpanded(index);
571 m_model->setExpanded(index, !expanded);
572 } else if (m_autoActivationBehavior != ExpansionOnly) {
573 Q_EMIT itemActivated(index);
574 }
575 }
576 }
577
578 bool KItemListController::inputMethodEvent(QInputMethodEvent *event)
579 {
580 Q_UNUSED(event)
581 return false;
582 }
583
584 bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent *event, const QTransform &transform)
585 {
586 m_mousePress = true;
587 m_pressedMouseGlobalPos = event->screenPos();
588
589 if (event->source() == Qt::MouseEventSynthesizedByQt && m_isTouchEvent) {
590 return false;
591 }
592
593 if (!m_view) {
594 return false;
595 }
596
597 const QPointF pressedMousePos = transform.map(event->pos());
598 m_pressedIndex = m_view->itemAt(pressedMousePos);
599
600 const Qt::MouseButtons buttons = event->buttons();
601
602 if (!onPress(event->pos(), event->modifiers(), buttons)) {
603 startRubberBand();
604 return false;
605 }
606
607 return true;
608 }
609
610 bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent *event, const QTransform &transform)
611 {
612 if (!m_view) {
613 return false;
614 }
615
616 if (m_view->m_tapAndHoldIndicator->isActive()) {
617 m_view->m_tapAndHoldIndicator->setActive(false);
618 }
619
620 if (event->source() == Qt::MouseEventSynthesizedByQt && !m_dragActionOrRightClick && m_isTouchEvent) {
621 return false;
622 }
623
624 if (m_pressedIndex.has_value() && !m_view->rubberBand()->isActive()) {
625 // Check whether a dragging should be started
626 if (event->buttons() & Qt::LeftButton) {
627 const auto distance = (event->screenPos() - m_pressedMouseGlobalPos).manhattanLength();
628 if (distance >= QApplication::startDragDistance()) {
629 if (!m_selectionManager->isSelected(m_pressedIndex.value())) {
630 // Always assure that the dragged item gets selected. Usually this is already
631 // done on the mouse-press event, but when using the selection-toggle on a
632 // selected item the dragged item is not selected yet.
633 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
634 } else {
635 // A selected item has been clicked to drag all selected items
636 // -> the selection should not be cleared when the mouse button is released.
637 m_clearSelectionIfItemsAreNotDragged = false;
638 }
639 startDragging();
640 m_mousePress = false;
641 }
642 }
643 } else {
644 KItemListRubberBand *rubberBand = m_view->rubberBand();
645 if (rubberBand->isActive()) {
646 QPointF endPos = transform.map(event->pos());
647
648 // Update the current item.
649 const std::optional<int> newCurrent = m_view->itemAt(endPos);
650 if (newCurrent.has_value()) {
651 // It's expected that the new current index is also the new anchor (bug 163451).
652 m_selectionManager->endAnchoredSelection();
653 m_selectionManager->setCurrentItem(newCurrent.value());
654 m_selectionManager->beginAnchoredSelection(newCurrent.value());
655 }
656
657 if (m_view->scrollOrientation() == Qt::Vertical) {
658 endPos.ry() += m_view->scrollOffset();
659 } else {
660 endPos.rx() += m_view->scrollOffset();
661 }
662 rubberBand->setEndPosition(endPos);
663 }
664 }
665
666 return false;
667 }
668
669 bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, const QTransform &transform)
670 {
671 m_mousePress = false;
672 m_isTouchEvent = false;
673
674 if (!m_view) {
675 return false;
676 }
677
678 for (KItemListWidget *widget : m_view->visibleItemListWidgets()) {
679 widget->setPressed(false);
680 }
681
682 if (m_view->m_tapAndHoldIndicator->isActive()) {
683 m_view->m_tapAndHoldIndicator->setActive(false);
684 }
685
686 KItemListRubberBand *rubberBand = m_view->rubberBand();
687 if (event->source() == Qt::MouseEventSynthesizedByQt && !rubberBand->isActive() && m_isTouchEvent) {
688 return false;
689 }
690
691 Q_EMIT mouseButtonReleased(m_pressedIndex.value_or(-1), event->buttons());
692
693 return onRelease(transform.map(event->pos()), event->modifiers(), event->button(), false);
694 }
695
696 bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event, const QTransform &transform)
697 {
698 const QPointF pos = transform.map(event->pos());
699 const std::optional<int> index = m_view->itemAt(pos);
700
701 if (event->button() & (Qt::ForwardButton | Qt::BackButton)) {
702 // "Forward" and "Back" are reserved for quickly navigating through the
703 // history. Double-clicking those buttons should be interpreted as two
704 // separate button presses. We arrive here for the second click, which
705 // we now react to just as we would for a singular click
706 Q_EMIT mouseButtonPressed(index.value_or(-1), event->button());
707 return false;
708 }
709
710 if (!index.has_value()) {
711 Q_EMIT doubleClickViewBackground(event->button());
712 return false;
713 }
714
715 // Expand item if desired - See Bug 295573
716 if (m_mouseDoubleClickAction != ActivateItemOnly) {
717 if (m_view && m_model && m_view->supportsItemExpanding() && m_model->isExpandable(index.value_or(-1))) {
718 const bool expanded = m_model->isExpanded(index.value());
719 m_model->setExpanded(index.value(), !expanded);
720 }
721 }
722
723 if (event->button() & ~Qt::LeftButton) {
724 return false;
725 }
726
727 if (m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) {
728 return false;
729 }
730
731 const bool emitItemActivated = index.has_value() && index.value() < m_model->count() && !m_view->isAboveExpansionToggle(index.value(), pos);
732 if (emitItemActivated) {
733 if (!QApplication::keyboardModifiers()) {
734 m_selectionManager->clearSelection(); // The user does not want to manage/manipulate the item currently, only activate it.
735 }
736 Q_EMIT itemActivated(index.value());
737 }
738 return false;
739 }
740
741 bool KItemListController::contextMenuEvent(QContextMenuEvent *event)
742 {
743 if (event->reason() == QContextMenuEvent::Keyboard) {
744 // Emit the signal itemContextMenuRequested() if at least one item is selected.
745 // Otherwise the signal viewContextMenuRequested() will be emitted.
746 const KItemSet selectedItems = m_selectionManager->selectedItems();
747 int index = -1;
748 if (selectedItems.count() >= 2) {
749 const int currentItemIndex = m_selectionManager->currentItem();
750 index = selectedItems.contains(currentItemIndex) ? currentItemIndex : selectedItems.first();
751 } else if (selectedItems.count() == 1) {
752 index = selectedItems.first();
753 }
754
755 if (index >= 0) {
756 const QRectF contextRect = m_view->itemContextRect(index);
757 const QPointF pos(m_view->scene()->views().first()->mapToGlobal(contextRect.bottomRight().toPoint()));
758 Q_EMIT itemContextMenuRequested(index, pos);
759 } else {
760 Q_EMIT viewContextMenuRequested(event->globalPos());
761 }
762 return true;
763 }
764
765 const auto pos = event->pos();
766 const auto globalPos = event->globalPos();
767
768 if (m_view->headerBoundaries().contains(pos)) {
769 Q_EMIT headerContextMenuRequested(globalPos);
770 return true;
771 }
772
773 const auto pressedItem = m_view->itemAt(pos);
774 // We only open a context menu for the pressed item if it is selected.
775 // That's because the same click might have de-selected the item or because the press was only in the row of the item but not on it.
776 if (pressedItem && m_selectionManager->selectedItems().contains(pressedItem.value())) {
777 // The selection rectangle for an item was clicked
778 Q_EMIT itemContextMenuRequested(pressedItem.value(), globalPos);
779 return true;
780 }
781
782 // Remove any hover highlights so the context menu doesn't look like it applies to a row.
783 const auto widgets = m_view->visibleItemListWidgets();
784 for (KItemListWidget *widget : widgets) {
785 if (widget->isHovered()) {
786 widget->setHovered(false);
787 Q_EMIT itemUnhovered(widget->index());
788 }
789 }
790 Q_EMIT viewContextMenuRequested(globalPos);
791 return true;
792 }
793
794 bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform)
795 {
796 Q_UNUSED(event)
797 Q_UNUSED(transform)
798
799 DragAndDropHelper::clearUrlListMatchesUrlCache();
800
801 return false;
802 }
803
804 bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform)
805 {
806 Q_UNUSED(event)
807 Q_UNUSED(transform)
808
809 m_autoActivationTimer->stop();
810 m_view->setAutoScroll(false);
811 m_view->hideDropIndicator();
812
813 KItemListWidget *widget = hoveredWidget();
814 if (widget) {
815 widget->setHovered(false);
816 Q_EMIT itemUnhovered(widget->index());
817 }
818 return false;
819 }
820
821 bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform)
822 {
823 if (!m_model || !m_view) {
824 return false;
825 }
826
827 QUrl hoveredDir = m_model->directory();
828 KItemListWidget *oldHoveredWidget = hoveredWidget();
829
830 const QPointF pos = transform.map(event->pos());
831 KItemListWidget *newHoveredWidget = widgetForDropPos(pos);
832 int index = -1;
833
834 if (oldHoveredWidget != newHoveredWidget) {
835 m_autoActivationTimer->stop();
836
837 if (oldHoveredWidget) {
838 oldHoveredWidget->setHovered(false);
839 Q_EMIT itemUnhovered(oldHoveredWidget->index());
840 }
841 }
842
843 if (newHoveredWidget) {
844 bool droppingBetweenItems = false;
845 if (m_model->sortRole().isEmpty()) {
846 // The model supports inserting items between other items.
847 droppingBetweenItems = (m_view->showDropIndicator(pos) >= 0);
848 }
849
850 index = newHoveredWidget->index();
851
852 if (m_model->isDir(index)) {
853 hoveredDir = m_model->url(index);
854 }
855
856 if (!droppingBetweenItems) {
857 // Something has been dragged on an item.
858 m_view->hideDropIndicator();
859 if (!newHoveredWidget->isHovered()) {
860 newHoveredWidget->setHovered(true);
861 Q_EMIT itemHovered(index);
862 }
863
864 if (m_autoActivationEnabled && !m_autoActivationTimer->isActive() && m_model->canEnterOnHover(index)) {
865 m_autoActivationTimer->setProperty("index", index);
866 m_autoActivationTimer->start();
867 newHoveredWidget->startActivateSoonAnimation(m_autoActivationTimer->remainingTime());
868 }
869
870 } else {
871 m_autoActivationTimer->stop();
872 if (newHoveredWidget && newHoveredWidget->isHovered()) {
873 newHoveredWidget->setHovered(false);
874 Q_EMIT itemUnhovered(index);
875 }
876 }
877 } else {
878 m_view->hideDropIndicator();
879 }
880
881 if (DragAndDropHelper::urlListMatchesUrl(event->mimeData()->urls(), hoveredDir)) {
882 event->setDropAction(Qt::IgnoreAction);
883 event->ignore();
884 } else {
885 if (m_model->supportsDropping(index)) {
886 event->setDropAction(event->proposedAction());
887 event->accept();
888 } else {
889 event->setDropAction(Qt::IgnoreAction);
890 event->ignore();
891 }
892 }
893 return false;
894 }
895
896 bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform)
897 {
898 if (!m_view) {
899 return false;
900 }
901
902 m_autoActivationTimer->stop();
903 m_view->setAutoScroll(false);
904
905 const QPointF pos = transform.map(event->pos());
906
907 int dropAboveIndex = -1;
908 if (m_model->sortRole().isEmpty()) {
909 // The model supports inserting of items between other items.
910 dropAboveIndex = m_view->showDropIndicator(pos);
911 }
912
913 if (dropAboveIndex >= 0) {
914 // Something has been dropped between two items.
915 m_view->hideDropIndicator();
916 Q_EMIT aboveItemDropEvent(dropAboveIndex, event);
917 } else if (!event->mimeData()->hasFormat(m_model->blacklistItemDropEventMimeType())) {
918 // Something has been dropped on an item or on an empty part of the view.
919 const KItemListWidget *receivingWidget = widgetForDropPos(pos);
920 if (receivingWidget) {
921 Q_EMIT itemDropEvent(receivingWidget->index(), event);
922 } else {
923 Q_EMIT itemDropEvent(-1, event);
924 }
925 }
926
927 QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropEnd);
928 QAccessible::updateAccessibility(&accessibilityEvent);
929
930 return true;
931 }
932
933 bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent *event, const QTransform &transform)
934 {
935 Q_UNUSED(event)
936 Q_UNUSED(transform)
937 return false;
938 }
939
940 bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent *event, const QTransform &transform)
941 {
942 Q_UNUSED(transform)
943 if (!m_model || !m_view) {
944 return false;
945 }
946
947 // We identify the widget whose expansionArea had been hovered before this hoverMoveEvent() triggered.
948 // we can't use hoveredWidget() here (it handles the icon+text rect, not the expansion rect)
949 // like hoveredWidget(), we find the hovered widget for the expansion rect
950 const auto visibleItemListWidgets = m_view->visibleItemListWidgets();
951 const auto oldHoveredExpansionWidgetIterator = std::find_if(visibleItemListWidgets.begin(), visibleItemListWidgets.end(), [](auto &widget) {
952 return widget->expansionAreaHovered();
953 });
954 const auto oldHoveredExpansionWidget =
955 oldHoveredExpansionWidgetIterator == visibleItemListWidgets.end() ? std::nullopt : std::make_optional(*oldHoveredExpansionWidgetIterator);
956
957 const auto unhoverOldHoveredWidget = [&]() {
958 if (auto oldHoveredWidget = hoveredWidget(); oldHoveredWidget) {
959 // handle the text+icon one
960 oldHoveredWidget->setHovered(false);
961 Q_EMIT itemUnhovered(oldHoveredWidget->index());
962 }
963 };
964
965 const auto unhoverOldExpansionWidget = [&]() {
966 if (oldHoveredExpansionWidget) {
967 // then the expansion toggle
968 (*oldHoveredExpansionWidget)->setExpansionAreaHovered(false);
969 }
970 };
971
972 const QPointF pos = transform.map(event->pos());
973 if (KItemListWidget *newHoveredWidget = widgetForPos(pos); newHoveredWidget) {
974 // something got hovered, work out which part and set hover for the appropriate widget
975 const auto mappedPos = newHoveredWidget->mapFromItem(m_view, pos);
976 const bool isOnExpansionToggle = newHoveredWidget->expansionToggleRect().contains(mappedPos);
977
978 if (isOnExpansionToggle) {
979 // make sure we unhover the old one first if old!=new
980 if (oldHoveredExpansionWidget && *oldHoveredExpansionWidget != newHoveredWidget) {
981 (*oldHoveredExpansionWidget)->setExpansionAreaHovered(false);
982 }
983 // we also unhover any old icon+text hovers, in case the mouse movement from icon+text to expansion toggle is too fast (i.e. newHoveredWidget is never null between the transition)
984 unhoverOldHoveredWidget();
985
986 newHoveredWidget->setExpansionAreaHovered(true);
987 } else {
988 // make sure we unhover the old one first if old!=new
989 auto oldHoveredWidget = hoveredWidget();
990 if (oldHoveredWidget && oldHoveredWidget != newHoveredWidget) {
991 oldHoveredWidget->setHovered(false);
992 Q_EMIT itemUnhovered(oldHoveredWidget->index());
993 }
994 // we also unhover any old expansion toggle hovers, in case the mouse movement from expansion toggle to icon+text is too fast (i.e. newHoveredWidget is never null between the transition)
995 unhoverOldExpansionWidget();
996
997 const bool isOverIconAndText = newHoveredWidget->selectionRectCore().contains(mappedPos);
998 const bool hasMultipleSelection = m_selectionManager->selectedItems().count() > 1;
999
1000 if (hasMultipleSelection && !isOverIconAndText) {
1001 // In case we have multiple selections, clicking on any row will deselect the selection.
1002 // So, as a visual cue for signalling that clicking anywhere won't select, but clear current highlights,
1003 // we disable hover of the *row*(i.e. blank space to the right of the icon+text)
1004
1005 // (no-op in this branch for masked hover)
1006 } else {
1007 newHoveredWidget->setHoverPosition(mappedPos);
1008 if (oldHoveredWidget != newHoveredWidget) {
1009 newHoveredWidget->setHovered(true);
1010 Q_EMIT itemHovered(newHoveredWidget->index());
1011 }
1012 }
1013 }
1014 } else {
1015 // unhover any currently hovered expansion and text+icon widgets
1016 unhoverOldHoveredWidget();
1017 unhoverOldExpansionWidget();
1018 }
1019 return false;
1020 }
1021
1022 bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent *event, const QTransform &transform)
1023 {
1024 Q_UNUSED(event)
1025 Q_UNUSED(transform)
1026
1027 m_mousePress = false;
1028 m_isTouchEvent = false;
1029
1030 if (!m_model || !m_view) {
1031 return false;
1032 }
1033
1034 const auto widgets = m_view->visibleItemListWidgets();
1035 for (KItemListWidget *widget : widgets) {
1036 widget->setPressed(false);
1037 if (widget->isHovered()) {
1038 widget->setHovered(false);
1039 Q_EMIT itemUnhovered(widget->index());
1040 }
1041 }
1042 return false;
1043 }
1044
1045 bool KItemListController::wheelEvent(QGraphicsSceneWheelEvent *event, const QTransform &transform)
1046 {
1047 Q_UNUSED(event)
1048 Q_UNUSED(transform)
1049 return false;
1050 }
1051
1052 bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent *event, const QTransform &transform)
1053 {
1054 Q_UNUSED(event)
1055 Q_UNUSED(transform)
1056 return false;
1057 }
1058
1059 bool KItemListController::gestureEvent(QGestureEvent *event, const QTransform &transform)
1060 {
1061 if (!m_view) {
1062 return false;
1063 }
1064
1065 //you can touch on different views at the same time, but only one QWidget gets a mousePressEvent
1066 //we use this to get the right QWidget
1067 //the only exception is a tap gesture with state GestureStarted, we need to reset some variable
1068 if (!m_mousePress) {
1069 if (QGesture *tap = event->gesture(Qt::TapGesture)) {
1070 QTapGesture *tapGesture = static_cast<QTapGesture *>(tap);
1071 if (tapGesture->state() == Qt::GestureStarted) {
1072 tapTriggered(tapGesture, transform);
1073 }
1074 }
1075 return false;
1076 }
1077
1078 bool accepted = false;
1079
1080 if (QGesture *tap = event->gesture(Qt::TapGesture)) {
1081 tapTriggered(static_cast<QTapGesture *>(tap), transform);
1082 accepted = true;
1083 }
1084 if (event->gesture(Qt::TapAndHoldGesture)) {
1085 tapAndHoldTriggered(event, transform);
1086 accepted = true;
1087 }
1088 if (event->gesture(Qt::PinchGesture)) {
1089 pinchTriggered(event, transform);
1090 accepted = true;
1091 }
1092 if (event->gesture(m_swipeGesture)) {
1093 swipeTriggered(event, transform);
1094 accepted = true;
1095 }
1096 if (event->gesture(m_twoFingerTapGesture)) {
1097 twoFingerTapTriggered(event, transform);
1098 accepted = true;
1099 }
1100 return accepted;
1101 }
1102
1103 bool KItemListController::touchBeginEvent(QTouchEvent *event, const QTransform &transform)
1104 {
1105 Q_UNUSED(event)
1106 Q_UNUSED(transform)
1107
1108 m_isTouchEvent = true;
1109 return false;
1110 }
1111
1112 void KItemListController::tapTriggered(QTapGesture *tap, const QTransform &transform)
1113 {
1114 static bool scrollerWasActive = false;
1115
1116 if (tap->state() == Qt::GestureStarted) {
1117 m_dragActionOrRightClick = false;
1118 m_isSwipeGesture = false;
1119 m_pinchGestureInProgress = false;
1120 scrollerWasActive = m_scrollerIsScrolling;
1121 }
1122
1123 if (tap->state() == Qt::GestureFinished) {
1124 m_mousePress = false;
1125
1126 //if at the moment of the gesture start the QScroller was active, the user made the tap
1127 //to stop the QScroller and not to tap on an item
1128 if (scrollerWasActive) {
1129 return;
1130 }
1131
1132 if (m_view->m_tapAndHoldIndicator->isActive()) {
1133 m_view->m_tapAndHoldIndicator->setActive(false);
1134 }
1135
1136 const QPointF pressedMousePos = transform.map(tap->position());
1137 m_pressedIndex = m_view->itemAt(pressedMousePos);
1138 if (m_dragActionOrRightClick) {
1139 m_dragActionOrRightClick = false;
1140 } else {
1141 onPress(tap->position().toPoint(), Qt::NoModifier, Qt::LeftButton);
1142 onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::LeftButton, true);
1143 }
1144 m_isTouchEvent = false;
1145 }
1146 }
1147
1148 void KItemListController::tapAndHoldTriggered(QGestureEvent *event, const QTransform &transform)
1149 {
1150 //the Qt TabAndHold gesture is triggerable with a mouse click, we don't want this
1151 if (!m_isTouchEvent) {
1152 return;
1153 }
1154
1155 const QTapAndHoldGesture *tap = static_cast<QTapAndHoldGesture *>(event->gesture(Qt::TapAndHoldGesture));
1156 if (tap->state() == Qt::GestureFinished) {
1157 //if a pinch gesture is in progress we don't want a TabAndHold gesture
1158 if (m_pinchGestureInProgress) {
1159 return;
1160 }
1161 const QPointF pressedMousePos = transform.map(event->mapToGraphicsScene(tap->position()));
1162 m_pressedIndex = m_view->itemAt(pressedMousePos);
1163 if (m_pressedIndex.has_value()) {
1164 if (!m_selectionManager->isSelected(m_pressedIndex.value())) {
1165 m_selectionManager->clearSelection();
1166 m_selectionManager->setSelected(m_pressedIndex.value());
1167 }
1168 if (!m_selectionMode) {
1169 Q_EMIT selectionModeChangeRequested(true);
1170 }
1171 } else {
1172 m_selectionManager->clearSelection();
1173 startRubberBand();
1174 }
1175
1176 Q_EMIT scrollerStop();
1177
1178 m_view->m_tapAndHoldIndicator->setStartPosition(pressedMousePos);
1179 m_view->m_tapAndHoldIndicator->setActive(true);
1180
1181 m_dragActionOrRightClick = true;
1182 }
1183 }
1184
1185 void KItemListController::pinchTriggered(QGestureEvent *event, const QTransform &transform)
1186 {
1187 Q_UNUSED(transform)
1188
1189 const QPinchGesture *pinch = static_cast<QPinchGesture *>(event->gesture(Qt::PinchGesture));
1190 const qreal sensitivityModifier = 0.2;
1191 static qreal counter = 0;
1192
1193 if (pinch->state() == Qt::GestureStarted) {
1194 m_pinchGestureInProgress = true;
1195 counter = 0;
1196 }
1197 if (pinch->state() == Qt::GestureUpdated) {
1198 //if a swipe gesture was recognized or in progress, we don't want a pinch gesture to change the zoom
1199 if (m_isSwipeGesture) {
1200 return;
1201 }
1202 counter = counter + (pinch->scaleFactor() - 1);
1203 if (counter >= sensitivityModifier) {
1204 Q_EMIT increaseZoom();
1205 counter = 0;
1206 } else if (counter <= -sensitivityModifier) {
1207 Q_EMIT decreaseZoom();
1208 counter = 0;
1209 }
1210 }
1211 }
1212
1213 void KItemListController::swipeTriggered(QGestureEvent *event, const QTransform &transform)
1214 {
1215 Q_UNUSED(transform)
1216
1217 const KTwoFingerSwipe *swipe = static_cast<KTwoFingerSwipe *>(event->gesture(m_swipeGesture));
1218
1219 if (!swipe) {
1220 return;
1221 }
1222 if (swipe->state() == Qt::GestureStarted) {
1223 m_isSwipeGesture = true;
1224 }
1225
1226 if (swipe->state() == Qt::GestureCanceled) {
1227 m_isSwipeGesture = false;
1228 }
1229
1230 if (swipe->state() == Qt::GestureFinished) {
1231 Q_EMIT scrollerStop();
1232
1233 if (swipe->swipeAngle() <= 20 || swipe->swipeAngle() >= 340) {
1234 Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), Qt::BackButton);
1235 } else if (swipe->swipeAngle() <= 200 && swipe->swipeAngle() >= 160) {
1236 Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), Qt::ForwardButton);
1237 } else if (swipe->swipeAngle() <= 110 && swipe->swipeAngle() >= 60) {
1238 Q_EMIT swipeUp();
1239 }
1240 m_isSwipeGesture = true;
1241 }
1242 }
1243
1244 void KItemListController::twoFingerTapTriggered(QGestureEvent *event, const QTransform &transform)
1245 {
1246 const KTwoFingerTap *twoTap = static_cast<KTwoFingerTap *>(event->gesture(m_twoFingerTapGesture));
1247
1248 if (!twoTap) {
1249 return;
1250 }
1251
1252 if (twoTap->state() == Qt::GestureStarted) {
1253 const QPointF pressedMousePos = transform.map(twoTap->pos());
1254 m_pressedIndex = m_view->itemAt(pressedMousePos);
1255 if (m_pressedIndex.has_value()) {
1256 onPress(twoTap->pos().toPoint(), Qt::ControlModifier, Qt::LeftButton);
1257 onRelease(transform.map(twoTap->pos()), Qt::ControlModifier, Qt::LeftButton, false);
1258 }
1259 }
1260 }
1261
1262 bool KItemListController::processEvent(QEvent *event, const QTransform &transform)
1263 {
1264 if (!event) {
1265 return false;
1266 }
1267
1268 switch (event->type()) {
1269 case QEvent::KeyPress:
1270 return keyPressEvent(static_cast<QKeyEvent *>(event));
1271 case QEvent::InputMethod:
1272 return inputMethodEvent(static_cast<QInputMethodEvent *>(event));
1273 case QEvent::GraphicsSceneMousePress:
1274 return mousePressEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1275 case QEvent::GraphicsSceneMouseMove:
1276 return mouseMoveEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1277 case QEvent::GraphicsSceneMouseRelease:
1278 return mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1279 case QEvent::GraphicsSceneMouseDoubleClick:
1280 return mouseDoubleClickEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1281 case QEvent::ContextMenu:
1282 return contextMenuEvent(static_cast<QContextMenuEvent *>(event));
1283 case QEvent::GraphicsSceneWheel:
1284 return wheelEvent(static_cast<QGraphicsSceneWheelEvent *>(event), QTransform());
1285 case QEvent::GraphicsSceneDragEnter:
1286 return dragEnterEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1287 case QEvent::GraphicsSceneDragLeave:
1288 return dragLeaveEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1289 case QEvent::GraphicsSceneDragMove:
1290 return dragMoveEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1291 case QEvent::GraphicsSceneDrop:
1292 return dropEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1293 case QEvent::GraphicsSceneHoverEnter:
1294 return hoverEnterEvent(static_cast<QGraphicsSceneHoverEvent *>(event), QTransform());
1295 case QEvent::GraphicsSceneHoverMove:
1296 return hoverMoveEvent(static_cast<QGraphicsSceneHoverEvent *>(event), QTransform());
1297 case QEvent::GraphicsSceneHoverLeave:
1298 return hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent *>(event), QTransform());
1299 case QEvent::GraphicsSceneResize:
1300 return resizeEvent(static_cast<QGraphicsSceneResizeEvent *>(event), transform);
1301 case QEvent::Gesture:
1302 return gestureEvent(static_cast<QGestureEvent *>(event), transform);
1303 case QEvent::TouchBegin:
1304 return touchBeginEvent(static_cast<QTouchEvent *>(event), transform);
1305 default:
1306 break;
1307 }
1308
1309 return false;
1310 }
1311
1312 void KItemListController::slotViewScrollOffsetChanged(qreal current, qreal previous)
1313 {
1314 if (!m_view) {
1315 return;
1316 }
1317
1318 KItemListRubberBand *rubberBand = m_view->rubberBand();
1319 if (rubberBand->isActive()) {
1320 const qreal diff = current - previous;
1321 // TODO: Ideally just QCursor::pos() should be used as
1322 // new end-position but it seems there is no easy way
1323 // to have something like QWidget::mapFromGlobal() for QGraphicsWidget
1324 // (... or I just missed an easy way to do the mapping)
1325 QPointF endPos = rubberBand->endPosition();
1326 if (m_view->scrollOrientation() == Qt::Vertical) {
1327 endPos.ry() += diff;
1328 } else {
1329 endPos.rx() += diff;
1330 }
1331
1332 rubberBand->setEndPosition(endPos);
1333 }
1334 }
1335
1336 void KItemListController::slotRubberBandChanged()
1337 {
1338 if (!m_view || !m_model || m_model->count() <= 0) {
1339 return;
1340 }
1341
1342 const KItemListRubberBand *rubberBand = m_view->rubberBand();
1343 const QPointF startPos = rubberBand->startPosition();
1344 const QPointF endPos = rubberBand->endPosition();
1345 QRectF rubberBandRect = QRectF(startPos, endPos).normalized();
1346
1347 const bool scrollVertical = (m_view->scrollOrientation() == Qt::Vertical);
1348 if (scrollVertical) {
1349 rubberBandRect.translate(0, -m_view->scrollOffset());
1350 } else {
1351 rubberBandRect.translate(-m_view->scrollOffset(), 0);
1352 }
1353
1354 if (!m_oldSelection.isEmpty()) {
1355 // Clear the old selection that was available before the rubberband has
1356 // been activated in case if no Shift- or Control-key are pressed
1357 const bool shiftOrControlPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier || QApplication::keyboardModifiers() & Qt::ControlModifier;
1358 if (!shiftOrControlPressed && !m_selectionMode) {
1359 m_oldSelection.clear();
1360 }
1361 }
1362
1363 KItemSet selectedItems;
1364
1365 // Select all visible items that intersect with the rubberband
1366 const auto widgets = m_view->visibleItemListWidgets();
1367 for (const KItemListWidget *widget : widgets) {
1368 const int index = widget->index();
1369
1370 const QRectF widgetRect = m_view->itemRect(index);
1371 if (widgetRect.intersects(rubberBandRect)) {
1372 // Select the full row intersecting with the rubberband rectangle
1373 const QRectF selectionRect = widget->selectionRectFull().translated(widgetRect.topLeft());
1374 if (selectionRect.intersects(rubberBandRect)) {
1375 selectedItems.insert(index);
1376 }
1377 }
1378 }
1379
1380 // Select all invisible items that intersect with the rubberband. Instead of
1381 // iterating all items only the area which might be touched by the rubberband
1382 // will be checked.
1383 const bool increaseIndex = scrollVertical ? startPos.y() > endPos.y() : startPos.x() > endPos.x();
1384
1385 int index = increaseIndex ? m_view->lastVisibleIndex() + 1 : m_view->firstVisibleIndex() - 1;
1386 bool selectionFinished = false;
1387 do {
1388 const QRectF widgetRect = m_view->itemRect(index);
1389 if (widgetRect.intersects(rubberBandRect)) {
1390 selectedItems.insert(index);
1391 }
1392
1393 if (increaseIndex) {
1394 ++index;
1395 selectionFinished = (index >= m_model->count()) || (scrollVertical && widgetRect.top() > rubberBandRect.bottom())
1396 || (!scrollVertical && widgetRect.left() > rubberBandRect.right());
1397 } else {
1398 --index;
1399 selectionFinished = (index < 0) || (scrollVertical && widgetRect.bottom() < rubberBandRect.top())
1400 || (!scrollVertical && widgetRect.right() < rubberBandRect.left());
1401 }
1402 } while (!selectionFinished);
1403
1404 if ((QApplication::keyboardModifiers() & Qt::ControlModifier) || m_selectionMode) {
1405 // If Control is pressed, the selection state of all items in the rubberband is toggled.
1406 // Therefore, the new selection contains:
1407 // 1. All previously selected items which are not inside the rubberband, and
1408 // 2. all items inside the rubberband which have not been selected previously.
1409 m_selectionManager->setSelectedItems(m_oldSelection ^ selectedItems);
1410 } else {
1411 m_selectionManager->setSelectedItems(selectedItems + m_oldSelection);
1412 }
1413 }
1414
1415 void KItemListController::startDragging()
1416 {
1417 if (!m_view || !m_model) {
1418 return;
1419 }
1420
1421 const KItemSet selectedItems = m_selectionManager->selectedItems();
1422 if (selectedItems.isEmpty()) {
1423 return;
1424 }
1425
1426 QMimeData *data = m_model->createMimeData(selectedItems);
1427 if (!data) {
1428 return;
1429 }
1430 KUrlMimeData::exportUrlsToPortal(data);
1431
1432 // The created drag object will be owned and deleted
1433 // by QApplication::activeWindow().
1434 QDrag *drag = new QDrag(QApplication::activeWindow());
1435 drag->setMimeData(data);
1436
1437 const QPixmap pixmap = m_view->createDragPixmap(selectedItems);
1438 drag->setPixmap(pixmap);
1439
1440 const QPoint hotSpot((pixmap.width() / pixmap.devicePixelRatio()) / 2, 0);
1441 drag->setHotSpot(hotSpot);
1442
1443 drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction);
1444
1445 QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropStart);
1446 QAccessible::updateAccessibility(&accessibilityEvent);
1447 }
1448
1449 KItemListWidget *KItemListController::hoveredWidget() const
1450 {
1451 Q_ASSERT(m_view);
1452
1453 const auto widgets = m_view->visibleItemListWidgets();
1454 for (KItemListWidget *widget : widgets) {
1455 if (widget->isHovered()) {
1456 return widget;
1457 }
1458 }
1459
1460 return nullptr;
1461 }
1462
1463 KItemListWidget *KItemListController::widgetForPos(const QPointF &pos) const
1464 {
1465 Q_ASSERT(m_view);
1466
1467 if (m_view->headerBoundaries().contains(pos)) {
1468 return nullptr;
1469 }
1470
1471 const auto widgets = m_view->visibleItemListWidgets();
1472 for (KItemListWidget *widget : widgets) {
1473 const QPointF mappedPos = widget->mapFromItem(m_view, pos);
1474 if (widget->contains(mappedPos)) {
1475 return widget;
1476 }
1477 }
1478
1479 return nullptr;
1480 }
1481
1482 KItemListWidget *KItemListController::widgetForDropPos(const QPointF &pos) const
1483 {
1484 Q_ASSERT(m_view);
1485
1486 if (m_view->headerBoundaries().contains(pos)) {
1487 return nullptr;
1488 }
1489
1490 const auto widgets = m_view->visibleItemListWidgets();
1491 for (KItemListWidget *widget : widgets) {
1492 const QPointF mappedPos = widget->mapFromItem(m_view, pos);
1493 if (widget->selectionRectCore().contains(mappedPos)) {
1494 return widget;
1495 }
1496 }
1497
1498 return nullptr;
1499 }
1500
1501 void KItemListController::updateKeyboardAnchor()
1502 {
1503 const bool validAnchor =
1504 m_keyboardAnchorIndex >= 0 && m_keyboardAnchorIndex < m_model->count() && keyboardAnchorPos(m_keyboardAnchorIndex) == m_keyboardAnchorPos;
1505 if (!validAnchor) {
1506 const int index = m_selectionManager->currentItem();
1507 m_keyboardAnchorIndex = index;
1508 m_keyboardAnchorPos = keyboardAnchorPos(index);
1509 }
1510 }
1511
1512 int KItemListController::nextRowIndex(int index) const
1513 {
1514 if (m_keyboardAnchorIndex < 0) {
1515 return index;
1516 }
1517
1518 const int maxIndex = m_model->count() - 1;
1519 if (index == maxIndex) {
1520 return index;
1521 }
1522
1523 const bool reversed = m_view->layoutDirection() == Qt::RightToLeft && m_view->scrollOrientation() == Qt::Vertical;
1524
1525 // Calculate the index of the last column inside the row of the current index
1526 int lastColumnIndex = index;
1527 while ((!reversed && keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex))
1528 || (reversed && keyboardAnchorPos(lastColumnIndex + 1) < keyboardAnchorPos(lastColumnIndex))) {
1529 ++lastColumnIndex;
1530 if (lastColumnIndex >= maxIndex) {
1531 return index;
1532 }
1533 }
1534
1535 // Based on the last column index go to the next row and calculate the nearest index
1536 // that is below the current index
1537 int nextRowIndex = lastColumnIndex + 1;
1538 int searchIndex = nextRowIndex;
1539 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(nextRowIndex));
1540 while (searchIndex < maxIndex
1541 && ((!reversed && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex))
1542 || (reversed && keyboardAnchorPos(searchIndex + 1) < keyboardAnchorPos(searchIndex)))) {
1543 ++searchIndex;
1544 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1545 if (searchDiff < minDiff) {
1546 minDiff = searchDiff;
1547 nextRowIndex = searchIndex;
1548 }
1549 }
1550
1551 return nextRowIndex;
1552 }
1553
1554 int KItemListController::previousRowIndex(int index) const
1555 {
1556 if (m_keyboardAnchorIndex < 0 || index == 0) {
1557 return index;
1558 }
1559
1560 const bool reversed = m_view->layoutDirection() == Qt::RightToLeft && m_view->scrollOrientation() == Qt::Vertical;
1561
1562 // Calculate the index of the first column inside the row of the current index
1563 int firstColumnIndex = index;
1564 while ((!reversed && keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex))
1565 || (reversed && keyboardAnchorPos(firstColumnIndex - 1) > keyboardAnchorPos(firstColumnIndex))) {
1566 --firstColumnIndex;
1567 if (firstColumnIndex <= 0) {
1568 return index;
1569 }
1570 }
1571
1572 // Based on the first column index go to the previous row and calculate the nearest index
1573 // that is above the current index
1574 int previousRowIndex = firstColumnIndex - 1;
1575 int searchIndex = previousRowIndex;
1576 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(previousRowIndex));
1577 while (searchIndex > 0
1578 && ((!reversed && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex))
1579 || (reversed && keyboardAnchorPos(searchIndex - 1) > keyboardAnchorPos(searchIndex)))) {
1580 --searchIndex;
1581 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1582 if (searchDiff < minDiff) {
1583 minDiff = searchDiff;
1584 previousRowIndex = searchIndex;
1585 }
1586 }
1587
1588 return previousRowIndex;
1589 }
1590
1591 qreal KItemListController::keyboardAnchorPos(int index) const
1592 {
1593 const QRectF itemRect = m_view->itemRect(index);
1594 if (!itemRect.isEmpty()) {
1595 return (m_view->scrollOrientation() == Qt::Vertical) ? itemRect.x() : itemRect.y();
1596 }
1597
1598 return 0;
1599 }
1600
1601 void KItemListController::updateExtendedSelectionRegion()
1602 {
1603 if (m_view) {
1604 const bool extend = (m_selectionBehavior != MultiSelection);
1605 KItemListStyleOption option = m_view->styleOption();
1606 if (option.extendedSelectionRegion != extend) {
1607 option.extendedSelectionRegion = extend;
1608 m_view->setStyleOption(option);
1609 }
1610 }
1611 }
1612
1613 bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons)
1614 {
1615 Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), buttons);
1616
1617 if (buttons & (Qt::BackButton | Qt::ForwardButton)) {
1618 // Do not select items when clicking the back/forward buttons, see
1619 // https://bugs.kde.org/show_bug.cgi?id=327412.
1620 return true;
1621 }
1622
1623 const QPointF pressedMousePos = m_view->transform().map(pos);
1624
1625 if (m_view->isAboveExpansionToggle(m_pressedIndex.value_or(-1), pressedMousePos)) {
1626 m_selectionManager->endAnchoredSelection();
1627 m_selectionManager->setCurrentItem(m_pressedIndex.value());
1628 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1629 return true;
1630 }
1631
1632 m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), pressedMousePos);
1633 if (m_selectionTogglePressed) {
1634 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1635 // The previous anchored selection has been finished already in
1636 // KItemListSelectionManager::setSelected(). We can safely change
1637 // the current item and start a new anchored selection now.
1638 m_selectionManager->setCurrentItem(m_pressedIndex.value());
1639 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1640 return true;
1641 }
1642
1643 const bool shiftPressed = modifiers & Qt::ShiftModifier;
1644 const bool controlPressed = (modifiers & Qt::ControlModifier) || m_selectionMode; // Keeping selectionMode similar to pressing control will hopefully
1645 // simplify the overall logic and possibilities both for users and devs.
1646 const bool leftClick = buttons & Qt::LeftButton;
1647 const bool rightClick = buttons & Qt::RightButton;
1648 const bool singleClickActivation = m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick);
1649
1650 // The previous selection is cleared if either
1651 // 1. The selection mode is SingleSelection, or
1652 // 2. the selection mode is MultiSelection, and *none* of the following conditions are met:
1653 // a) Shift or Control are pressed.
1654 // b) The clicked item is selected already. In that case, the user might want to:
1655 // - start dragging multiple items, or
1656 // - open the context menu and perform an action for all selected items.
1657 const bool shiftOrControlPressed = shiftPressed || controlPressed;
1658 const bool pressedItemAlreadySelected = m_pressedIndex.has_value() && m_selectionManager->isSelected(m_pressedIndex.value());
1659 const bool clearSelection = m_selectionBehavior == SingleSelection || (!shiftOrControlPressed && !pressedItemAlreadySelected);
1660
1661 // When this method returns false, a rubberBand selection is created using KItemListController::startRubberBand via the caller.
1662 if (clearSelection) {
1663 const int selectedItemsCount = m_selectionManager->selectedItems().count();
1664 m_selectionManager->clearSelection();
1665 // clear and bail when we got an existing multi-selection
1666 if (selectedItemsCount > 1 && m_pressedIndex.has_value()) {
1667 const auto row = m_view->m_visibleItems.value(m_pressedIndex.value());
1668 const auto mappedPos = row->mapFromItem(m_view, pos);
1669 if (row->selectionRectCore().contains(mappedPos)) {
1670 // we are indeed inside the text/icon rect, keep m_pressedIndex what it is
1671 // and short-circuit for single-click activation (it will then propagate to onRelease and activate the item)
1672 // or we just keep going for double-click activation
1673 if (singleClickActivation || m_singleClickActivationEnforced) {
1674 if (!pressedItemAlreadySelected) {
1675 // An unselected item was clicked directly while deselecting multiple other items so we mark it "current".
1676 m_selectionManager->setCurrentItem(m_pressedIndex.value());
1677 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1678 if (!leftClick) {
1679 // We select the item here because this press is not meant to directly activate the item.
1680 // We do not want to select items unless the user wants to edit them.
1681 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1682 }
1683 }
1684 if (leftClick) {
1685 row->setPressed(true);
1686 }
1687 return true; // event handled, don't create rubber band
1688 }
1689 } else {
1690 // we're not inside the text/icon rect, as we've already cleared the selection
1691 // we can just stop here and make sure handlers down the line (i.e. onRelease) don't activate
1692 m_pressedIndex.reset();
1693 // we don't stop event propagation and proceed to create a rubber band and let onRelease
1694 // decide (based on m_pressedIndex) whether we're in a drag (drag => new rubber band, click => don't select the item)
1695 return false;
1696 }
1697 }
1698 } else if (pressedItemAlreadySelected && !shiftOrControlPressed && leftClick) {
1699 // The user might want to start dragging multiple items, but if he clicks the item
1700 // in order to trigger it instead, the other selected items must be deselected.
1701 // However, we do not know yet what the user is going to do.
1702 // -> remember that the user pressed an item which had been selected already and
1703 // clear the selection in mouseReleaseEvent(), unless the items are dragged.
1704 m_clearSelectionIfItemsAreNotDragged = true;
1705
1706 if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex.value_or(-1), pressedMousePos)) {
1707 Q_EMIT selectedItemTextPressed(m_pressedIndex.value_or(-1));
1708 }
1709 }
1710
1711 if (!shiftPressed) {
1712 // Finish the anchored selection before the current index is changed
1713 m_selectionManager->endAnchoredSelection();
1714 }
1715
1716 if (rightClick) {
1717 // Stop rubber band from persisting after right-clicks
1718 KItemListRubberBand *rubberBand = m_view->rubberBand();
1719 if (rubberBand->isActive()) {
1720 disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1721 rubberBand->setActive(false);
1722 m_view->setAutoScroll(false);
1723 }
1724
1725 if (!m_pressedIndex.has_value()) {
1726 // We have a right-click in an empty region, don't create rubber band.
1727 return true;
1728 }
1729 }
1730
1731 if (m_pressedIndex.has_value()) {
1732 // The hover highlight area of an item is being pressed.
1733 const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); // anything outside of row.contains() will be the empty region of the row rect
1734
1735 const bool hitTargetIsRowEmptyRegion = !row->selectionRectCore().contains(row->mapFromItem(m_view, pos));
1736 // again, when this method returns false, a rubberBand selection is created as the event is not consumed;
1737 // createRubberBand here tells us whether to return true or false.
1738 bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty());
1739
1740 if (leftClick) {
1741 row->setPressed(true);
1742 }
1743
1744 if (rightClick && hitTargetIsRowEmptyRegion) {
1745 // We have a right click outside the icon and text rect but within the hover highlight area.
1746 // We don't want items to get selected through this, so we return now.
1747 return true;
1748 }
1749
1750 m_selectionManager->setCurrentItem(m_pressedIndex.value());
1751
1752 switch (m_selectionBehavior) {
1753 case NoSelection:
1754 break;
1755
1756 case SingleSelection:
1757 if (!leftClick || shiftOrControlPressed || (!singleClickActivation && !m_singleClickActivationEnforced)) {
1758 m_selectionManager->setSelected(m_pressedIndex.value());
1759 }
1760 break;
1761
1762 case MultiSelection:
1763 if (controlPressed && !shiftPressed && leftClick) {
1764 // A left mouse button press is happening on an item while control is pressed. This either means a user wants to:
1765 // - toggle the selection of item(s) or
1766 // - they want to begin a drag on the item(s) to copy them.
1767 // We rule out the latter, if the item is not clicked directly and was unselected previously.
1768 const auto row = m_view->m_visibleItems.value(m_pressedIndex.value());
1769 const auto mappedPos = row->mapFromItem(m_view, pos);
1770 if (!row->selectionRectCore().contains(mappedPos)) {
1771 createRubberBand = true;
1772 } else {
1773 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1774 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1775 createRubberBand = false; // multi selection, don't propagate any further
1776 // This will be the start of an item drag-to-copy operation if the user now moves the mouse before releasing the mouse button.
1777 }
1778 } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
1779 // Select the pressed item and start a new anchored selection
1780 if (!leftClick || shiftOrControlPressed || (!singleClickActivation && !m_singleClickActivationEnforced)) {
1781 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select);
1782 }
1783 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1784 }
1785 break;
1786
1787 default:
1788 Q_ASSERT(false);
1789 break;
1790 }
1791
1792 return !createRubberBand;
1793 }
1794
1795 return false;
1796 }
1797
1798 bool KItemListController::onRelease(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch)
1799 {
1800 const QPointF pressedMousePos = pos;
1801 const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), pressedMousePos);
1802 if (isAboveSelectionToggle) {
1803 m_selectionTogglePressed = false;
1804 return true;
1805 }
1806
1807 if (!isAboveSelectionToggle && m_selectionTogglePressed) {
1808 m_selectionManager->setSelected(m_pressedIndex.value_or(-1), 1, KItemListSelectionManager::Toggle);
1809 m_selectionTogglePressed = false;
1810 return true;
1811 }
1812
1813 const bool controlPressed = modifiers & Qt::ControlModifier;
1814 const bool shiftOrControlPressed = modifiers & Qt::ShiftModifier || controlPressed;
1815
1816 const std::optional<int> index = m_view->itemAt(pos);
1817
1818 KItemListRubberBand *rubberBand = m_view->rubberBand();
1819 bool rubberBandRelease = false;
1820 if (rubberBand->isActive()) {
1821 disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1822 rubberBand->setActive(false);
1823 m_oldSelection.clear();
1824 m_view->setAutoScroll(false);
1825 rubberBandRelease = true;
1826 // We check for actual rubber band drag here: if delta between start and end is less than drag threshold,
1827 // then we have a single click on one of the rows
1828 if ((rubberBand->endPosition() - rubberBand->startPosition()).manhattanLength() < QApplication::startDragDistance()) {
1829 rubberBandRelease = false; // since we're only selecting, unmark rubber band release flag
1830 // m_pressedIndex will have no value if we came from a multi-selection clearing onPress
1831 // in that case, we don't select anything
1832 if (index.has_value() && m_pressedIndex.has_value()) {
1833 if (controlPressed && m_selectionBehavior == MultiSelection) {
1834 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1835 } else {
1836 m_selectionManager->setSelected(index.value());
1837 }
1838 if (!m_selectionManager->isAnchoredSelectionActive()) {
1839 m_selectionManager->beginAnchoredSelection(index.value());
1840 }
1841 }
1842 }
1843 }
1844
1845 if (index.has_value() && index == m_pressedIndex) {
1846 // The release event is done above the same item as the press event
1847
1848 if (m_clearSelectionIfItemsAreNotDragged) {
1849 // A selected item has been clicked, but no drag operation has been started
1850 // -> clear the rest of the selection.
1851 m_selectionManager->clearSelection();
1852 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select);
1853 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1854 }
1855
1856 if (buttons & Qt::LeftButton) {
1857 bool emitItemActivated = true;
1858 if (m_view->isAboveExpansionToggle(index.value(), pos)) {
1859 const bool expanded = m_model->isExpanded(index.value());
1860 m_model->setExpanded(index.value(), !expanded);
1861
1862 Q_EMIT itemExpansionToggleClicked(index.value());
1863 emitItemActivated = false;
1864 } else if (shiftOrControlPressed && m_selectionBehavior != SingleSelection) {
1865 // The mouse click should only update the selection, not trigger the item, except when
1866 // we are in single selection mode
1867 emitItemActivated = false;
1868 } else {
1869 const bool singleClickActivation = m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced;
1870 if (!singleClickActivation) {
1871 emitItemActivated = touch && !m_selectionMode;
1872 } else {
1873 // activate on single click only if we didn't come from a rubber band release
1874 emitItemActivated = !rubberBandRelease;
1875 }
1876 }
1877 if (emitItemActivated) {
1878 Q_EMIT itemActivated(index.value());
1879 }
1880 } else if (buttons & Qt::MiddleButton) {
1881 Q_EMIT itemMiddleClicked(index.value());
1882 }
1883 }
1884
1885 m_pressedMouseGlobalPos = QPointF();
1886 m_pressedIndex = std::nullopt;
1887 m_clearSelectionIfItemsAreNotDragged = false;
1888 return false;
1889 }
1890
1891 void KItemListController::startRubberBand()
1892 {
1893 if (m_selectionBehavior == MultiSelection) {
1894 QPoint startPos = m_view->transform().map(m_view->scene()->views().first()->mapFromGlobal(m_pressedMouseGlobalPos.toPoint()));
1895 if (m_view->scrollOrientation() == Qt::Vertical) {
1896 startPos.ry() += m_view->scrollOffset();
1897 } else {
1898 startPos.rx() += m_view->scrollOffset();
1899 }
1900
1901 m_oldSelection = m_selectionManager->selectedItems();
1902 KItemListRubberBand *rubberBand = m_view->rubberBand();
1903 rubberBand->setStartPosition(startPos);
1904 rubberBand->setEndPosition(startPos);
1905 rubberBand->setActive(true);
1906 connect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1907 m_view->setAutoScroll(true);
1908 }
1909 }
1910
1911 void KItemListController::slotStateChanged(QScroller::State newState)
1912 {
1913 if (newState == QScroller::Scrolling) {
1914 m_scrollerIsScrolling = true;
1915 } else if (newState == QScroller::Inactive) {
1916 m_scrollerIsScrolling = false;
1917 }
1918 }
1919
1920 #include "moc_kitemlistcontroller.cpp"