]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistcontroller.cpp
Avoid unwanted drag and drop action
[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() && !m_selectionManager->isSelected(m_pressedIndex.value())) {
1099 m_selectionManager->clearSelection();
1100 m_selectionManager->setSelected(m_pressedIndex.value());
1101 if (!m_selectionMode) {
1102 Q_EMIT selectionModeChangeRequested(true);
1103 }
1104 } else if (!m_pressedIndex.has_value()) {
1105 m_selectionManager->clearSelection();
1106 startRubberBand();
1107 }
1108
1109 Q_EMIT scrollerStop();
1110
1111 m_view->m_tapAndHoldIndicator->setStartPosition(pressedMousePos);
1112 m_view->m_tapAndHoldIndicator->setActive(true);
1113
1114 m_dragActionOrRightClick = true;
1115 }
1116 }
1117
1118 void KItemListController::pinchTriggered(QGestureEvent *event, const QTransform &transform)
1119 {
1120 Q_UNUSED(transform)
1121
1122 const QPinchGesture *pinch = static_cast<QPinchGesture *>(event->gesture(Qt::PinchGesture));
1123 const qreal sensitivityModifier = 0.2;
1124 static qreal counter = 0;
1125
1126 if (pinch->state() == Qt::GestureStarted) {
1127 m_pinchGestureInProgress = true;
1128 counter = 0;
1129 }
1130 if (pinch->state() == Qt::GestureUpdated) {
1131 //if a swipe gesture was recognized or in progress, we don't want a pinch gesture to change the zoom
1132 if (m_isSwipeGesture) {
1133 return;
1134 }
1135 counter = counter + (pinch->scaleFactor() - 1);
1136 if (counter >= sensitivityModifier) {
1137 Q_EMIT increaseZoom();
1138 counter = 0;
1139 } else if (counter <= -sensitivityModifier) {
1140 Q_EMIT decreaseZoom();
1141 counter = 0;
1142 }
1143 }
1144 }
1145
1146 void KItemListController::swipeTriggered(QGestureEvent *event, const QTransform &transform)
1147 {
1148 Q_UNUSED(transform)
1149
1150 const KTwoFingerSwipe *swipe = static_cast<KTwoFingerSwipe *>(event->gesture(m_swipeGesture));
1151
1152 if (!swipe) {
1153 return;
1154 }
1155 if (swipe->state() == Qt::GestureStarted) {
1156 m_isSwipeGesture = true;
1157 }
1158
1159 if (swipe->state() == Qt::GestureCanceled) {
1160 m_isSwipeGesture = false;
1161 }
1162
1163 if (swipe->state() == Qt::GestureFinished) {
1164 Q_EMIT scrollerStop();
1165
1166 if (swipe->swipeAngle() <= 20 || swipe->swipeAngle() >= 340) {
1167 Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), Qt::BackButton);
1168 } else if (swipe->swipeAngle() <= 200 && swipe->swipeAngle() >= 160) {
1169 Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), Qt::ForwardButton);
1170 } else if (swipe->swipeAngle() <= 110 && swipe->swipeAngle() >= 60) {
1171 Q_EMIT swipeUp();
1172 }
1173 m_isSwipeGesture = true;
1174 }
1175 }
1176
1177 void KItemListController::twoFingerTapTriggered(QGestureEvent *event, const QTransform &transform)
1178 {
1179 const KTwoFingerTap *twoTap = static_cast<KTwoFingerTap *>(event->gesture(m_twoFingerTapGesture));
1180
1181 if (!twoTap) {
1182 return;
1183 }
1184
1185 if (twoTap->state() == Qt::GestureStarted) {
1186 const QPointF pressedMousePos = transform.map(twoTap->pos());
1187 m_pressedIndex = m_view->itemAt(pressedMousePos);
1188 if (m_pressedIndex.has_value()) {
1189 onPress(twoTap->screenPos().toPoint(), twoTap->pos().toPoint(), Qt::ControlModifier, Qt::LeftButton);
1190 onRelease(transform.map(twoTap->pos()), Qt::ControlModifier, Qt::LeftButton, false);
1191 }
1192 }
1193 }
1194
1195 bool KItemListController::processEvent(QEvent *event, const QTransform &transform)
1196 {
1197 if (!event) {
1198 return false;
1199 }
1200
1201 switch (event->type()) {
1202 case QEvent::KeyPress:
1203 return keyPressEvent(static_cast<QKeyEvent *>(event));
1204 case QEvent::InputMethod:
1205 return inputMethodEvent(static_cast<QInputMethodEvent *>(event));
1206 case QEvent::GraphicsSceneMousePress:
1207 return mousePressEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1208 case QEvent::GraphicsSceneMouseMove:
1209 return mouseMoveEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1210 case QEvent::GraphicsSceneMouseRelease:
1211 return mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1212 case QEvent::GraphicsSceneMouseDoubleClick:
1213 return mouseDoubleClickEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1214 case QEvent::GraphicsSceneWheel:
1215 return wheelEvent(static_cast<QGraphicsSceneWheelEvent *>(event), QTransform());
1216 case QEvent::GraphicsSceneDragEnter:
1217 return dragEnterEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1218 case QEvent::GraphicsSceneDragLeave:
1219 return dragLeaveEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1220 case QEvent::GraphicsSceneDragMove:
1221 return dragMoveEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1222 case QEvent::GraphicsSceneDrop:
1223 return dropEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1224 case QEvent::GraphicsSceneHoverEnter:
1225 return hoverEnterEvent(static_cast<QGraphicsSceneHoverEvent *>(event), QTransform());
1226 case QEvent::GraphicsSceneHoverMove:
1227 return hoverMoveEvent(static_cast<QGraphicsSceneHoverEvent *>(event), QTransform());
1228 case QEvent::GraphicsSceneHoverLeave:
1229 return hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent *>(event), QTransform());
1230 case QEvent::GraphicsSceneResize:
1231 return resizeEvent(static_cast<QGraphicsSceneResizeEvent *>(event), transform);
1232 case QEvent::Gesture:
1233 return gestureEvent(static_cast<QGestureEvent *>(event), transform);
1234 case QEvent::TouchBegin:
1235 return touchBeginEvent(static_cast<QTouchEvent *>(event), transform);
1236 default:
1237 break;
1238 }
1239
1240 return false;
1241 }
1242
1243 void KItemListController::slotViewScrollOffsetChanged(qreal current, qreal previous)
1244 {
1245 if (!m_view) {
1246 return;
1247 }
1248
1249 KItemListRubberBand *rubberBand = m_view->rubberBand();
1250 if (rubberBand->isActive()) {
1251 const qreal diff = current - previous;
1252 // TODO: Ideally just QCursor::pos() should be used as
1253 // new end-position but it seems there is no easy way
1254 // to have something like QWidget::mapFromGlobal() for QGraphicsWidget
1255 // (... or I just missed an easy way to do the mapping)
1256 QPointF endPos = rubberBand->endPosition();
1257 if (m_view->scrollOrientation() == Qt::Vertical) {
1258 endPos.ry() += diff;
1259 } else {
1260 endPos.rx() += diff;
1261 }
1262
1263 rubberBand->setEndPosition(endPos);
1264 }
1265 }
1266
1267 void KItemListController::slotRubberBandChanged()
1268 {
1269 if (!m_view || !m_model || m_model->count() <= 0) {
1270 return;
1271 }
1272
1273 const KItemListRubberBand *rubberBand = m_view->rubberBand();
1274 const QPointF startPos = rubberBand->startPosition();
1275 const QPointF endPos = rubberBand->endPosition();
1276 QRectF rubberBandRect = QRectF(startPos, endPos).normalized();
1277
1278 const bool scrollVertical = (m_view->scrollOrientation() == Qt::Vertical);
1279 if (scrollVertical) {
1280 rubberBandRect.translate(0, -m_view->scrollOffset());
1281 } else {
1282 rubberBandRect.translate(-m_view->scrollOffset(), 0);
1283 }
1284
1285 if (!m_oldSelection.isEmpty()) {
1286 // Clear the old selection that was available before the rubberband has
1287 // been activated in case if no Shift- or Control-key are pressed
1288 const bool shiftOrControlPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier || QApplication::keyboardModifiers() & Qt::ControlModifier;
1289 if (!shiftOrControlPressed && !m_selectionMode) {
1290 m_oldSelection.clear();
1291 }
1292 }
1293
1294 KItemSet selectedItems;
1295
1296 // Select all visible items that intersect with the rubberband
1297 const auto widgets = m_view->visibleItemListWidgets();
1298 for (const KItemListWidget *widget : widgets) {
1299 const int index = widget->index();
1300
1301 const QRectF widgetRect = m_view->itemRect(index);
1302 if (widgetRect.intersects(rubberBandRect)) {
1303 const QRectF iconRect = widget->iconRect().translated(widgetRect.topLeft());
1304 const QRectF textRect = widget->textRect().translated(widgetRect.topLeft());
1305 if (iconRect.intersects(rubberBandRect) || textRect.intersects(rubberBandRect)) {
1306 selectedItems.insert(index);
1307 }
1308 }
1309 }
1310
1311 // Select all invisible items that intersect with the rubberband. Instead of
1312 // iterating all items only the area which might be touched by the rubberband
1313 // will be checked.
1314 const bool increaseIndex = scrollVertical ? startPos.y() > endPos.y() : startPos.x() > endPos.x();
1315
1316 int index = increaseIndex ? m_view->lastVisibleIndex() + 1 : m_view->firstVisibleIndex() - 1;
1317 bool selectionFinished = false;
1318 do {
1319 const QRectF widgetRect = m_view->itemRect(index);
1320 if (widgetRect.intersects(rubberBandRect)) {
1321 selectedItems.insert(index);
1322 }
1323
1324 if (increaseIndex) {
1325 ++index;
1326 selectionFinished = (index >= m_model->count()) || (scrollVertical && widgetRect.top() > rubberBandRect.bottom())
1327 || (!scrollVertical && widgetRect.left() > rubberBandRect.right());
1328 } else {
1329 --index;
1330 selectionFinished = (index < 0) || (scrollVertical && widgetRect.bottom() < rubberBandRect.top())
1331 || (!scrollVertical && widgetRect.right() < rubberBandRect.left());
1332 }
1333 } while (!selectionFinished);
1334
1335 if ((QApplication::keyboardModifiers() & Qt::ControlModifier) || m_selectionMode) {
1336 // If Control is pressed, the selection state of all items in the rubberband is toggled.
1337 // Therefore, the new selection contains:
1338 // 1. All previously selected items which are not inside the rubberband, and
1339 // 2. all items inside the rubberband which have not been selected previously.
1340 m_selectionManager->setSelectedItems(m_oldSelection ^ selectedItems);
1341 } else {
1342 m_selectionManager->setSelectedItems(selectedItems + m_oldSelection);
1343 }
1344 }
1345
1346 void KItemListController::startDragging()
1347 {
1348 if (!m_view || !m_model) {
1349 return;
1350 }
1351
1352 const KItemSet selectedItems = m_selectionManager->selectedItems();
1353 if (selectedItems.isEmpty()) {
1354 return;
1355 }
1356
1357 QMimeData *data = m_model->createMimeData(selectedItems);
1358 if (!data) {
1359 return;
1360 }
1361 KUrlMimeData::exportUrlsToPortal(data);
1362
1363 // The created drag object will be owned and deleted
1364 // by QApplication::activeWindow().
1365 QDrag *drag = new QDrag(QApplication::activeWindow());
1366 drag->setMimeData(data);
1367
1368 const QPixmap pixmap = m_view->createDragPixmap(selectedItems);
1369 drag->setPixmap(pixmap);
1370
1371 const QPoint hotSpot((pixmap.width() / pixmap.devicePixelRatio()) / 2, 0);
1372 drag->setHotSpot(hotSpot);
1373
1374 drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction);
1375
1376 QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropStart);
1377 QAccessible::updateAccessibility(&accessibilityEvent);
1378 }
1379
1380 KItemListWidget *KItemListController::hoveredWidget() const
1381 {
1382 Q_ASSERT(m_view);
1383
1384 const auto widgets = m_view->visibleItemListWidgets();
1385 for (KItemListWidget *widget : widgets) {
1386 if (widget->isHovered()) {
1387 return widget;
1388 }
1389 }
1390
1391 return nullptr;
1392 }
1393
1394 KItemListWidget *KItemListController::widgetForPos(const QPointF &pos) const
1395 {
1396 Q_ASSERT(m_view);
1397
1398 const auto widgets = m_view->visibleItemListWidgets();
1399 for (KItemListWidget *widget : widgets) {
1400 const QPointF mappedPos = widget->mapFromItem(m_view, pos);
1401 if (widget->contains(mappedPos) || widget->selectionRect().contains(mappedPos)) {
1402 return widget;
1403 }
1404 }
1405
1406 return nullptr;
1407 }
1408
1409 KItemListWidget *KItemListController::widgetForDropPos(const QPointF &pos) const
1410 {
1411 Q_ASSERT(m_view);
1412
1413 const auto widgets = m_view->visibleItemListWidgets();
1414 for (KItemListWidget *widget : widgets) {
1415 const QPointF mappedPos = widget->mapFromItem(m_view, pos);
1416 if (widget->contains(mappedPos)) {
1417 return widget;
1418 }
1419 }
1420
1421 return nullptr;
1422 }
1423
1424 void KItemListController::updateKeyboardAnchor()
1425 {
1426 const bool validAnchor =
1427 m_keyboardAnchorIndex >= 0 && m_keyboardAnchorIndex < m_model->count() && keyboardAnchorPos(m_keyboardAnchorIndex) == m_keyboardAnchorPos;
1428 if (!validAnchor) {
1429 const int index = m_selectionManager->currentItem();
1430 m_keyboardAnchorIndex = index;
1431 m_keyboardAnchorPos = keyboardAnchorPos(index);
1432 }
1433 }
1434
1435 int KItemListController::nextRowIndex(int index) const
1436 {
1437 if (m_keyboardAnchorIndex < 0) {
1438 return index;
1439 }
1440
1441 const int maxIndex = m_model->count() - 1;
1442 if (index == maxIndex) {
1443 return index;
1444 }
1445
1446 // Calculate the index of the last column inside the row of the current index
1447 int lastColumnIndex = index;
1448 while (keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex)) {
1449 ++lastColumnIndex;
1450 if (lastColumnIndex >= maxIndex) {
1451 return index;
1452 }
1453 }
1454
1455 // Based on the last column index go to the next row and calculate the nearest index
1456 // that is below the current index
1457 int nextRowIndex = lastColumnIndex + 1;
1458 int searchIndex = nextRowIndex;
1459 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(nextRowIndex));
1460 while (searchIndex < maxIndex && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex)) {
1461 ++searchIndex;
1462 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1463 if (searchDiff < minDiff) {
1464 minDiff = searchDiff;
1465 nextRowIndex = searchIndex;
1466 }
1467 }
1468
1469 return nextRowIndex;
1470 }
1471
1472 int KItemListController::previousRowIndex(int index) const
1473 {
1474 if (m_keyboardAnchorIndex < 0 || index == 0) {
1475 return index;
1476 }
1477
1478 // Calculate the index of the first column inside the row of the current index
1479 int firstColumnIndex = index;
1480 while (keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex)) {
1481 --firstColumnIndex;
1482 if (firstColumnIndex <= 0) {
1483 return index;
1484 }
1485 }
1486
1487 // Based on the first column index go to the previous row and calculate the nearest index
1488 // that is above the current index
1489 int previousRowIndex = firstColumnIndex - 1;
1490 int searchIndex = previousRowIndex;
1491 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(previousRowIndex));
1492 while (searchIndex > 0 && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex)) {
1493 --searchIndex;
1494 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1495 if (searchDiff < minDiff) {
1496 minDiff = searchDiff;
1497 previousRowIndex = searchIndex;
1498 }
1499 }
1500
1501 return previousRowIndex;
1502 }
1503
1504 qreal KItemListController::keyboardAnchorPos(int index) const
1505 {
1506 const QRectF itemRect = m_view->itemRect(index);
1507 if (!itemRect.isEmpty()) {
1508 return (m_view->scrollOrientation() == Qt::Vertical) ? itemRect.x() : itemRect.y();
1509 }
1510
1511 return 0;
1512 }
1513
1514 void KItemListController::updateExtendedSelectionRegion()
1515 {
1516 if (m_view) {
1517 const bool extend = (m_selectionBehavior != MultiSelection);
1518 KItemListStyleOption option = m_view->styleOption();
1519 if (option.extendedSelectionRegion != extend) {
1520 option.extendedSelectionRegion = extend;
1521 m_view->setStyleOption(option);
1522 }
1523 }
1524 }
1525
1526 bool KItemListController::onPress(const QPoint &screenPos, const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons)
1527 {
1528 Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), buttons);
1529
1530 if (buttons & (Qt::BackButton | Qt::ForwardButton)) {
1531 // Do not select items when clicking the back/forward buttons, see
1532 // https://bugs.kde.org/show_bug.cgi?id=327412.
1533 return true;
1534 }
1535
1536 const QPointF pressedMousePos = m_view->transform().map(pos);
1537
1538 if (m_view->isAboveExpansionToggle(m_pressedIndex.value_or(-1), pressedMousePos)) {
1539 m_selectionManager->endAnchoredSelection();
1540 m_selectionManager->setCurrentItem(m_pressedIndex.value());
1541 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1542 return true;
1543 }
1544
1545 m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), pressedMousePos);
1546 if (m_selectionTogglePressed) {
1547 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1548 // The previous anchored selection has been finished already in
1549 // KItemListSelectionManager::setSelected(). We can safely change
1550 // the current item and start a new anchored selection now.
1551 m_selectionManager->setCurrentItem(m_pressedIndex.value());
1552 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1553 return true;
1554 }
1555
1556 const bool shiftPressed = modifiers & Qt::ShiftModifier;
1557 const bool controlPressed = (modifiers & Qt::ControlModifier) || m_selectionMode; // Keeping selectionMode similar to pressing control will hopefully
1558 // simplify the overall logic and possibilities both for users and devs.
1559 const bool leftClick = buttons & Qt::LeftButton;
1560 const bool rightClick = buttons & Qt::RightButton;
1561
1562 // The previous selection is cleared if either
1563 // 1. The selection mode is SingleSelection, or
1564 // 2. the selection mode is MultiSelection, and *none* of the following conditions are met:
1565 // a) Shift or Control are pressed.
1566 // b) The clicked item is selected already. In that case, the user might want to:
1567 // - start dragging multiple items, or
1568 // - open the context menu and perform an action for all selected items.
1569 const bool shiftOrControlPressed = shiftPressed || controlPressed;
1570 const bool pressedItemAlreadySelected = m_pressedIndex.has_value() && m_selectionManager->isSelected(m_pressedIndex.value());
1571 const bool clearSelection = m_selectionBehavior == SingleSelection || (!shiftOrControlPressed && !pressedItemAlreadySelected);
1572
1573 // When this method returns false, a rubberBand selection is created using KItemListController::startRubberBand via the caller.
1574 if (clearSelection) {
1575 const int selectedItemsCount = m_selectionManager->selectedItems().count();
1576 m_selectionManager->clearSelection();
1577 // clear and bail when we got an existing multi-selection
1578 if (selectedItemsCount > 1 && m_pressedIndex.has_value()) {
1579 const auto row = m_view->m_visibleItems.value(m_pressedIndex.value());
1580 const auto mappedPos = row->mapFromItem(m_view, pos);
1581 if (pressedItemAlreadySelected || row->iconRect().contains(mappedPos) || row->textRect().contains(mappedPos)) {
1582 // we are indeed inside the text/icon rect, keep m_pressedIndex what it is
1583 // and short-circuit for single-click activation (it will then propagate to onRelease and activate the item)
1584 // or we just keep going for double-click activation
1585 if (m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) {
1586 if (!pressedItemAlreadySelected) {
1587 // An unselected item was clicked directly while deselecting multiple other items so we select it.
1588 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1589 m_selectionManager->setCurrentItem(m_pressedIndex.value());
1590 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1591 }
1592 return true; // event handled, don't create rubber band
1593 }
1594 } else {
1595 // we're not inside the text/icon rect, as we've already cleared the selection
1596 // we can just stop here and make sure handlers down the line (i.e. onRelease) don't activate
1597 m_pressedIndex.reset();
1598 // we don't stop event propagation and proceed to create a rubber band and let onRelease
1599 // decide (based on m_pressedIndex) whether we're in a drag (drag => new rubber band, click => don't select the item)
1600 return false;
1601 }
1602 }
1603 } else if (pressedItemAlreadySelected && !shiftOrControlPressed && leftClick) {
1604 // The user might want to start dragging multiple items, but if he clicks the item
1605 // in order to trigger it instead, the other selected items must be deselected.
1606 // However, we do not know yet what the user is going to do.
1607 // -> remember that the user pressed an item which had been selected already and
1608 // clear the selection in mouseReleaseEvent(), unless the items are dragged.
1609 m_clearSelectionIfItemsAreNotDragged = true;
1610
1611 if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex.value_or(-1), pressedMousePos)) {
1612 Q_EMIT selectedItemTextPressed(m_pressedIndex.value_or(-1));
1613 }
1614 }
1615
1616 if (!shiftPressed) {
1617 // Finish the anchored selection before the current index is changed
1618 m_selectionManager->endAnchoredSelection();
1619 }
1620
1621 if (rightClick) {
1622 // Do header hit check and short circuit before commencing any state changing effects
1623 if (m_view->headerBoundaries().contains(pos)) {
1624 Q_EMIT headerContextMenuRequested(screenPos);
1625 return true;
1626 }
1627
1628 // Stop rubber band from persisting after right-clicks
1629 KItemListRubberBand *rubberBand = m_view->rubberBand();
1630 if (rubberBand->isActive()) {
1631 disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1632 rubberBand->setActive(false);
1633 m_view->setAutoScroll(false);
1634 }
1635 }
1636
1637 if (m_pressedIndex.has_value()) {
1638 // The hover highlight area of an item is being pressed.
1639 m_selectionManager->setCurrentItem(m_pressedIndex.value());
1640 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
1641 const bool hitTargetIsRowEmptyRegion = !row->contains(row->mapFromItem(m_view, pos));
1642 // again, when this method returns false, a rubberBand selection is created as the event is not consumed;
1643 // createRubberBand here tells us whether to return true or false.
1644 bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty());
1645
1646 if (rightClick && hitTargetIsRowEmptyRegion) {
1647 // We have a right click outside the icon and text rect but within the hover highlight area
1648 // but it is unclear if this means that a selection rectangle for an item was clicked or the background of the view.
1649 if (m_selectionManager->selectedItems().contains(m_pressedIndex.value())) {
1650 // The selection rectangle for an item was clicked
1651 Q_EMIT itemContextMenuRequested(m_pressedIndex.value(), screenPos);
1652 } else {
1653 row->setHovered(false); // Removes the hover highlight so the context menu doesn't look like it applies to the row.
1654 Q_EMIT viewContextMenuRequested(screenPos);
1655 }
1656 return true;
1657 }
1658
1659 switch (m_selectionBehavior) {
1660 case NoSelection:
1661 break;
1662
1663 case SingleSelection:
1664 m_selectionManager->setSelected(m_pressedIndex.value());
1665 break;
1666
1667 case MultiSelection:
1668 if (controlPressed && !shiftPressed && leftClick) {
1669 // A left mouse button press is happening on an item while control is pressed. This either means a user wants to:
1670 // - toggle the selection of item(s) or
1671 // - they want to begin a drag on the item(s) to copy them.
1672 // We rule out the latter, if the item is not clicked directly and was unselected previously.
1673 const auto row = m_view->m_visibleItems.value(m_pressedIndex.value());
1674 const auto mappedPos = row->mapFromItem(m_view, pos);
1675 if (!row->iconRect().contains(mappedPos) && !row->textRect().contains(mappedPos) && !pressedItemAlreadySelected) {
1676 createRubberBand = true;
1677 } else {
1678 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1679 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1680 createRubberBand = false; // multi selection, don't propagate any further
1681 // This will be the start of an item drag-to-copy operation if the user now moves the mouse before releasing the mouse button.
1682 }
1683 } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
1684 // Select the pressed item and start a new anchored selection
1685 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select);
1686 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1687 }
1688 break;
1689
1690 default:
1691 Q_ASSERT(false);
1692 break;
1693 }
1694
1695 if (rightClick) {
1696 Q_EMIT itemContextMenuRequested(m_pressedIndex.value(), screenPos);
1697 }
1698 return !createRubberBand;
1699 }
1700
1701 if (rightClick) {
1702 // header right click handling would have been done before this so just normal context
1703 // menu here is fine
1704 Q_EMIT viewContextMenuRequested(screenPos);
1705 return true;
1706 }
1707
1708 return false;
1709 }
1710
1711 bool KItemListController::onRelease(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch)
1712 {
1713 const QPointF pressedMousePos = pos;
1714 const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), pressedMousePos);
1715 if (isAboveSelectionToggle) {
1716 m_selectionTogglePressed = false;
1717 return true;
1718 }
1719
1720 if (!isAboveSelectionToggle && m_selectionTogglePressed) {
1721 m_selectionManager->setSelected(m_pressedIndex.value_or(-1), 1, KItemListSelectionManager::Toggle);
1722 m_selectionTogglePressed = false;
1723 return true;
1724 }
1725
1726 const bool controlPressed = modifiers & Qt::ControlModifier;
1727 const bool shiftOrControlPressed = modifiers & Qt::ShiftModifier || controlPressed;
1728
1729 const std::optional<int> index = m_view->itemAt(pos);
1730
1731 KItemListRubberBand *rubberBand = m_view->rubberBand();
1732 bool rubberBandRelease = false;
1733 if (rubberBand->isActive()) {
1734 disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1735 rubberBand->setActive(false);
1736 m_oldSelection.clear();
1737 m_view->setAutoScroll(false);
1738 rubberBandRelease = true;
1739 // We check for actual rubber band drag here: if delta between start and end is less than drag threshold,
1740 // then we have a single click on one of the rows
1741 if ((rubberBand->endPosition() - rubberBand->startPosition()).manhattanLength() < QApplication::startDragDistance()) {
1742 rubberBandRelease = false; // since we're only selecting, unmark rubber band release flag
1743 // m_pressedIndex will have no value if we came from a multi-selection clearing onPress
1744 // in that case, we don't select anything
1745 if (index.has_value() && m_pressedIndex.has_value()) {
1746 if (controlPressed && m_selectionBehavior == MultiSelection) {
1747 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1748 } else {
1749 m_selectionManager->setSelected(index.value());
1750 }
1751 if (!m_selectionManager->isAnchoredSelectionActive()) {
1752 m_selectionManager->beginAnchoredSelection(index.value());
1753 }
1754 }
1755 }
1756 }
1757
1758 if (index.has_value() && index == m_pressedIndex) {
1759 // The release event is done above the same item as the press event
1760
1761 if (m_clearSelectionIfItemsAreNotDragged) {
1762 // A selected item has been clicked, but no drag operation has been started
1763 // -> clear the rest of the selection.
1764 m_selectionManager->clearSelection();
1765 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select);
1766 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1767 }
1768
1769 if (buttons & Qt::LeftButton) {
1770 bool emitItemActivated = true;
1771 if (m_view->isAboveExpansionToggle(index.value(), pos)) {
1772 const bool expanded = m_model->isExpanded(index.value());
1773 m_model->setExpanded(index.value(), !expanded);
1774
1775 Q_EMIT itemExpansionToggleClicked(index.value());
1776 emitItemActivated = false;
1777 } else if (shiftOrControlPressed && m_selectionBehavior != SingleSelection) {
1778 // The mouse click should only update the selection, not trigger the item, except when
1779 // we are in single selection mode
1780 emitItemActivated = false;
1781 } else {
1782 const bool singleClickActivation = m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced;
1783 if (!singleClickActivation) {
1784 emitItemActivated = touch && !m_selectionMode;
1785 } else {
1786 // activate on single click only if we didn't come from a rubber band release
1787 emitItemActivated = !rubberBandRelease;
1788 }
1789 }
1790 if (emitItemActivated) {
1791 Q_EMIT itemActivated(index.value());
1792 }
1793 } else if (buttons & Qt::MiddleButton) {
1794 Q_EMIT itemMiddleClicked(index.value());
1795 }
1796 }
1797
1798 m_pressedMouseGlobalPos = QPointF();
1799 m_pressedIndex = std::nullopt;
1800 m_clearSelectionIfItemsAreNotDragged = false;
1801 return false;
1802 }
1803
1804 void KItemListController::startRubberBand()
1805 {
1806 if (m_selectionBehavior == MultiSelection) {
1807 QPoint startPos = m_view->transform().map(m_view->scene()->views().first()->mapFromGlobal(m_pressedMouseGlobalPos.toPoint()));
1808 if (m_view->scrollOrientation() == Qt::Vertical) {
1809 startPos.ry() += m_view->scrollOffset();
1810 if (m_view->itemSize().width() < 0) {
1811 // Use a special rubberband for views that have only one column and
1812 // expand the rubberband to use the whole width of the view.
1813 startPos.setX(0);
1814 }
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"