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