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