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