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