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