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