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