]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistcontroller.cpp
view: Add setting to trigger user set action with double click
[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 (!index.has_value()) {
704 Q_EMIT doubleClickViewBackground(event->button());
705 return false;
706 }
707
708 // Expand item if desired - See Bug 295573
709 if (m_mouseDoubleClickAction != ActivateItemOnly) {
710 if (m_view && m_model && m_view->supportsItemExpanding() && m_model->isExpandable(index.value_or(-1))) {
711 const bool expanded = m_model->isExpanded(index.value());
712 m_model->setExpanded(index.value(), !expanded);
713 }
714 }
715
716 if (event->button() & Qt::RightButton) {
717 return false;
718 }
719
720 if (m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) {
721 return false;
722 }
723
724 const bool emitItemActivated = index.has_value() && index.value() < m_model->count() && !m_view->isAboveExpansionToggle(index.value(), pos);
725 if (emitItemActivated) {
726 Q_EMIT itemActivated(index.value());
727 }
728 return false;
729 }
730
731 bool KItemListController::contextMenuEvent(QContextMenuEvent *event)
732 {
733 if (event->reason() == QContextMenuEvent::Keyboard) {
734 // Emit the signal itemContextMenuRequested() if at least one item is selected.
735 // Otherwise the signal viewContextMenuRequested() will be emitted.
736 const KItemSet selectedItems = m_selectionManager->selectedItems();
737 int index = -1;
738 if (selectedItems.count() >= 2) {
739 const int currentItemIndex = m_selectionManager->currentItem();
740 index = selectedItems.contains(currentItemIndex) ? currentItemIndex : selectedItems.first();
741 } else if (selectedItems.count() == 1) {
742 index = selectedItems.first();
743 }
744
745 if (index >= 0) {
746 const QRectF contextRect = m_view->itemContextRect(index);
747 const QPointF pos(m_view->scene()->views().first()->mapToGlobal(contextRect.bottomRight().toPoint()));
748 Q_EMIT itemContextMenuRequested(index, pos);
749 } else {
750 Q_EMIT viewContextMenuRequested(event->globalPos());
751 }
752 return true;
753 }
754
755 const auto pos = event->pos();
756 const auto globalPos = event->globalPos();
757
758 if (m_view->headerBoundaries().contains(pos)) {
759 Q_EMIT headerContextMenuRequested(globalPos);
760 return true;
761 }
762
763 const auto pressedItem = m_view->itemAt(pos);
764 // We only open a context menu for the pressed item if it is selected.
765 // 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.
766 if (pressedItem && m_selectionManager->selectedItems().contains(pressedItem.value())) {
767 // The selection rectangle for an item was clicked
768 Q_EMIT itemContextMenuRequested(pressedItem.value(), globalPos);
769 return true;
770 }
771
772 // Remove any hover highlights so the context menu doesn't look like it applies to a row.
773 const auto widgets = m_view->visibleItemListWidgets();
774 for (KItemListWidget *widget : widgets) {
775 if (widget->isHovered()) {
776 widget->setHovered(false);
777 Q_EMIT itemUnhovered(widget->index());
778 }
779 }
780 Q_EMIT viewContextMenuRequested(globalPos);
781 return true;
782 }
783
784 bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform)
785 {
786 Q_UNUSED(event)
787 Q_UNUSED(transform)
788
789 DragAndDropHelper::clearUrlListMatchesUrlCache();
790
791 return false;
792 }
793
794 bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform)
795 {
796 Q_UNUSED(event)
797 Q_UNUSED(transform)
798
799 m_autoActivationTimer->stop();
800 m_view->setAutoScroll(false);
801 m_view->hideDropIndicator();
802
803 KItemListWidget *widget = hoveredWidget();
804 if (widget) {
805 widget->setHovered(false);
806 Q_EMIT itemUnhovered(widget->index());
807 }
808 return false;
809 }
810
811 bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform)
812 {
813 if (!m_model || !m_view) {
814 return false;
815 }
816
817 QUrl hoveredDir = m_model->directory();
818 KItemListWidget *oldHoveredWidget = hoveredWidget();
819
820 const QPointF pos = transform.map(event->pos());
821 KItemListWidget *newHoveredWidget = widgetForDropPos(pos);
822 int index = -1;
823
824 if (oldHoveredWidget != newHoveredWidget) {
825 m_autoActivationTimer->stop();
826
827 if (oldHoveredWidget) {
828 oldHoveredWidget->setHovered(false);
829 Q_EMIT itemUnhovered(oldHoveredWidget->index());
830 }
831 }
832
833 if (newHoveredWidget) {
834 bool droppingBetweenItems = false;
835 if (m_model->sortRole().isEmpty()) {
836 // The model supports inserting items between other items.
837 droppingBetweenItems = (m_view->showDropIndicator(pos) >= 0);
838 }
839
840 index = newHoveredWidget->index();
841
842 if (m_model->isDir(index)) {
843 hoveredDir = m_model->url(index);
844 }
845
846 if (!droppingBetweenItems) {
847 // Something has been dragged on an item.
848 m_view->hideDropIndicator();
849 if (!newHoveredWidget->isHovered()) {
850 newHoveredWidget->setHovered(true);
851 Q_EMIT itemHovered(index);
852 }
853
854 if (!m_autoActivationTimer->isActive() && m_autoActivationTimer->interval() >= 0 && m_model->canEnterOnHover(index)) {
855 m_autoActivationTimer->setProperty("index", index);
856 m_autoActivationTimer->start();
857 newHoveredWidget->startActivateSoonAnimation(m_autoActivationTimer->remainingTime());
858 }
859
860 } else {
861 m_autoActivationTimer->stop();
862 if (newHoveredWidget && newHoveredWidget->isHovered()) {
863 newHoveredWidget->setHovered(false);
864 Q_EMIT itemUnhovered(index);
865 }
866 }
867 } else {
868 m_view->hideDropIndicator();
869 }
870
871 if (DragAndDropHelper::urlListMatchesUrl(event->mimeData()->urls(), hoveredDir)) {
872 event->setDropAction(Qt::IgnoreAction);
873 event->ignore();
874 } else {
875 if (m_model->supportsDropping(index)) {
876 event->setDropAction(event->proposedAction());
877 event->accept();
878 } else {
879 event->setDropAction(Qt::IgnoreAction);
880 event->ignore();
881 }
882 }
883 return false;
884 }
885
886 bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform)
887 {
888 if (!m_view) {
889 return false;
890 }
891
892 m_autoActivationTimer->stop();
893 m_view->setAutoScroll(false);
894
895 const QPointF pos = transform.map(event->pos());
896
897 int dropAboveIndex = -1;
898 if (m_model->sortRole().isEmpty()) {
899 // The model supports inserting of items between other items.
900 dropAboveIndex = m_view->showDropIndicator(pos);
901 }
902
903 if (dropAboveIndex >= 0) {
904 // Something has been dropped between two items.
905 m_view->hideDropIndicator();
906 Q_EMIT aboveItemDropEvent(dropAboveIndex, event);
907 } else if (!event->mimeData()->hasFormat(m_model->blacklistItemDropEventMimeType())) {
908 // Something has been dropped on an item or on an empty part of the view.
909 const KItemListWidget *receivingWidget = widgetForDropPos(pos);
910 if (receivingWidget) {
911 Q_EMIT itemDropEvent(receivingWidget->index(), event);
912 } else {
913 Q_EMIT itemDropEvent(-1, event);
914 }
915 }
916
917 QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropEnd);
918 QAccessible::updateAccessibility(&accessibilityEvent);
919
920 return true;
921 }
922
923 bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent *event, const QTransform &transform)
924 {
925 Q_UNUSED(event)
926 Q_UNUSED(transform)
927 return false;
928 }
929
930 bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent *event, const QTransform &transform)
931 {
932 Q_UNUSED(transform)
933 if (!m_model || !m_view) {
934 return false;
935 }
936
937 // We identify the widget whose expansionArea had been hovered before this hoverMoveEvent() triggered.
938 // we can't use hoveredWidget() here (it handles the icon+text rect, not the expansion rect)
939 // like hoveredWidget(), we find the hovered widget for the expansion rect
940 const auto visibleItemListWidgets = m_view->visibleItemListWidgets();
941 const auto oldHoveredExpansionWidgetIterator = std::find_if(visibleItemListWidgets.begin(), visibleItemListWidgets.end(), [](auto &widget) {
942 return widget->expansionAreaHovered();
943 });
944 const auto oldHoveredExpansionWidget =
945 oldHoveredExpansionWidgetIterator == visibleItemListWidgets.end() ? std::nullopt : std::make_optional(*oldHoveredExpansionWidgetIterator);
946
947 const auto unhoverOldHoveredWidget = [&]() {
948 if (auto oldHoveredWidget = hoveredWidget(); oldHoveredWidget) {
949 // handle the text+icon one
950 oldHoveredWidget->setHovered(false);
951 Q_EMIT itemUnhovered(oldHoveredWidget->index());
952 }
953 };
954
955 const auto unhoverOldExpansionWidget = [&]() {
956 if (oldHoveredExpansionWidget) {
957 // then the expansion toggle
958 (*oldHoveredExpansionWidget)->setExpansionAreaHovered(false);
959 }
960 };
961
962 const QPointF pos = transform.map(event->pos());
963 if (KItemListWidget *newHoveredWidget = widgetForPos(pos); newHoveredWidget) {
964 // something got hovered, work out which part and set hover for the appropriate widget
965 const auto mappedPos = newHoveredWidget->mapFromItem(m_view, pos);
966 const bool isOnExpansionToggle = newHoveredWidget->expansionToggleRect().contains(mappedPos);
967
968 if (isOnExpansionToggle) {
969 // make sure we unhover the old one first if old!=new
970 if (oldHoveredExpansionWidget && *oldHoveredExpansionWidget != newHoveredWidget) {
971 (*oldHoveredExpansionWidget)->setExpansionAreaHovered(false);
972 }
973 // 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)
974 unhoverOldHoveredWidget();
975
976 newHoveredWidget->setExpansionAreaHovered(true);
977 } else {
978 // make sure we unhover the old one first if old!=new
979 auto oldHoveredWidget = hoveredWidget();
980 if (oldHoveredWidget && oldHoveredWidget != newHoveredWidget) {
981 oldHoveredWidget->setHovered(false);
982 Q_EMIT itemUnhovered(oldHoveredWidget->index());
983 }
984 // 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)
985 unhoverOldExpansionWidget();
986
987 const bool isOverIconAndText = newHoveredWidget->iconRect().contains(mappedPos) || newHoveredWidget->textRect().contains(mappedPos);
988 const bool hasMultipleSelection = m_selectionManager->selectedItems().count() > 1;
989
990 if (hasMultipleSelection && !isOverIconAndText) {
991 // In case we have multiple selections, clicking on any row will deselect the selection.
992 // So, as a visual cue for signalling that clicking anywhere won't select, but clear current highlights,
993 // we disable hover of the *row*(i.e. blank space to the right of the icon+text)
994
995 // (no-op in this branch for masked hover)
996 } else {
997 newHoveredWidget->setHoverPosition(mappedPos);
998 if (oldHoveredWidget != newHoveredWidget) {
999 newHoveredWidget->setHovered(true);
1000 Q_EMIT itemHovered(newHoveredWidget->index());
1001 }
1002 }
1003 }
1004 } else {
1005 // unhover any currently hovered expansion and text+icon widgets
1006 unhoverOldHoveredWidget();
1007 unhoverOldExpansionWidget();
1008 }
1009 return false;
1010 }
1011
1012 bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent *event, const QTransform &transform)
1013 {
1014 Q_UNUSED(event)
1015 Q_UNUSED(transform)
1016
1017 m_mousePress = false;
1018 m_isTouchEvent = false;
1019
1020 if (!m_model || !m_view) {
1021 return false;
1022 }
1023
1024 const auto widgets = m_view->visibleItemListWidgets();
1025 for (KItemListWidget *widget : widgets) {
1026 if (widget->isHovered()) {
1027 widget->setHovered(false);
1028 Q_EMIT itemUnhovered(widget->index());
1029 }
1030 }
1031 return false;
1032 }
1033
1034 bool KItemListController::wheelEvent(QGraphicsSceneWheelEvent *event, const QTransform &transform)
1035 {
1036 Q_UNUSED(event)
1037 Q_UNUSED(transform)
1038 return false;
1039 }
1040
1041 bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent *event, const QTransform &transform)
1042 {
1043 Q_UNUSED(event)
1044 Q_UNUSED(transform)
1045 return false;
1046 }
1047
1048 bool KItemListController::gestureEvent(QGestureEvent *event, const QTransform &transform)
1049 {
1050 if (!m_view) {
1051 return false;
1052 }
1053
1054 //you can touch on different views at the same time, but only one QWidget gets a mousePressEvent
1055 //we use this to get the right QWidget
1056 //the only exception is a tap gesture with state GestureStarted, we need to reset some variable
1057 if (!m_mousePress) {
1058 if (QGesture *tap = event->gesture(Qt::TapGesture)) {
1059 QTapGesture *tapGesture = static_cast<QTapGesture *>(tap);
1060 if (tapGesture->state() == Qt::GestureStarted) {
1061 tapTriggered(tapGesture, transform);
1062 }
1063 }
1064 return false;
1065 }
1066
1067 bool accepted = false;
1068
1069 if (QGesture *tap = event->gesture(Qt::TapGesture)) {
1070 tapTriggered(static_cast<QTapGesture *>(tap), transform);
1071 accepted = true;
1072 }
1073 if (event->gesture(Qt::TapAndHoldGesture)) {
1074 tapAndHoldTriggered(event, transform);
1075 accepted = true;
1076 }
1077 if (event->gesture(Qt::PinchGesture)) {
1078 pinchTriggered(event, transform);
1079 accepted = true;
1080 }
1081 if (event->gesture(m_swipeGesture)) {
1082 swipeTriggered(event, transform);
1083 accepted = true;
1084 }
1085 if (event->gesture(m_twoFingerTapGesture)) {
1086 twoFingerTapTriggered(event, transform);
1087 accepted = true;
1088 }
1089 return accepted;
1090 }
1091
1092 bool KItemListController::touchBeginEvent(QTouchEvent *event, const QTransform &transform)
1093 {
1094 Q_UNUSED(event)
1095 Q_UNUSED(transform)
1096
1097 m_isTouchEvent = true;
1098 return false;
1099 }
1100
1101 void KItemListController::tapTriggered(QTapGesture *tap, const QTransform &transform)
1102 {
1103 static bool scrollerWasActive = false;
1104
1105 if (tap->state() == Qt::GestureStarted) {
1106 m_dragActionOrRightClick = false;
1107 m_isSwipeGesture = false;
1108 m_pinchGestureInProgress = false;
1109 scrollerWasActive = m_scrollerIsScrolling;
1110 }
1111
1112 if (tap->state() == Qt::GestureFinished) {
1113 m_mousePress = false;
1114
1115 //if at the moment of the gesture start the QScroller was active, the user made the tap
1116 //to stop the QScroller and not to tap on an item
1117 if (scrollerWasActive) {
1118 return;
1119 }
1120
1121 if (m_view->m_tapAndHoldIndicator->isActive()) {
1122 m_view->m_tapAndHoldIndicator->setActive(false);
1123 }
1124
1125 const QPointF pressedMousePos = transform.map(tap->position());
1126 m_pressedIndex = m_view->itemAt(pressedMousePos);
1127 if (m_dragActionOrRightClick) {
1128 m_dragActionOrRightClick = false;
1129 } else {
1130 onPress(tap->position().toPoint(), Qt::NoModifier, Qt::LeftButton);
1131 onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::LeftButton, true);
1132 }
1133 m_isTouchEvent = false;
1134 }
1135 }
1136
1137 void KItemListController::tapAndHoldTriggered(QGestureEvent *event, const QTransform &transform)
1138 {
1139 //the Qt TabAndHold gesture is triggerable with a mouse click, we don't want this
1140 if (!m_isTouchEvent) {
1141 return;
1142 }
1143
1144 const QTapAndHoldGesture *tap = static_cast<QTapAndHoldGesture *>(event->gesture(Qt::TapAndHoldGesture));
1145 if (tap->state() == Qt::GestureFinished) {
1146 //if a pinch gesture is in progress we don't want a TabAndHold gesture
1147 if (m_pinchGestureInProgress) {
1148 return;
1149 }
1150 const QPointF pressedMousePos = transform.map(event->mapToGraphicsScene(tap->position()));
1151 m_pressedIndex = m_view->itemAt(pressedMousePos);
1152 if (m_pressedIndex.has_value()) {
1153 if (!m_selectionManager->isSelected(m_pressedIndex.value())) {
1154 m_selectionManager->clearSelection();
1155 m_selectionManager->setSelected(m_pressedIndex.value());
1156 }
1157 if (!m_selectionMode) {
1158 Q_EMIT selectionModeChangeRequested(true);
1159 }
1160 } else {
1161 m_selectionManager->clearSelection();
1162 startRubberBand();
1163 }
1164
1165 Q_EMIT scrollerStop();
1166
1167 m_view->m_tapAndHoldIndicator->setStartPosition(pressedMousePos);
1168 m_view->m_tapAndHoldIndicator->setActive(true);
1169
1170 m_dragActionOrRightClick = true;
1171 }
1172 }
1173
1174 void KItemListController::pinchTriggered(QGestureEvent *event, const QTransform &transform)
1175 {
1176 Q_UNUSED(transform)
1177
1178 const QPinchGesture *pinch = static_cast<QPinchGesture *>(event->gesture(Qt::PinchGesture));
1179 const qreal sensitivityModifier = 0.2;
1180 static qreal counter = 0;
1181
1182 if (pinch->state() == Qt::GestureStarted) {
1183 m_pinchGestureInProgress = true;
1184 counter = 0;
1185 }
1186 if (pinch->state() == Qt::GestureUpdated) {
1187 //if a swipe gesture was recognized or in progress, we don't want a pinch gesture to change the zoom
1188 if (m_isSwipeGesture) {
1189 return;
1190 }
1191 counter = counter + (pinch->scaleFactor() - 1);
1192 if (counter >= sensitivityModifier) {
1193 Q_EMIT increaseZoom();
1194 counter = 0;
1195 } else if (counter <= -sensitivityModifier) {
1196 Q_EMIT decreaseZoom();
1197 counter = 0;
1198 }
1199 }
1200 }
1201
1202 void KItemListController::swipeTriggered(QGestureEvent *event, const QTransform &transform)
1203 {
1204 Q_UNUSED(transform)
1205
1206 const KTwoFingerSwipe *swipe = static_cast<KTwoFingerSwipe *>(event->gesture(m_swipeGesture));
1207
1208 if (!swipe) {
1209 return;
1210 }
1211 if (swipe->state() == Qt::GestureStarted) {
1212 m_isSwipeGesture = true;
1213 }
1214
1215 if (swipe->state() == Qt::GestureCanceled) {
1216 m_isSwipeGesture = false;
1217 }
1218
1219 if (swipe->state() == Qt::GestureFinished) {
1220 Q_EMIT scrollerStop();
1221
1222 if (swipe->swipeAngle() <= 20 || swipe->swipeAngle() >= 340) {
1223 Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), Qt::BackButton);
1224 } else if (swipe->swipeAngle() <= 200 && swipe->swipeAngle() >= 160) {
1225 Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), Qt::ForwardButton);
1226 } else if (swipe->swipeAngle() <= 110 && swipe->swipeAngle() >= 60) {
1227 Q_EMIT swipeUp();
1228 }
1229 m_isSwipeGesture = true;
1230 }
1231 }
1232
1233 void KItemListController::twoFingerTapTriggered(QGestureEvent *event, const QTransform &transform)
1234 {
1235 const KTwoFingerTap *twoTap = static_cast<KTwoFingerTap *>(event->gesture(m_twoFingerTapGesture));
1236
1237 if (!twoTap) {
1238 return;
1239 }
1240
1241 if (twoTap->state() == Qt::GestureStarted) {
1242 const QPointF pressedMousePos = transform.map(twoTap->pos());
1243 m_pressedIndex = m_view->itemAt(pressedMousePos);
1244 if (m_pressedIndex.has_value()) {
1245 onPress(twoTap->pos().toPoint(), Qt::ControlModifier, Qt::LeftButton);
1246 onRelease(transform.map(twoTap->pos()), Qt::ControlModifier, Qt::LeftButton, false);
1247 }
1248 }
1249 }
1250
1251 bool KItemListController::processEvent(QEvent *event, const QTransform &transform)
1252 {
1253 if (!event) {
1254 return false;
1255 }
1256
1257 switch (event->type()) {
1258 case QEvent::KeyPress:
1259 return keyPressEvent(static_cast<QKeyEvent *>(event));
1260 case QEvent::InputMethod:
1261 return inputMethodEvent(static_cast<QInputMethodEvent *>(event));
1262 case QEvent::GraphicsSceneMousePress:
1263 return mousePressEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1264 case QEvent::GraphicsSceneMouseMove:
1265 return mouseMoveEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1266 case QEvent::GraphicsSceneMouseRelease:
1267 return mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1268 case QEvent::GraphicsSceneMouseDoubleClick:
1269 return mouseDoubleClickEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1270 case QEvent::ContextMenu:
1271 return contextMenuEvent(static_cast<QContextMenuEvent *>(event));
1272 case QEvent::GraphicsSceneWheel:
1273 return wheelEvent(static_cast<QGraphicsSceneWheelEvent *>(event), QTransform());
1274 case QEvent::GraphicsSceneDragEnter:
1275 return dragEnterEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1276 case QEvent::GraphicsSceneDragLeave:
1277 return dragLeaveEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1278 case QEvent::GraphicsSceneDragMove:
1279 return dragMoveEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1280 case QEvent::GraphicsSceneDrop:
1281 return dropEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1282 case QEvent::GraphicsSceneHoverEnter:
1283 return hoverEnterEvent(static_cast<QGraphicsSceneHoverEvent *>(event), QTransform());
1284 case QEvent::GraphicsSceneHoverMove:
1285 return hoverMoveEvent(static_cast<QGraphicsSceneHoverEvent *>(event), QTransform());
1286 case QEvent::GraphicsSceneHoverLeave:
1287 return hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent *>(event), QTransform());
1288 case QEvent::GraphicsSceneResize:
1289 return resizeEvent(static_cast<QGraphicsSceneResizeEvent *>(event), transform);
1290 case QEvent::Gesture:
1291 return gestureEvent(static_cast<QGestureEvent *>(event), transform);
1292 case QEvent::TouchBegin:
1293 return touchBeginEvent(static_cast<QTouchEvent *>(event), transform);
1294 default:
1295 break;
1296 }
1297
1298 return false;
1299 }
1300
1301 void KItemListController::slotViewScrollOffsetChanged(qreal current, qreal previous)
1302 {
1303 if (!m_view) {
1304 return;
1305 }
1306
1307 KItemListRubberBand *rubberBand = m_view->rubberBand();
1308 if (rubberBand->isActive()) {
1309 const qreal diff = current - previous;
1310 // TODO: Ideally just QCursor::pos() should be used as
1311 // new end-position but it seems there is no easy way
1312 // to have something like QWidget::mapFromGlobal() for QGraphicsWidget
1313 // (... or I just missed an easy way to do the mapping)
1314 QPointF endPos = rubberBand->endPosition();
1315 if (m_view->scrollOrientation() == Qt::Vertical) {
1316 endPos.ry() += diff;
1317 } else {
1318 endPos.rx() += diff;
1319 }
1320
1321 rubberBand->setEndPosition(endPos);
1322 }
1323 }
1324
1325 void KItemListController::slotRubberBandChanged()
1326 {
1327 if (!m_view || !m_model || m_model->count() <= 0) {
1328 return;
1329 }
1330
1331 const KItemListRubberBand *rubberBand = m_view->rubberBand();
1332 const QPointF startPos = rubberBand->startPosition();
1333 const QPointF endPos = rubberBand->endPosition();
1334 QRectF rubberBandRect = QRectF(startPos, endPos).normalized();
1335
1336 const bool scrollVertical = (m_view->scrollOrientation() == Qt::Vertical);
1337 if (scrollVertical) {
1338 rubberBandRect.translate(0, -m_view->scrollOffset());
1339 } else {
1340 rubberBandRect.translate(-m_view->scrollOffset(), 0);
1341 }
1342
1343 if (!m_oldSelection.isEmpty()) {
1344 // Clear the old selection that was available before the rubberband has
1345 // been activated in case if no Shift- or Control-key are pressed
1346 const bool shiftOrControlPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier || QApplication::keyboardModifiers() & Qt::ControlModifier;
1347 if (!shiftOrControlPressed && !m_selectionMode) {
1348 m_oldSelection.clear();
1349 }
1350 }
1351
1352 KItemSet selectedItems;
1353
1354 // Select all visible items that intersect with the rubberband
1355 const auto widgets = m_view->visibleItemListWidgets();
1356 for (const KItemListWidget *widget : widgets) {
1357 const int index = widget->index();
1358
1359 const QRectF widgetRect = m_view->itemRect(index);
1360 if (widgetRect.intersects(rubberBandRect)) {
1361 // Select the full row intersecting with the rubberband rectangle
1362 const QRectF selectionRect = widget->selectionRect().translated(widgetRect.topLeft());
1363 const QRectF iconRect = widget->iconRect().translated(widgetRect.topLeft());
1364 if (selectionRect.intersects(rubberBandRect) || iconRect.intersects(rubberBandRect)) {
1365 selectedItems.insert(index);
1366 }
1367 }
1368 }
1369
1370 // Select all invisible items that intersect with the rubberband. Instead of
1371 // iterating all items only the area which might be touched by the rubberband
1372 // will be checked.
1373 const bool increaseIndex = scrollVertical ? startPos.y() > endPos.y() : startPos.x() > endPos.x();
1374
1375 int index = increaseIndex ? m_view->lastVisibleIndex() + 1 : m_view->firstVisibleIndex() - 1;
1376 bool selectionFinished = false;
1377 do {
1378 const QRectF widgetRect = m_view->itemRect(index);
1379 if (widgetRect.intersects(rubberBandRect)) {
1380 selectedItems.insert(index);
1381 }
1382
1383 if (increaseIndex) {
1384 ++index;
1385 selectionFinished = (index >= m_model->count()) || (scrollVertical && widgetRect.top() > rubberBandRect.bottom())
1386 || (!scrollVertical && widgetRect.left() > rubberBandRect.right());
1387 } else {
1388 --index;
1389 selectionFinished = (index < 0) || (scrollVertical && widgetRect.bottom() < rubberBandRect.top())
1390 || (!scrollVertical && widgetRect.right() < rubberBandRect.left());
1391 }
1392 } while (!selectionFinished);
1393
1394 if ((QApplication::keyboardModifiers() & Qt::ControlModifier) || m_selectionMode) {
1395 // If Control is pressed, the selection state of all items in the rubberband is toggled.
1396 // Therefore, the new selection contains:
1397 // 1. All previously selected items which are not inside the rubberband, and
1398 // 2. all items inside the rubberband which have not been selected previously.
1399 m_selectionManager->setSelectedItems(m_oldSelection ^ selectedItems);
1400 } else {
1401 m_selectionManager->setSelectedItems(selectedItems + m_oldSelection);
1402 }
1403 }
1404
1405 void KItemListController::startDragging()
1406 {
1407 if (!m_view || !m_model) {
1408 return;
1409 }
1410
1411 const KItemSet selectedItems = m_selectionManager->selectedItems();
1412 if (selectedItems.isEmpty()) {
1413 return;
1414 }
1415
1416 QMimeData *data = m_model->createMimeData(selectedItems);
1417 if (!data) {
1418 return;
1419 }
1420 KUrlMimeData::exportUrlsToPortal(data);
1421
1422 // The created drag object will be owned and deleted
1423 // by QApplication::activeWindow().
1424 QDrag *drag = new QDrag(QApplication::activeWindow());
1425 drag->setMimeData(data);
1426
1427 const QPixmap pixmap = m_view->createDragPixmap(selectedItems);
1428 drag->setPixmap(pixmap);
1429
1430 const QPoint hotSpot((pixmap.width() / pixmap.devicePixelRatio()) / 2, 0);
1431 drag->setHotSpot(hotSpot);
1432
1433 drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction);
1434
1435 QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropStart);
1436 QAccessible::updateAccessibility(&accessibilityEvent);
1437 }
1438
1439 KItemListWidget *KItemListController::hoveredWidget() const
1440 {
1441 Q_ASSERT(m_view);
1442
1443 const auto widgets = m_view->visibleItemListWidgets();
1444 for (KItemListWidget *widget : widgets) {
1445 if (widget->isHovered()) {
1446 return widget;
1447 }
1448 }
1449
1450 return nullptr;
1451 }
1452
1453 KItemListWidget *KItemListController::widgetForPos(const QPointF &pos) const
1454 {
1455 Q_ASSERT(m_view);
1456
1457 if (m_view->headerBoundaries().contains(pos)) {
1458 return nullptr;
1459 }
1460
1461 const auto widgets = m_view->visibleItemListWidgets();
1462 for (KItemListWidget *widget : widgets) {
1463 const QPointF mappedPos = widget->mapFromItem(m_view, pos);
1464 if (widget->contains(mappedPos) || widget->selectionRect().contains(mappedPos)) {
1465 return widget;
1466 }
1467 }
1468
1469 return nullptr;
1470 }
1471
1472 KItemListWidget *KItemListController::widgetForDropPos(const QPointF &pos) const
1473 {
1474 Q_ASSERT(m_view);
1475
1476 if (m_view->headerBoundaries().contains(pos)) {
1477 return nullptr;
1478 }
1479
1480 const auto widgets = m_view->visibleItemListWidgets();
1481 for (KItemListWidget *widget : widgets) {
1482 const QPointF mappedPos = widget->mapFromItem(m_view, pos);
1483 if (widget->contains(mappedPos)) {
1484 return widget;
1485 }
1486 }
1487
1488 return nullptr;
1489 }
1490
1491 void KItemListController::updateKeyboardAnchor()
1492 {
1493 const bool validAnchor =
1494 m_keyboardAnchorIndex >= 0 && m_keyboardAnchorIndex < m_model->count() && keyboardAnchorPos(m_keyboardAnchorIndex) == m_keyboardAnchorPos;
1495 if (!validAnchor) {
1496 const int index = m_selectionManager->currentItem();
1497 m_keyboardAnchorIndex = index;
1498 m_keyboardAnchorPos = keyboardAnchorPos(index);
1499 }
1500 }
1501
1502 int KItemListController::nextRowIndex(int index) const
1503 {
1504 if (m_keyboardAnchorIndex < 0) {
1505 return index;
1506 }
1507
1508 const int maxIndex = m_model->count() - 1;
1509 if (index == maxIndex) {
1510 return index;
1511 }
1512
1513 const bool reversed = m_view->layoutDirection() == Qt::RightToLeft && m_view->scrollOrientation() == Qt::Vertical;
1514
1515 // Calculate the index of the last column inside the row of the current index
1516 int lastColumnIndex = index;
1517 while ((!reversed && keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex))
1518 || (reversed && keyboardAnchorPos(lastColumnIndex + 1) < keyboardAnchorPos(lastColumnIndex))) {
1519 ++lastColumnIndex;
1520 if (lastColumnIndex >= maxIndex) {
1521 return index;
1522 }
1523 }
1524
1525 // Based on the last column index go to the next row and calculate the nearest index
1526 // that is below the current index
1527 int nextRowIndex = lastColumnIndex + 1;
1528 int searchIndex = nextRowIndex;
1529 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(nextRowIndex));
1530 while (searchIndex < maxIndex
1531 && ((!reversed && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex))
1532 || (reversed && keyboardAnchorPos(searchIndex + 1) < keyboardAnchorPos(searchIndex)))) {
1533 ++searchIndex;
1534 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1535 if (searchDiff < minDiff) {
1536 minDiff = searchDiff;
1537 nextRowIndex = searchIndex;
1538 }
1539 }
1540
1541 return nextRowIndex;
1542 }
1543
1544 int KItemListController::previousRowIndex(int index) const
1545 {
1546 if (m_keyboardAnchorIndex < 0 || index == 0) {
1547 return index;
1548 }
1549
1550 const bool reversed = m_view->layoutDirection() == Qt::RightToLeft && m_view->scrollOrientation() == Qt::Vertical;
1551
1552 // Calculate the index of the first column inside the row of the current index
1553 int firstColumnIndex = index;
1554 while ((!reversed && keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex))
1555 || (reversed && keyboardAnchorPos(firstColumnIndex - 1) > keyboardAnchorPos(firstColumnIndex))) {
1556 --firstColumnIndex;
1557 if (firstColumnIndex <= 0) {
1558 return index;
1559 }
1560 }
1561
1562 // Based on the first column index go to the previous row and calculate the nearest index
1563 // that is above the current index
1564 int previousRowIndex = firstColumnIndex - 1;
1565 int searchIndex = previousRowIndex;
1566 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(previousRowIndex));
1567 while (searchIndex > 0
1568 && ((!reversed && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex))
1569 || (reversed && keyboardAnchorPos(searchIndex - 1) > keyboardAnchorPos(searchIndex)))) {
1570 --searchIndex;
1571 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1572 if (searchDiff < minDiff) {
1573 minDiff = searchDiff;
1574 previousRowIndex = searchIndex;
1575 }
1576 }
1577
1578 return previousRowIndex;
1579 }
1580
1581 qreal KItemListController::keyboardAnchorPos(int index) const
1582 {
1583 const QRectF itemRect = m_view->itemRect(index);
1584 if (!itemRect.isEmpty()) {
1585 return (m_view->scrollOrientation() == Qt::Vertical) ? itemRect.x() : itemRect.y();
1586 }
1587
1588 return 0;
1589 }
1590
1591 void KItemListController::updateExtendedSelectionRegion()
1592 {
1593 if (m_view) {
1594 const bool extend = (m_selectionBehavior != MultiSelection);
1595 KItemListStyleOption option = m_view->styleOption();
1596 if (option.extendedSelectionRegion != extend) {
1597 option.extendedSelectionRegion = extend;
1598 m_view->setStyleOption(option);
1599 }
1600 }
1601 }
1602
1603 bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons)
1604 {
1605 Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), buttons);
1606
1607 if (buttons & (Qt::BackButton | Qt::ForwardButton)) {
1608 // Do not select items when clicking the back/forward buttons, see
1609 // https://bugs.kde.org/show_bug.cgi?id=327412.
1610 return true;
1611 }
1612
1613 const QPointF pressedMousePos = m_view->transform().map(pos);
1614
1615 if (m_view->isAboveExpansionToggle(m_pressedIndex.value_or(-1), pressedMousePos)) {
1616 m_selectionManager->endAnchoredSelection();
1617 m_selectionManager->setCurrentItem(m_pressedIndex.value());
1618 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1619 return true;
1620 }
1621
1622 m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), pressedMousePos);
1623 if (m_selectionTogglePressed) {
1624 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1625 // The previous anchored selection has been finished already in
1626 // KItemListSelectionManager::setSelected(). We can safely change
1627 // the current item and start a new anchored selection now.
1628 m_selectionManager->setCurrentItem(m_pressedIndex.value());
1629 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1630 return true;
1631 }
1632
1633 const bool shiftPressed = modifiers & Qt::ShiftModifier;
1634 const bool controlPressed = (modifiers & Qt::ControlModifier) || m_selectionMode; // Keeping selectionMode similar to pressing control will hopefully
1635 // simplify the overall logic and possibilities both for users and devs.
1636 const bool leftClick = buttons & Qt::LeftButton;
1637 const bool rightClick = buttons & Qt::RightButton;
1638
1639 // The previous selection is cleared if either
1640 // 1. The selection mode is SingleSelection, or
1641 // 2. the selection mode is MultiSelection, and *none* of the following conditions are met:
1642 // a) Shift or Control are pressed.
1643 // b) The clicked item is selected already. In that case, the user might want to:
1644 // - start dragging multiple items, or
1645 // - open the context menu and perform an action for all selected items.
1646 const bool shiftOrControlPressed = shiftPressed || controlPressed;
1647 const bool pressedItemAlreadySelected = m_pressedIndex.has_value() && m_selectionManager->isSelected(m_pressedIndex.value());
1648 const bool clearSelection = m_selectionBehavior == SingleSelection || (!shiftOrControlPressed && !pressedItemAlreadySelected);
1649
1650 // When this method returns false, a rubberBand selection is created using KItemListController::startRubberBand via the caller.
1651 if (clearSelection) {
1652 const int selectedItemsCount = m_selectionManager->selectedItems().count();
1653 m_selectionManager->clearSelection();
1654 // clear and bail when we got an existing multi-selection
1655 if (selectedItemsCount > 1 && m_pressedIndex.has_value()) {
1656 const auto row = m_view->m_visibleItems.value(m_pressedIndex.value());
1657 const auto mappedPos = row->mapFromItem(m_view, pos);
1658 if (pressedItemAlreadySelected || row->iconRect().contains(mappedPos) || row->textRect().contains(mappedPos)) {
1659 // we are indeed inside the text/icon rect, keep m_pressedIndex what it is
1660 // and short-circuit for single-click activation (it will then propagate to onRelease and activate the item)
1661 // or we just keep going for double-click activation
1662 if (m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) {
1663 if (!pressedItemAlreadySelected) {
1664 // An unselected item was clicked directly while deselecting multiple other items so we select it.
1665 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1666 m_selectionManager->setCurrentItem(m_pressedIndex.value());
1667 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1668 }
1669 return true; // event handled, don't create rubber band
1670 }
1671 } else {
1672 // we're not inside the text/icon rect, as we've already cleared the selection
1673 // we can just stop here and make sure handlers down the line (i.e. onRelease) don't activate
1674 m_pressedIndex.reset();
1675 // we don't stop event propagation and proceed to create a rubber band and let onRelease
1676 // decide (based on m_pressedIndex) whether we're in a drag (drag => new rubber band, click => don't select the item)
1677 return false;
1678 }
1679 }
1680 } else if (pressedItemAlreadySelected && !shiftOrControlPressed && leftClick) {
1681 // The user might want to start dragging multiple items, but if he clicks the item
1682 // in order to trigger it instead, the other selected items must be deselected.
1683 // However, we do not know yet what the user is going to do.
1684 // -> remember that the user pressed an item which had been selected already and
1685 // clear the selection in mouseReleaseEvent(), unless the items are dragged.
1686 m_clearSelectionIfItemsAreNotDragged = true;
1687
1688 if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex.value_or(-1), pressedMousePos)) {
1689 Q_EMIT selectedItemTextPressed(m_pressedIndex.value_or(-1));
1690 }
1691 }
1692
1693 if (!shiftPressed) {
1694 // Finish the anchored selection before the current index is changed
1695 m_selectionManager->endAnchoredSelection();
1696 }
1697
1698 if (rightClick) {
1699 // Stop rubber band from persisting after right-clicks
1700 KItemListRubberBand *rubberBand = m_view->rubberBand();
1701 if (rubberBand->isActive()) {
1702 disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1703 rubberBand->setActive(false);
1704 m_view->setAutoScroll(false);
1705 }
1706
1707 if (!m_pressedIndex.has_value()) {
1708 // We have a right-click in an empty region, don't create rubber band.
1709 return true;
1710 }
1711 }
1712
1713 if (m_pressedIndex.has_value()) {
1714 // The hover highlight area of an item is being pressed.
1715 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
1716 const bool hitTargetIsRowEmptyRegion = !row->contains(row->mapFromItem(m_view, pos));
1717 // again, when this method returns false, a rubberBand selection is created as the event is not consumed;
1718 // createRubberBand here tells us whether to return true or false.
1719 bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty());
1720
1721 if (rightClick && hitTargetIsRowEmptyRegion) {
1722 // We have a right click outside the icon and text rect but within the hover highlight area.
1723 // We don't want items to get selected through this, so we return now.
1724 return true;
1725 }
1726
1727 m_selectionManager->setCurrentItem(m_pressedIndex.value());
1728
1729 switch (m_selectionBehavior) {
1730 case NoSelection:
1731 break;
1732
1733 case SingleSelection:
1734 m_selectionManager->setSelected(m_pressedIndex.value());
1735 break;
1736
1737 case MultiSelection:
1738 if (controlPressed && !shiftPressed && leftClick) {
1739 // A left mouse button press is happening on an item while control is pressed. This either means a user wants to:
1740 // - toggle the selection of item(s) or
1741 // - they want to begin a drag on the item(s) to copy them.
1742 // We rule out the latter, if the item is not clicked directly and was unselected previously.
1743 const auto row = m_view->m_visibleItems.value(m_pressedIndex.value());
1744 const auto mappedPos = row->mapFromItem(m_view, pos);
1745 if (!row->iconRect().contains(mappedPos) && !row->textRect().contains(mappedPos) && !pressedItemAlreadySelected) {
1746 createRubberBand = true;
1747 } else {
1748 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1749 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1750 createRubberBand = false; // multi selection, don't propagate any further
1751 // This will be the start of an item drag-to-copy operation if the user now moves the mouse before releasing the mouse button.
1752 }
1753 } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
1754 // Select the pressed item and start a new anchored selection
1755 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select);
1756 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1757 }
1758 break;
1759
1760 default:
1761 Q_ASSERT(false);
1762 break;
1763 }
1764
1765 return !createRubberBand;
1766 }
1767
1768 return false;
1769 }
1770
1771 bool KItemListController::onRelease(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch)
1772 {
1773 const QPointF pressedMousePos = pos;
1774 const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), pressedMousePos);
1775 if (isAboveSelectionToggle) {
1776 m_selectionTogglePressed = false;
1777 return true;
1778 }
1779
1780 if (!isAboveSelectionToggle && m_selectionTogglePressed) {
1781 m_selectionManager->setSelected(m_pressedIndex.value_or(-1), 1, KItemListSelectionManager::Toggle);
1782 m_selectionTogglePressed = false;
1783 return true;
1784 }
1785
1786 const bool controlPressed = modifiers & Qt::ControlModifier;
1787 const bool shiftOrControlPressed = modifiers & Qt::ShiftModifier || controlPressed;
1788
1789 const std::optional<int> index = m_view->itemAt(pos);
1790
1791 KItemListRubberBand *rubberBand = m_view->rubberBand();
1792 bool rubberBandRelease = false;
1793 if (rubberBand->isActive()) {
1794 disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1795 rubberBand->setActive(false);
1796 m_oldSelection.clear();
1797 m_view->setAutoScroll(false);
1798 rubberBandRelease = true;
1799 // We check for actual rubber band drag here: if delta between start and end is less than drag threshold,
1800 // then we have a single click on one of the rows
1801 if ((rubberBand->endPosition() - rubberBand->startPosition()).manhattanLength() < QApplication::startDragDistance()) {
1802 rubberBandRelease = false; // since we're only selecting, unmark rubber band release flag
1803 // m_pressedIndex will have no value if we came from a multi-selection clearing onPress
1804 // in that case, we don't select anything
1805 if (index.has_value() && m_pressedIndex.has_value()) {
1806 if (controlPressed && m_selectionBehavior == MultiSelection) {
1807 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1808 } else {
1809 m_selectionManager->setSelected(index.value());
1810 }
1811 if (!m_selectionManager->isAnchoredSelectionActive()) {
1812 m_selectionManager->beginAnchoredSelection(index.value());
1813 }
1814 }
1815 }
1816 }
1817
1818 if (index.has_value() && index == m_pressedIndex) {
1819 // The release event is done above the same item as the press event
1820
1821 if (m_clearSelectionIfItemsAreNotDragged) {
1822 // A selected item has been clicked, but no drag operation has been started
1823 // -> clear the rest of the selection.
1824 m_selectionManager->clearSelection();
1825 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select);
1826 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1827 }
1828
1829 if (buttons & Qt::LeftButton) {
1830 bool emitItemActivated = true;
1831 if (m_view->isAboveExpansionToggle(index.value(), pos)) {
1832 const bool expanded = m_model->isExpanded(index.value());
1833 m_model->setExpanded(index.value(), !expanded);
1834
1835 Q_EMIT itemExpansionToggleClicked(index.value());
1836 emitItemActivated = false;
1837 } else if (shiftOrControlPressed && m_selectionBehavior != SingleSelection) {
1838 // The mouse click should only update the selection, not trigger the item, except when
1839 // we are in single selection mode
1840 emitItemActivated = false;
1841 } else {
1842 const bool singleClickActivation = m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced;
1843 if (!singleClickActivation) {
1844 emitItemActivated = touch && !m_selectionMode;
1845 } else {
1846 // activate on single click only if we didn't come from a rubber band release
1847 emitItemActivated = !rubberBandRelease;
1848 }
1849 }
1850 if (emitItemActivated) {
1851 Q_EMIT itemActivated(index.value());
1852 }
1853 } else if (buttons & Qt::MiddleButton) {
1854 Q_EMIT itemMiddleClicked(index.value());
1855 }
1856 }
1857
1858 m_pressedMouseGlobalPos = QPointF();
1859 m_pressedIndex = std::nullopt;
1860 m_clearSelectionIfItemsAreNotDragged = false;
1861 return false;
1862 }
1863
1864 void KItemListController::startRubberBand()
1865 {
1866 if (m_selectionBehavior == MultiSelection) {
1867 QPoint startPos = m_view->transform().map(m_view->scene()->views().first()->mapFromGlobal(m_pressedMouseGlobalPos.toPoint()));
1868 if (m_view->scrollOrientation() == Qt::Vertical) {
1869 startPos.ry() += m_view->scrollOffset();
1870 } else {
1871 startPos.rx() += m_view->scrollOffset();
1872 }
1873
1874 m_oldSelection = m_selectionManager->selectedItems();
1875 KItemListRubberBand *rubberBand = m_view->rubberBand();
1876 rubberBand->setStartPosition(startPos);
1877 rubberBand->setEndPosition(startPos);
1878 rubberBand->setActive(true);
1879 connect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1880 m_view->setAutoScroll(true);
1881 }
1882 }
1883
1884 void KItemListController::slotStateChanged(QScroller::State newState)
1885 {
1886 if (newState == QScroller::Scrolling) {
1887 m_scrollerIsScrolling = true;
1888 } else if (newState == QScroller::Inactive) {
1889 m_scrollerIsScrolling = false;
1890 }
1891 }
1892
1893 #include "moc_kitemlistcontroller.cpp"