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