File indexing completed on 2024-05-12 09:50:56

0001 /*
0002  * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
0003  * SPDX-FileCopyrightText: 2012 Frank Reininghaus <frank78ac@googlemail.com>
0004  *
0005  * Based on the Itemviews NG project from Trolltech Labs
0006  *
0007  * SPDX-License-Identifier: GPL-2.0-or-later
0008  */
0009 
0010 #include "kitemlistcontroller.h"
0011 
0012 #include "kitemlistselectionmanager.h"
0013 #include "kitemlistview.h"
0014 #include "private/kitemlistkeyboardsearchmanager.h"
0015 #include "private/kitemlistrubberband.h"
0016 #include "views/draganddrophelper.h"
0017 
0018 #include <KTwoFingerSwipe>
0019 #include <KTwoFingerTap>
0020 #include <KUrlMimeData>
0021 
0022 #include <QAccessible>
0023 #include <QApplication>
0024 #include <QDrag>
0025 #include <QGesture>
0026 #include <QGraphicsScene>
0027 #include <QGraphicsSceneEvent>
0028 #include <QGraphicsView>
0029 #include <QMimeData>
0030 #include <QTimer>
0031 #include <QTouchEvent>
0032 
0033 KItemListController::KItemListController(KItemModelBase *model, KItemListView *view, QObject *parent)
0034     : QObject(parent)
0035     , m_singleClickActivationEnforced(false)
0036     , m_selectionMode(false)
0037     , m_selectionTogglePressed(false)
0038     , m_clearSelectionIfItemsAreNotDragged(false)
0039     , m_isSwipeGesture(false)
0040     , m_dragActionOrRightClick(false)
0041     , m_scrollerIsScrolling(false)
0042     , m_pinchGestureInProgress(false)
0043     , m_mousePress(false)
0044     , m_isTouchEvent(false)
0045     , m_selectionBehavior(NoSelection)
0046     , m_autoActivationBehavior(ActivationAndExpansion)
0047     , m_mouseDoubleClickAction(ActivateItemOnly)
0048     , m_model(nullptr)
0049     , m_view(nullptr)
0050     , m_selectionManager(new KItemListSelectionManager(this))
0051     , m_keyboardManager(new KItemListKeyboardSearchManager(this))
0052     , m_pressedIndex(std::nullopt)
0053     , m_pressedMouseGlobalPos()
0054     , m_autoActivationTimer(nullptr)
0055     , m_swipeGesture(Qt::CustomGesture)
0056     , m_twoFingerTapGesture(Qt::CustomGesture)
0057     , m_oldSelection()
0058     , m_keyboardAnchorIndex(-1)
0059     , m_keyboardAnchorPos(0)
0060 {
0061     connect(m_keyboardManager, &KItemListKeyboardSearchManager::changeCurrentItem, this, &KItemListController::slotChangeCurrentItem);
0062     connect(m_selectionManager, &KItemListSelectionManager::currentChanged, m_keyboardManager, &KItemListKeyboardSearchManager::slotCurrentChanged);
0063     connect(m_selectionManager, &KItemListSelectionManager::selectionChanged, m_keyboardManager, &KItemListKeyboardSearchManager::slotSelectionChanged);
0064 
0065     m_autoActivationTimer = new QTimer(this);
0066     m_autoActivationTimer->setSingleShot(true);
0067     m_autoActivationTimer->setInterval(-1);
0068     connect(m_autoActivationTimer, &QTimer::timeout, this, &KItemListController::slotAutoActivationTimeout);
0069 
0070     setModel(model);
0071     setView(view);
0072 
0073     m_swipeGesture = QGestureRecognizer::registerRecognizer(new KTwoFingerSwipeRecognizer());
0074     m_twoFingerTapGesture = QGestureRecognizer::registerRecognizer(new KTwoFingerTapRecognizer());
0075     view->grabGesture(m_swipeGesture);
0076     view->grabGesture(m_twoFingerTapGesture);
0077     view->grabGesture(Qt::TapGesture);
0078     view->grabGesture(Qt::TapAndHoldGesture);
0079     view->grabGesture(Qt::PinchGesture);
0080 }
0081 
0082 KItemListController::~KItemListController()
0083 {
0084     setView(nullptr);
0085     Q_ASSERT(!m_view);
0086 
0087     setModel(nullptr);
0088     Q_ASSERT(!m_model);
0089 }
0090 
0091 void KItemListController::setModel(KItemModelBase *model)
0092 {
0093     if (m_model == model) {
0094         return;
0095     }
0096 
0097     KItemModelBase *oldModel = m_model;
0098     if (oldModel) {
0099         oldModel->deleteLater();
0100     }
0101 
0102     m_model = model;
0103     if (m_model) {
0104         m_model->setParent(this);
0105     }
0106 
0107     if (m_view) {
0108         m_view->setModel(m_model);
0109     }
0110 
0111     m_selectionManager->setModel(m_model);
0112 
0113     Q_EMIT modelChanged(m_model, oldModel);
0114 }
0115 
0116 KItemModelBase *KItemListController::model() const
0117 {
0118     return m_model;
0119 }
0120 
0121 KItemListSelectionManager *KItemListController::selectionManager() const
0122 {
0123     return m_selectionManager;
0124 }
0125 
0126 void KItemListController::setView(KItemListView *view)
0127 {
0128     if (m_view == view) {
0129         return;
0130     }
0131 
0132     KItemListView *oldView = m_view;
0133     if (oldView) {
0134         disconnect(oldView, &KItemListView::scrollOffsetChanged, this, &KItemListController::slotViewScrollOffsetChanged);
0135         oldView->deleteLater();
0136     }
0137 
0138     m_view = view;
0139 
0140     if (m_view) {
0141         m_view->setParent(this);
0142         m_view->setController(this);
0143         m_view->setModel(m_model);
0144         connect(m_view, &KItemListView::scrollOffsetChanged, this, &KItemListController::slotViewScrollOffsetChanged);
0145         updateExtendedSelectionRegion();
0146     }
0147 
0148     Q_EMIT viewChanged(m_view, oldView);
0149 }
0150 
0151 KItemListView *KItemListController::view() const
0152 {
0153     return m_view;
0154 }
0155 
0156 void KItemListController::setSelectionBehavior(SelectionBehavior behavior)
0157 {
0158     m_selectionBehavior = behavior;
0159     updateExtendedSelectionRegion();
0160 }
0161 
0162 KItemListController::SelectionBehavior KItemListController::selectionBehavior() const
0163 {
0164     return m_selectionBehavior;
0165 }
0166 
0167 void KItemListController::setAutoActivationBehavior(AutoActivationBehavior behavior)
0168 {
0169     m_autoActivationBehavior = behavior;
0170 }
0171 
0172 KItemListController::AutoActivationBehavior KItemListController::autoActivationBehavior() const
0173 {
0174     return m_autoActivationBehavior;
0175 }
0176 
0177 void KItemListController::setMouseDoubleClickAction(MouseDoubleClickAction action)
0178 {
0179     m_mouseDoubleClickAction = action;
0180 }
0181 
0182 KItemListController::MouseDoubleClickAction KItemListController::mouseDoubleClickAction() const
0183 {
0184     return m_mouseDoubleClickAction;
0185 }
0186 
0187 int KItemListController::indexCloseToMousePressedPosition() const
0188 {
0189     const QPointF pressedMousePos = m_view->transform().map(m_view->scene()->views().first()->mapFromGlobal(m_pressedMouseGlobalPos.toPoint()));
0190 
0191     QHashIterator<KItemListWidget *, KItemListGroupHeader *> it(m_view->m_visibleGroups);
0192     while (it.hasNext()) {
0193         it.next();
0194         KItemListGroupHeader *groupHeader = it.value();
0195         const QPointF mappedToGroup = groupHeader->mapFromItem(nullptr, pressedMousePos);
0196         if (groupHeader->contains(mappedToGroup)) {
0197             return it.key()->index();
0198         }
0199     }
0200     return -1;
0201 }
0202 
0203 void KItemListController::setAutoActivationDelay(int delay)
0204 {
0205     m_autoActivationTimer->setInterval(delay);
0206 }
0207 
0208 int KItemListController::autoActivationDelay() const
0209 {
0210     return m_autoActivationTimer->interval();
0211 }
0212 
0213 void KItemListController::setSingleClickActivationEnforced(bool singleClick)
0214 {
0215     m_singleClickActivationEnforced = singleClick;
0216 }
0217 
0218 bool KItemListController::singleClickActivationEnforced() const
0219 {
0220     return m_singleClickActivationEnforced;
0221 }
0222 
0223 void KItemListController::setSelectionModeEnabled(bool enabled)
0224 {
0225     m_selectionMode = enabled;
0226 }
0227 
0228 bool KItemListController::selectionMode() const
0229 {
0230     return m_selectionMode;
0231 }
0232 
0233 bool KItemListController::isSearchAsYouTypeActive() const
0234 {
0235     return m_keyboardManager->isSearchAsYouTypeActive();
0236 }
0237 
0238 bool KItemListController::keyPressEvent(QKeyEvent *event)
0239 {
0240     int index = m_selectionManager->currentItem();
0241     int key = event->key();
0242     const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
0243 
0244     // Handle the expanding/collapsing of items
0245     // expand / collapse all selected directories
0246     if (m_view->supportsItemExpanding() && m_model->isExpandable(index) && (key == Qt::Key_Right || key == Qt::Key_Left)) {
0247         const bool expandOrCollapse = key == Qt::Key_Right ? true : false;
0248         bool shouldReturn = m_model->setExpanded(index, expandOrCollapse);
0249 
0250         // edit in reverse to preserve index of the first handled items
0251         const auto selectedItems = m_selectionManager->selectedItems();
0252         for (auto it = selectedItems.rbegin(); it != selectedItems.rend(); ++it) {
0253             shouldReturn |= m_model->setExpanded(*it, expandOrCollapse);
0254             if (!shiftPressed) {
0255                 m_selectionManager->setSelected(*it);
0256             }
0257         }
0258         if (shouldReturn) {
0259             // update keyboard anchors
0260             if (shiftPressed) {
0261                 m_keyboardAnchorIndex = selectedItems.count() > 0 ? qMin(index, selectedItems.last()) : index;
0262                 m_keyboardAnchorPos = keyboardAnchorPos(m_keyboardAnchorIndex);
0263             }
0264 
0265             event->ignore();
0266             return true;
0267         }
0268     }
0269 
0270     const bool controlPressed = event->modifiers() & Qt::ControlModifier;
0271     const bool shiftOrControlPressed = shiftPressed || controlPressed;
0272     const bool navigationPressed = key == Qt::Key_Home || key == Qt::Key_End || key == Qt::Key_PageUp || key == Qt::Key_PageDown || key == Qt::Key_Up
0273         || key == Qt::Key_Down || key == Qt::Key_Left || key == Qt::Key_Right;
0274 
0275     const int itemCount = m_model->count();
0276 
0277     // For horizontal scroll orientation, transform
0278     // the arrow keys to simplify the event handling.
0279     if (m_view->scrollOrientation() == Qt::Horizontal) {
0280         switch (key) {
0281         case Qt::Key_Up:
0282             key = Qt::Key_Left;
0283             break;
0284         case Qt::Key_Down:
0285             key = Qt::Key_Right;
0286             break;
0287         case Qt::Key_Left:
0288             key = Qt::Key_Up;
0289             break;
0290         case Qt::Key_Right:
0291             key = Qt::Key_Down;
0292             break;
0293         default:
0294             break;
0295         }
0296     }
0297 
0298     const bool selectSingleItem = m_selectionBehavior != NoSelection && itemCount == 1 && navigationPressed;
0299 
0300     if (selectSingleItem) {
0301         const int current = m_selectionManager->currentItem();
0302         m_selectionManager->setSelected(current);
0303         return true;
0304     }
0305 
0306     switch (key) {
0307     case Qt::Key_Home:
0308         index = 0;
0309         m_keyboardAnchorIndex = index;
0310         m_keyboardAnchorPos = keyboardAnchorPos(index);
0311         break;
0312 
0313     case Qt::Key_End:
0314         index = itemCount - 1;
0315         m_keyboardAnchorIndex = index;
0316         m_keyboardAnchorPos = keyboardAnchorPos(index);
0317         break;
0318 
0319     case Qt::Key_Left:
0320         if (index > 0) {
0321             const int expandedParentsCount = m_model->expandedParentsCount(index);
0322             if (expandedParentsCount == 0) {
0323                 --index;
0324             } else {
0325                 // Go to the parent of the current item.
0326                 do {
0327                     --index;
0328                 } while (index > 0 && m_model->expandedParentsCount(index) == expandedParentsCount);
0329             }
0330             m_keyboardAnchorIndex = index;
0331             m_keyboardAnchorPos = keyboardAnchorPos(index);
0332         }
0333         break;
0334 
0335     case Qt::Key_Right:
0336         if (index < itemCount - 1) {
0337             ++index;
0338             m_keyboardAnchorIndex = index;
0339             m_keyboardAnchorPos = keyboardAnchorPos(index);
0340         }
0341         break;
0342 
0343     case Qt::Key_Up:
0344         updateKeyboardAnchor();
0345         if (shiftPressed && !m_selectionManager->isAnchoredSelectionActive() && m_selectionManager->isSelected(index)) {
0346             m_selectionManager->beginAnchoredSelection(index);
0347         }
0348         index = previousRowIndex(index);
0349         break;
0350 
0351     case Qt::Key_Down:
0352         updateKeyboardAnchor();
0353         if (shiftPressed && !m_selectionManager->isAnchoredSelectionActive() && m_selectionManager->isSelected(index)) {
0354             m_selectionManager->beginAnchoredSelection(index);
0355         }
0356         index = nextRowIndex(index);
0357         break;
0358 
0359     case Qt::Key_PageUp:
0360         if (m_view->scrollOrientation() == Qt::Horizontal) {
0361             // The new current index should correspond to the first item in the current column.
0362             int newIndex = qMax(index - 1, 0);
0363             while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() < m_view->itemRect(index).topLeft().y()) {
0364                 index = newIndex;
0365                 newIndex = qMax(index - 1, 0);
0366             }
0367             m_keyboardAnchorIndex = index;
0368             m_keyboardAnchorPos = keyboardAnchorPos(index);
0369         } else {
0370             const qreal currentItemBottom = m_view->itemRect(index).bottomLeft().y();
0371             const qreal height = m_view->geometry().height();
0372 
0373             // The new current item should be the first item in the current
0374             // column whose itemRect's top coordinate is larger than targetY.
0375             const qreal targetY = currentItemBottom - height;
0376 
0377             updateKeyboardAnchor();
0378             int newIndex = previousRowIndex(index);
0379             do {
0380                 index = newIndex;
0381                 updateKeyboardAnchor();
0382                 newIndex = previousRowIndex(index);
0383             } while (m_view->itemRect(newIndex).topLeft().y() > targetY && newIndex != index);
0384         }
0385         break;
0386 
0387     case Qt::Key_PageDown:
0388         if (m_view->scrollOrientation() == Qt::Horizontal) {
0389             // The new current index should correspond to the last item in the current column.
0390             int newIndex = qMin(index + 1, m_model->count() - 1);
0391             while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() > m_view->itemRect(index).topLeft().y()) {
0392                 index = newIndex;
0393                 newIndex = qMin(index + 1, m_model->count() - 1);
0394             }
0395             m_keyboardAnchorIndex = index;
0396             m_keyboardAnchorPos = keyboardAnchorPos(index);
0397         } else {
0398             const qreal currentItemTop = m_view->itemRect(index).topLeft().y();
0399             const qreal height = m_view->geometry().height();
0400 
0401             // The new current item should be the last item in the current
0402             // column whose itemRect's bottom coordinate is smaller than targetY.
0403             const qreal targetY = currentItemTop + height;
0404 
0405             updateKeyboardAnchor();
0406             int newIndex = nextRowIndex(index);
0407             do {
0408                 index = newIndex;
0409                 updateKeyboardAnchor();
0410                 newIndex = nextRowIndex(index);
0411             } while (m_view->itemRect(newIndex).bottomLeft().y() < targetY && newIndex != index);
0412         }
0413         break;
0414 
0415     case Qt::Key_Enter:
0416     case Qt::Key_Return: {
0417         const KItemSet selectedItems = m_selectionManager->selectedItems();
0418         if (selectedItems.count() >= 2) {
0419             Q_EMIT itemsActivated(selectedItems);
0420         } else if (selectedItems.count() == 1) {
0421             Q_EMIT itemActivated(selectedItems.first());
0422         } else {
0423             Q_EMIT itemActivated(index);
0424         }
0425         break;
0426     }
0427 
0428     case Qt::Key_Escape:
0429         if (m_selectionMode) {
0430             Q_EMIT selectionModeChangeRequested(false);
0431         } else if (m_selectionBehavior != SingleSelection) {
0432             m_selectionManager->clearSelection();
0433         }
0434         m_keyboardManager->cancelSearch();
0435         Q_EMIT escapePressed();
0436         break;
0437 
0438     case Qt::Key_Space:
0439         if (m_selectionBehavior == MultiSelection) {
0440             if (controlPressed) {
0441                 // Toggle the selection state of the current item.
0442                 m_selectionManager->endAnchoredSelection();
0443                 m_selectionManager->setSelected(index, 1, KItemListSelectionManager::Toggle);
0444                 m_selectionManager->beginAnchoredSelection(index);
0445                 break;
0446             } else {
0447                 // Select the current item if it is not selected yet.
0448                 const int current = m_selectionManager->currentItem();
0449                 if (!m_selectionManager->isSelected(current)) {
0450                     m_selectionManager->setSelected(current);
0451                     break;
0452                 }
0453             }
0454         }
0455         Q_FALLTHROUGH(); // fall through to the default case and add the Space to the current search string.
0456     default:
0457         m_keyboardManager->addKeys(event->text());
0458         // Make sure unconsumed events get propagated up the chain. #302329
0459         event->ignore();
0460         return false;
0461     }
0462 
0463     if (m_selectionManager->currentItem() != index) {
0464         switch (m_selectionBehavior) {
0465         case NoSelection:
0466             m_selectionManager->setCurrentItem(index);
0467             break;
0468 
0469         case SingleSelection:
0470             m_selectionManager->setCurrentItem(index);
0471             m_selectionManager->clearSelection();
0472             m_selectionManager->setSelected(index, 1);
0473             break;
0474 
0475         case MultiSelection:
0476             if (controlPressed) {
0477                 m_selectionManager->endAnchoredSelection();
0478             }
0479 
0480             m_selectionManager->setCurrentItem(index);
0481 
0482             if (!shiftOrControlPressed) {
0483                 m_selectionManager->clearSelection();
0484                 m_selectionManager->setSelected(index, 1);
0485             }
0486 
0487             if (!shiftPressed) {
0488                 m_selectionManager->beginAnchoredSelection(index);
0489             }
0490             break;
0491         }
0492     }
0493 
0494     if (navigationPressed) {
0495         m_view->scrollToItem(index);
0496     }
0497     return true;
0498 }
0499 
0500 void KItemListController::slotChangeCurrentItem(const QString &text, bool searchFromNextItem)
0501 {
0502     if (!m_model || m_model->count() == 0) {
0503         return;
0504     }
0505     int index;
0506     if (searchFromNextItem) {
0507         const int currentIndex = m_selectionManager->currentItem();
0508         index = m_model->indexForKeyboardSearch(text, (currentIndex + 1) % m_model->count());
0509     } else {
0510         index = m_model->indexForKeyboardSearch(text, 0);
0511     }
0512     if (index >= 0) {
0513         m_selectionManager->setCurrentItem(index);
0514 
0515         if (m_selectionBehavior != NoSelection) {
0516             m_selectionManager->replaceSelection(index);
0517             m_selectionManager->beginAnchoredSelection(index);
0518         }
0519 
0520         m_view->scrollToItem(index, KItemListView::ViewItemPosition::Beginning);
0521     }
0522 }
0523 
0524 void KItemListController::slotAutoActivationTimeout()
0525 {
0526     if (!m_model || !m_view) {
0527         return;
0528     }
0529 
0530     const int index = m_autoActivationTimer->property("index").toInt();
0531     if (index < 0 || index >= m_model->count()) {
0532         return;
0533     }
0534 
0535     /* m_view->isUnderMouse() fixes a bug in the Folder-View-Panel and in the
0536      * Places-Panel.
0537      *
0538      * Bug: When you drag a file onto a Folder-View-Item or a Places-Item and
0539      * then move away before the auto-activation timeout triggers, than the
0540      * item still becomes activated/expanded.
0541      *
0542      * See Bug 293200 and 305783
0543      */
0544     if (m_view->isUnderMouse()) {
0545         if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
0546             const bool expanded = m_model->isExpanded(index);
0547             m_model->setExpanded(index, !expanded);
0548         } else if (m_autoActivationBehavior != ExpansionOnly) {
0549             Q_EMIT itemActivated(index);
0550         }
0551     }
0552 }
0553 
0554 bool KItemListController::inputMethodEvent(QInputMethodEvent *event)
0555 {
0556     Q_UNUSED(event)
0557     return false;
0558 }
0559 
0560 bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent *event, const QTransform &transform)
0561 {
0562     m_mousePress = true;
0563     m_pressedMouseGlobalPos = event->screenPos();
0564 
0565     if (event->source() == Qt::MouseEventSynthesizedByQt && m_isTouchEvent) {
0566         return false;
0567     }
0568 
0569     if (!m_view) {
0570         return false;
0571     }
0572 
0573     const QPointF pressedMousePos = transform.map(event->pos());
0574     m_pressedIndex = m_view->itemAt(pressedMousePos);
0575 
0576     const Qt::MouseButtons buttons = event->buttons();
0577 
0578     if (!onPress(event->screenPos(), event->pos(), event->modifiers(), buttons)) {
0579         startRubberBand();
0580         return false;
0581     }
0582 
0583     return true;
0584 }
0585 
0586 bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent *event, const QTransform &transform)
0587 {
0588     if (!m_view) {
0589         return false;
0590     }
0591 
0592     if (m_view->m_tapAndHoldIndicator->isActive()) {
0593         m_view->m_tapAndHoldIndicator->setActive(false);
0594     }
0595 
0596     if (event->source() == Qt::MouseEventSynthesizedByQt && !m_dragActionOrRightClick && m_isTouchEvent) {
0597         return false;
0598     }
0599 
0600     if (m_pressedIndex.has_value() && !m_view->rubberBand()->isActive()) {
0601         // Check whether a dragging should be started
0602         if (event->buttons() & Qt::LeftButton) {
0603             const auto distance = (event->screenPos() - m_pressedMouseGlobalPos).manhattanLength();
0604             if (distance >= QApplication::startDragDistance()) {
0605                 if (!m_selectionManager->isSelected(m_pressedIndex.value())) {
0606                     // Always assure that the dragged item gets selected. Usually this is already
0607                     // done on the mouse-press event, but when using the selection-toggle on a
0608                     // selected item the dragged item is not selected yet.
0609                     m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
0610                 } else {
0611                     // A selected item has been clicked to drag all selected items
0612                     // -> the selection should not be cleared when the mouse button is released.
0613                     m_clearSelectionIfItemsAreNotDragged = false;
0614                 }
0615                 startDragging();
0616                 m_mousePress = false;
0617             }
0618         }
0619     } else {
0620         KItemListRubberBand *rubberBand = m_view->rubberBand();
0621         if (rubberBand->isActive()) {
0622             QPointF endPos = transform.map(event->pos());
0623 
0624             // Update the current item.
0625             const std::optional<int> newCurrent = m_view->itemAt(endPos);
0626             if (newCurrent.has_value()) {
0627                 // It's expected that the new current index is also the new anchor (bug 163451).
0628                 m_selectionManager->endAnchoredSelection();
0629                 m_selectionManager->setCurrentItem(newCurrent.value());
0630                 m_selectionManager->beginAnchoredSelection(newCurrent.value());
0631             }
0632 
0633             if (m_view->scrollOrientation() == Qt::Vertical) {
0634                 endPos.ry() += m_view->scrollOffset();
0635             } else {
0636                 endPos.rx() += m_view->scrollOffset();
0637             }
0638             rubberBand->setEndPosition(endPos);
0639         }
0640     }
0641 
0642     return false;
0643 }
0644 
0645 bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, const QTransform &transform)
0646 {
0647     m_mousePress = false;
0648     m_isTouchEvent = false;
0649 
0650     if (!m_view) {
0651         return false;
0652     }
0653 
0654     if (m_view->m_tapAndHoldIndicator->isActive()) {
0655         m_view->m_tapAndHoldIndicator->setActive(false);
0656     }
0657 
0658     KItemListRubberBand *rubberBand = m_view->rubberBand();
0659     if (event->source() == Qt::MouseEventSynthesizedByQt && !rubberBand->isActive() && m_isTouchEvent) {
0660         return false;
0661     }
0662 
0663     Q_EMIT mouseButtonReleased(m_pressedIndex.value_or(-1), event->buttons());
0664 
0665     return onRelease(transform.map(event->pos()), event->modifiers(), event->button(), false);
0666 }
0667 
0668 bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event, const QTransform &transform)
0669 {
0670     const QPointF pos = transform.map(event->pos());
0671     const std::optional<int> index = m_view->itemAt(pos);
0672 
0673     // Expand item if desired - See Bug 295573
0674     if (m_mouseDoubleClickAction != ActivateItemOnly) {
0675         if (m_view && m_model && m_view->supportsItemExpanding() && m_model->isExpandable(index.value_or(-1))) {
0676             const bool expanded = m_model->isExpanded(index.value());
0677             m_model->setExpanded(index.value(), !expanded);
0678         }
0679     }
0680 
0681     if (event->button() & Qt::RightButton) {
0682         return false;
0683     }
0684 
0685     bool emitItemActivated = !(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced)
0686         && (event->button() & Qt::LeftButton) && index.has_value() && index.value() < m_model->count();
0687     if (emitItemActivated) {
0688         Q_EMIT itemActivated(index.value());
0689     }
0690     return false;
0691 }
0692 
0693 bool KItemListController::contextMenuEvent(QContextMenuEvent *event)
0694 {
0695     if (event->reason() == QContextMenuEvent::Keyboard) {
0696         // Emit the signal itemContextMenuRequested() if at least one item is selected.
0697         // Otherwise the signal viewContextMenuRequested() will be emitted.
0698         const KItemSet selectedItems = m_selectionManager->selectedItems();
0699         int index = -1;
0700         if (selectedItems.count() >= 2) {
0701             const int currentItemIndex = m_selectionManager->currentItem();
0702             index = selectedItems.contains(currentItemIndex) ? currentItemIndex : selectedItems.first();
0703         } else if (selectedItems.count() == 1) {
0704             index = selectedItems.first();
0705         }
0706 
0707         if (index >= 0) {
0708             const QRectF contextRect = m_view->itemContextRect(index);
0709             const QPointF pos(m_view->scene()->views().first()->mapToGlobal(contextRect.bottomRight().toPoint()));
0710             Q_EMIT itemContextMenuRequested(index, pos);
0711         } else {
0712             Q_EMIT viewContextMenuRequested(event->globalPos());
0713         }
0714         return true;
0715     }
0716 
0717     const auto pos = event->pos();
0718     const auto globalPos = event->globalPos();
0719 
0720     if (m_view->headerBoundaries().contains(pos)) {
0721         Q_EMIT headerContextMenuRequested(globalPos);
0722         return true;
0723     }
0724 
0725     const auto pressedItem = m_view->itemAt(pos);
0726     // We only open a context menu for the pressed item if it is selected.
0727     // That's because the same click might have de-selected the item or because the press was only in the row of the item but not on it.
0728     if (pressedItem && m_selectionManager->selectedItems().contains(pressedItem.value())) {
0729         // The selection rectangle for an item was clicked
0730         Q_EMIT itemContextMenuRequested(m_pressedIndex.value(), globalPos);
0731         return true;
0732     }
0733 
0734     // Remove any hover highlights so the context menu doesn't look like it applies to a row.
0735     const auto widgets = m_view->visibleItemListWidgets();
0736     for (KItemListWidget *widget : widgets) {
0737         if (widget->isHovered()) {
0738             widget->setHovered(false);
0739             Q_EMIT itemUnhovered(widget->index());
0740         }
0741     }
0742     Q_EMIT viewContextMenuRequested(globalPos);
0743     return true;
0744 }
0745 
0746 bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform)
0747 {
0748     Q_UNUSED(event)
0749     Q_UNUSED(transform)
0750 
0751     DragAndDropHelper::clearUrlListMatchesUrlCache();
0752 
0753     return false;
0754 }
0755 
0756 bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform)
0757 {
0758     Q_UNUSED(event)
0759     Q_UNUSED(transform)
0760 
0761     m_autoActivationTimer->stop();
0762     m_view->setAutoScroll(false);
0763     m_view->hideDropIndicator();
0764 
0765     KItemListWidget *widget = hoveredWidget();
0766     if (widget) {
0767         widget->setHovered(false);
0768         Q_EMIT itemUnhovered(widget->index());
0769     }
0770     return false;
0771 }
0772 
0773 bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform)
0774 {
0775     if (!m_model || !m_view) {
0776         return false;
0777     }
0778 
0779     QUrl hoveredDir = m_model->directory();
0780     KItemListWidget *oldHoveredWidget = hoveredWidget();
0781 
0782     const QPointF pos = transform.map(event->pos());
0783     KItemListWidget *newHoveredWidget = widgetForDropPos(pos);
0784     int index = -1;
0785 
0786     if (oldHoveredWidget != newHoveredWidget) {
0787         m_autoActivationTimer->stop();
0788 
0789         if (oldHoveredWidget) {
0790             oldHoveredWidget->setHovered(false);
0791             Q_EMIT itemUnhovered(oldHoveredWidget->index());
0792         }
0793     }
0794 
0795     if (newHoveredWidget) {
0796         bool droppingBetweenItems = false;
0797         if (m_model->sortRole().isEmpty()) {
0798             // The model supports inserting items between other items.
0799             droppingBetweenItems = (m_view->showDropIndicator(pos) >= 0);
0800         }
0801 
0802         index = newHoveredWidget->index();
0803 
0804         if (m_model->isDir(index)) {
0805             hoveredDir = m_model->url(index);
0806         }
0807 
0808         if (!droppingBetweenItems) {
0809             // Something has been dragged on an item.
0810             m_view->hideDropIndicator();
0811             if (!newHoveredWidget->isHovered()) {
0812                 newHoveredWidget->setHovered(true);
0813                 Q_EMIT itemHovered(index);
0814             }
0815 
0816             if (!m_autoActivationTimer->isActive() && m_autoActivationTimer->interval() >= 0) {
0817                 m_autoActivationTimer->setProperty("index", index);
0818                 m_autoActivationTimer->start();
0819             }
0820         } else {
0821             m_autoActivationTimer->stop();
0822             if (newHoveredWidget && newHoveredWidget->isHovered()) {
0823                 newHoveredWidget->setHovered(false);
0824                 Q_EMIT itemUnhovered(index);
0825             }
0826         }
0827     } else {
0828         m_view->hideDropIndicator();
0829     }
0830 
0831     if (DragAndDropHelper::urlListMatchesUrl(event->mimeData()->urls(), hoveredDir)) {
0832         event->setDropAction(Qt::IgnoreAction);
0833         event->ignore();
0834     } else {
0835         if (m_model->supportsDropping(index)) {
0836             event->setDropAction(event->proposedAction());
0837             event->accept();
0838         } else {
0839             event->setDropAction(Qt::IgnoreAction);
0840             event->ignore();
0841         }
0842     }
0843     return false;
0844 }
0845 
0846 bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform)
0847 {
0848     if (!m_view) {
0849         return false;
0850     }
0851 
0852     m_autoActivationTimer->stop();
0853     m_view->setAutoScroll(false);
0854 
0855     const QPointF pos = transform.map(event->pos());
0856 
0857     int dropAboveIndex = -1;
0858     if (m_model->sortRole().isEmpty()) {
0859         // The model supports inserting of items between other items.
0860         dropAboveIndex = m_view->showDropIndicator(pos);
0861     }
0862 
0863     if (dropAboveIndex >= 0) {
0864         // Something has been dropped between two items.
0865         m_view->hideDropIndicator();
0866         Q_EMIT aboveItemDropEvent(dropAboveIndex, event);
0867     } else if (!event->mimeData()->hasFormat(m_model->blacklistItemDropEventMimeType())) {
0868         // Something has been dropped on an item or on an empty part of the view.
0869         const KItemListWidget *receivingWidget = widgetForDropPos(pos);
0870         if (receivingWidget) {
0871             Q_EMIT itemDropEvent(receivingWidget->index(), event);
0872         } else {
0873             Q_EMIT itemDropEvent(-1, event);
0874         }
0875     }
0876 
0877     QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropEnd);
0878     QAccessible::updateAccessibility(&accessibilityEvent);
0879 
0880     return true;
0881 }
0882 
0883 bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent *event, const QTransform &transform)
0884 {
0885     Q_UNUSED(event)
0886     Q_UNUSED(transform)
0887     return false;
0888 }
0889 
0890 bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent *event, const QTransform &transform)
0891 {
0892     Q_UNUSED(transform)
0893     if (!m_model || !m_view) {
0894         return false;
0895     }
0896 
0897     // We identify the widget whose expansionArea had been hovered before this hoverMoveEvent() triggered.
0898     // we can't use hoveredWidget() here (it handles the icon+text rect, not the expansion rect)
0899     // like hoveredWidget(), we find the hovered widget for the expansion rect
0900     const auto visibleItemListWidgets = m_view->visibleItemListWidgets();
0901     const auto oldHoveredExpansionWidgetIterator = std::find_if(visibleItemListWidgets.begin(), visibleItemListWidgets.end(), [](auto &widget) {
0902         return widget->expansionAreaHovered();
0903     });
0904     const auto oldHoveredExpansionWidget =
0905         oldHoveredExpansionWidgetIterator == visibleItemListWidgets.end() ? std::nullopt : std::make_optional(*oldHoveredExpansionWidgetIterator);
0906 
0907     const auto unhoverOldHoveredWidget = [&]() {
0908         if (auto oldHoveredWidget = hoveredWidget(); oldHoveredWidget) {
0909             // handle the text+icon one
0910             oldHoveredWidget->setHovered(false);
0911             Q_EMIT itemUnhovered(oldHoveredWidget->index());
0912         }
0913     };
0914 
0915     const auto unhoverOldExpansionWidget = [&]() {
0916         if (oldHoveredExpansionWidget) {
0917             // then the expansion toggle
0918             (*oldHoveredExpansionWidget)->setExpansionAreaHovered(false);
0919         }
0920     };
0921 
0922     const QPointF pos = transform.map(event->pos());
0923     if (KItemListWidget *newHoveredWidget = widgetForPos(pos); newHoveredWidget) {
0924         // something got hovered, work out which part and set hover for the appropriate widget
0925         const auto mappedPos = newHoveredWidget->mapFromItem(m_view, pos);
0926         const bool isOnExpansionToggle = newHoveredWidget->expansionToggleRect().contains(mappedPos);
0927 
0928         if (isOnExpansionToggle) {
0929             // make sure we unhover the old one first if old!=new
0930             if (oldHoveredExpansionWidget && *oldHoveredExpansionWidget != newHoveredWidget) {
0931                 (*oldHoveredExpansionWidget)->setExpansionAreaHovered(false);
0932             }
0933             // 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)
0934             unhoverOldHoveredWidget();
0935 
0936             newHoveredWidget->setExpansionAreaHovered(true);
0937         } else {
0938             // make sure we unhover the old one first if old!=new
0939             auto oldHoveredWidget = hoveredWidget();
0940             if (oldHoveredWidget && oldHoveredWidget != newHoveredWidget) {
0941                 oldHoveredWidget->setHovered(false);
0942                 Q_EMIT itemUnhovered(oldHoveredWidget->index());
0943             }
0944             // 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)
0945             unhoverOldExpansionWidget();
0946 
0947             const bool isOverIconAndText = newHoveredWidget->iconRect().contains(mappedPos) || newHoveredWidget->textRect().contains(mappedPos);
0948             const bool hasMultipleSelection = m_selectionManager->selectedItems().count() > 1;
0949 
0950             if (hasMultipleSelection && !isOverIconAndText) {
0951                 // In case we have multiple selections, clicking on any row will deselect the selection.
0952                 // So, as a visual cue for signalling that clicking anywhere won't select, but clear current highlights,
0953                 // we disable hover of the *row*(i.e. blank space to the right of the icon+text)
0954 
0955                 // (no-op in this branch for masked hover)
0956             } else {
0957                 newHoveredWidget->setHoverPosition(mappedPos);
0958                 if (oldHoveredWidget != newHoveredWidget) {
0959                     newHoveredWidget->setHovered(true);
0960                     Q_EMIT itemHovered(newHoveredWidget->index());
0961                 }
0962             }
0963         }
0964     } else {
0965         // unhover any currently hovered expansion and text+icon widgets
0966         unhoverOldHoveredWidget();
0967         unhoverOldExpansionWidget();
0968     }
0969     return false;
0970 }
0971 
0972 bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent *event, const QTransform &transform)
0973 {
0974     Q_UNUSED(event)
0975     Q_UNUSED(transform)
0976 
0977     m_mousePress = false;
0978     m_isTouchEvent = false;
0979 
0980     if (!m_model || !m_view) {
0981         return false;
0982     }
0983 
0984     const auto widgets = m_view->visibleItemListWidgets();
0985     for (KItemListWidget *widget : widgets) {
0986         if (widget->isHovered()) {
0987             widget->setHovered(false);
0988             Q_EMIT itemUnhovered(widget->index());
0989         }
0990     }
0991     return false;
0992 }
0993 
0994 bool KItemListController::wheelEvent(QGraphicsSceneWheelEvent *event, const QTransform &transform)
0995 {
0996     Q_UNUSED(event)
0997     Q_UNUSED(transform)
0998     return false;
0999 }
1000 
1001 bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent *event, const QTransform &transform)
1002 {
1003     Q_UNUSED(event)
1004     Q_UNUSED(transform)
1005     return false;
1006 }
1007 
1008 bool KItemListController::gestureEvent(QGestureEvent *event, const QTransform &transform)
1009 {
1010     if (!m_view) {
1011         return false;
1012     }
1013 
1014     //you can touch on different views at the same time, but only one QWidget gets a mousePressEvent
1015     //we use this to get the right QWidget
1016     //the only exception is a tap gesture with state GestureStarted, we need to reset some variable
1017     if (!m_mousePress) {
1018         if (QGesture *tap = event->gesture(Qt::TapGesture)) {
1019             QTapGesture *tapGesture = static_cast<QTapGesture *>(tap);
1020             if (tapGesture->state() == Qt::GestureStarted) {
1021                 tapTriggered(tapGesture, transform);
1022             }
1023         }
1024         return false;
1025     }
1026 
1027     bool accepted = false;
1028 
1029     if (QGesture *tap = event->gesture(Qt::TapGesture)) {
1030         tapTriggered(static_cast<QTapGesture *>(tap), transform);
1031         accepted = true;
1032     }
1033     if (event->gesture(Qt::TapAndHoldGesture)) {
1034         tapAndHoldTriggered(event, transform);
1035         accepted = true;
1036     }
1037     if (event->gesture(Qt::PinchGesture)) {
1038         pinchTriggered(event, transform);
1039         accepted = true;
1040     }
1041     if (event->gesture(m_swipeGesture)) {
1042         swipeTriggered(event, transform);
1043         accepted = true;
1044     }
1045     if (event->gesture(m_twoFingerTapGesture)) {
1046         twoFingerTapTriggered(event, transform);
1047         accepted = true;
1048     }
1049     return accepted;
1050 }
1051 
1052 bool KItemListController::touchBeginEvent(QTouchEvent *event, const QTransform &transform)
1053 {
1054     Q_UNUSED(event)
1055     Q_UNUSED(transform)
1056 
1057     m_isTouchEvent = true;
1058     return false;
1059 }
1060 
1061 void KItemListController::tapTriggered(QTapGesture *tap, const QTransform &transform)
1062 {
1063     static bool scrollerWasActive = false;
1064 
1065     if (tap->state() == Qt::GestureStarted) {
1066         m_dragActionOrRightClick = false;
1067         m_isSwipeGesture = false;
1068         m_pinchGestureInProgress = false;
1069         scrollerWasActive = m_scrollerIsScrolling;
1070     }
1071 
1072     if (tap->state() == Qt::GestureFinished) {
1073         m_mousePress = false;
1074 
1075         //if at the moment of the gesture start the QScroller was active, the user made the tap
1076         //to stop the QScroller and not to tap on an item
1077         if (scrollerWasActive) {
1078             return;
1079         }
1080 
1081         if (m_view->m_tapAndHoldIndicator->isActive()) {
1082             m_view->m_tapAndHoldIndicator->setActive(false);
1083         }
1084 
1085         const QPointF pressedMousePos = transform.map(tap->position());
1086         m_pressedIndex = m_view->itemAt(pressedMousePos);
1087         if (m_dragActionOrRightClick) {
1088             m_dragActionOrRightClick = false;
1089         } else {
1090             onPress(m_pressedMouseGlobalPos.toPoint(), tap->position().toPoint(), Qt::NoModifier, Qt::LeftButton);
1091             onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::LeftButton, true);
1092         }
1093         m_isTouchEvent = false;
1094     }
1095 }
1096 
1097 void KItemListController::tapAndHoldTriggered(QGestureEvent *event, const QTransform &transform)
1098 {
1099     //the Qt TabAndHold gesture is triggerable with a mouse click, we don't want this
1100     if (!m_isTouchEvent) {
1101         return;
1102     }
1103 
1104     const QTapAndHoldGesture *tap = static_cast<QTapAndHoldGesture *>(event->gesture(Qt::TapAndHoldGesture));
1105     if (tap->state() == Qt::GestureFinished) {
1106         //if a pinch gesture is in progress we don't want a TabAndHold gesture
1107         if (m_pinchGestureInProgress) {
1108             return;
1109         }
1110         const QPointF pressedMousePos = transform.map(event->mapToGraphicsScene(tap->position()));
1111         m_pressedIndex = m_view->itemAt(pressedMousePos);
1112         if (m_pressedIndex.has_value()) {
1113             if (!m_selectionManager->isSelected(m_pressedIndex.value())) {
1114                 m_selectionManager->clearSelection();
1115                 m_selectionManager->setSelected(m_pressedIndex.value());
1116             }
1117             if (!m_selectionMode) {
1118                 Q_EMIT selectionModeChangeRequested(true);
1119             }
1120         } else {
1121             m_selectionManager->clearSelection();
1122             startRubberBand();
1123         }
1124 
1125         Q_EMIT scrollerStop();
1126 
1127         m_view->m_tapAndHoldIndicator->setStartPosition(pressedMousePos);
1128         m_view->m_tapAndHoldIndicator->setActive(true);
1129 
1130         m_dragActionOrRightClick = true;
1131     }
1132 }
1133 
1134 void KItemListController::pinchTriggered(QGestureEvent *event, const QTransform &transform)
1135 {
1136     Q_UNUSED(transform)
1137 
1138     const QPinchGesture *pinch = static_cast<QPinchGesture *>(event->gesture(Qt::PinchGesture));
1139     const qreal sensitivityModifier = 0.2;
1140     static qreal counter = 0;
1141 
1142     if (pinch->state() == Qt::GestureStarted) {
1143         m_pinchGestureInProgress = true;
1144         counter = 0;
1145     }
1146     if (pinch->state() == Qt::GestureUpdated) {
1147         //if a swipe gesture was recognized or in progress, we don't want a pinch gesture to change the zoom
1148         if (m_isSwipeGesture) {
1149             return;
1150         }
1151         counter = counter + (pinch->scaleFactor() - 1);
1152         if (counter >= sensitivityModifier) {
1153             Q_EMIT increaseZoom();
1154             counter = 0;
1155         } else if (counter <= -sensitivityModifier) {
1156             Q_EMIT decreaseZoom();
1157             counter = 0;
1158         }
1159     }
1160 }
1161 
1162 void KItemListController::swipeTriggered(QGestureEvent *event, const QTransform &transform)
1163 {
1164     Q_UNUSED(transform)
1165 
1166     const KTwoFingerSwipe *swipe = static_cast<KTwoFingerSwipe *>(event->gesture(m_swipeGesture));
1167 
1168     if (!swipe) {
1169         return;
1170     }
1171     if (swipe->state() == Qt::GestureStarted) {
1172         m_isSwipeGesture = true;
1173     }
1174 
1175     if (swipe->state() == Qt::GestureCanceled) {
1176         m_isSwipeGesture = false;
1177     }
1178 
1179     if (swipe->state() == Qt::GestureFinished) {
1180         Q_EMIT scrollerStop();
1181 
1182         if (swipe->swipeAngle() <= 20 || swipe->swipeAngle() >= 340) {
1183             Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), Qt::BackButton);
1184         } else if (swipe->swipeAngle() <= 200 && swipe->swipeAngle() >= 160) {
1185             Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), Qt::ForwardButton);
1186         } else if (swipe->swipeAngle() <= 110 && swipe->swipeAngle() >= 60) {
1187             Q_EMIT swipeUp();
1188         }
1189         m_isSwipeGesture = true;
1190     }
1191 }
1192 
1193 void KItemListController::twoFingerTapTriggered(QGestureEvent *event, const QTransform &transform)
1194 {
1195     const KTwoFingerTap *twoTap = static_cast<KTwoFingerTap *>(event->gesture(m_twoFingerTapGesture));
1196 
1197     if (!twoTap) {
1198         return;
1199     }
1200 
1201     if (twoTap->state() == Qt::GestureStarted) {
1202         const QPointF pressedMousePos = transform.map(twoTap->pos());
1203         m_pressedIndex = m_view->itemAt(pressedMousePos);
1204         if (m_pressedIndex.has_value()) {
1205             onPress(twoTap->screenPos().toPoint(), twoTap->pos().toPoint(), Qt::ControlModifier, Qt::LeftButton);
1206             onRelease(transform.map(twoTap->pos()), Qt::ControlModifier, Qt::LeftButton, false);
1207         }
1208     }
1209 }
1210 
1211 bool KItemListController::processEvent(QEvent *event, const QTransform &transform)
1212 {
1213     if (!event) {
1214         return false;
1215     }
1216 
1217     switch (event->type()) {
1218     case QEvent::KeyPress:
1219         return keyPressEvent(static_cast<QKeyEvent *>(event));
1220     case QEvent::InputMethod:
1221         return inputMethodEvent(static_cast<QInputMethodEvent *>(event));
1222     case QEvent::GraphicsSceneMousePress:
1223         return mousePressEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1224     case QEvent::GraphicsSceneMouseMove:
1225         return mouseMoveEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1226     case QEvent::GraphicsSceneMouseRelease:
1227         return mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1228     case QEvent::GraphicsSceneMouseDoubleClick:
1229         return mouseDoubleClickEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
1230     case QEvent::ContextMenu:
1231         return contextMenuEvent(static_cast<QContextMenuEvent *>(event));
1232     case QEvent::GraphicsSceneWheel:
1233         return wheelEvent(static_cast<QGraphicsSceneWheelEvent *>(event), QTransform());
1234     case QEvent::GraphicsSceneDragEnter:
1235         return dragEnterEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1236     case QEvent::GraphicsSceneDragLeave:
1237         return dragLeaveEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1238     case QEvent::GraphicsSceneDragMove:
1239         return dragMoveEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1240     case QEvent::GraphicsSceneDrop:
1241         return dropEvent(static_cast<QGraphicsSceneDragDropEvent *>(event), QTransform());
1242     case QEvent::GraphicsSceneHoverEnter:
1243         return hoverEnterEvent(static_cast<QGraphicsSceneHoverEvent *>(event), QTransform());
1244     case QEvent::GraphicsSceneHoverMove:
1245         return hoverMoveEvent(static_cast<QGraphicsSceneHoverEvent *>(event), QTransform());
1246     case QEvent::GraphicsSceneHoverLeave:
1247         return hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent *>(event), QTransform());
1248     case QEvent::GraphicsSceneResize:
1249         return resizeEvent(static_cast<QGraphicsSceneResizeEvent *>(event), transform);
1250     case QEvent::Gesture:
1251         return gestureEvent(static_cast<QGestureEvent *>(event), transform);
1252     case QEvent::TouchBegin:
1253         return touchBeginEvent(static_cast<QTouchEvent *>(event), transform);
1254     default:
1255         break;
1256     }
1257 
1258     return false;
1259 }
1260 
1261 void KItemListController::slotViewScrollOffsetChanged(qreal current, qreal previous)
1262 {
1263     if (!m_view) {
1264         return;
1265     }
1266 
1267     KItemListRubberBand *rubberBand = m_view->rubberBand();
1268     if (rubberBand->isActive()) {
1269         const qreal diff = current - previous;
1270         // TODO: Ideally just QCursor::pos() should be used as
1271         // new end-position but it seems there is no easy way
1272         // to have something like QWidget::mapFromGlobal() for QGraphicsWidget
1273         // (... or I just missed an easy way to do the mapping)
1274         QPointF endPos = rubberBand->endPosition();
1275         if (m_view->scrollOrientation() == Qt::Vertical) {
1276             endPos.ry() += diff;
1277         } else {
1278             endPos.rx() += diff;
1279         }
1280 
1281         rubberBand->setEndPosition(endPos);
1282     }
1283 }
1284 
1285 void KItemListController::slotRubberBandChanged()
1286 {
1287     if (!m_view || !m_model || m_model->count() <= 0) {
1288         return;
1289     }
1290 
1291     const KItemListRubberBand *rubberBand = m_view->rubberBand();
1292     const QPointF startPos = rubberBand->startPosition();
1293     const QPointF endPos = rubberBand->endPosition();
1294     QRectF rubberBandRect = QRectF(startPos, endPos).normalized();
1295 
1296     const bool scrollVertical = (m_view->scrollOrientation() == Qt::Vertical);
1297     if (scrollVertical) {
1298         rubberBandRect.translate(0, -m_view->scrollOffset());
1299     } else {
1300         rubberBandRect.translate(-m_view->scrollOffset(), 0);
1301     }
1302 
1303     if (!m_oldSelection.isEmpty()) {
1304         // Clear the old selection that was available before the rubberband has
1305         // been activated in case if no Shift- or Control-key are pressed
1306         const bool shiftOrControlPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier || QApplication::keyboardModifiers() & Qt::ControlModifier;
1307         if (!shiftOrControlPressed && !m_selectionMode) {
1308             m_oldSelection.clear();
1309         }
1310     }
1311 
1312     KItemSet selectedItems;
1313 
1314     // Select all visible items that intersect with the rubberband
1315     const auto widgets = m_view->visibleItemListWidgets();
1316     for (const KItemListWidget *widget : widgets) {
1317         const int index = widget->index();
1318 
1319         const QRectF widgetRect = m_view->itemRect(index);
1320         if (widgetRect.intersects(rubberBandRect)) {
1321             // Select the full row intersecting with the rubberband rectangle
1322             const QRectF selectionRect = widget->selectionRect().translated(widgetRect.topLeft());
1323             const QRectF iconRect = widget->iconRect().translated(widgetRect.topLeft());
1324             if (selectionRect.intersects(rubberBandRect) || iconRect.intersects(rubberBandRect)) {
1325                 selectedItems.insert(index);
1326             }
1327         }
1328     }
1329 
1330     // Select all invisible items that intersect with the rubberband. Instead of
1331     // iterating all items only the area which might be touched by the rubberband
1332     // will be checked.
1333     const bool increaseIndex = scrollVertical ? startPos.y() > endPos.y() : startPos.x() > endPos.x();
1334 
1335     int index = increaseIndex ? m_view->lastVisibleIndex() + 1 : m_view->firstVisibleIndex() - 1;
1336     bool selectionFinished = false;
1337     do {
1338         const QRectF widgetRect = m_view->itemRect(index);
1339         if (widgetRect.intersects(rubberBandRect)) {
1340             selectedItems.insert(index);
1341         }
1342 
1343         if (increaseIndex) {
1344             ++index;
1345             selectionFinished = (index >= m_model->count()) || (scrollVertical && widgetRect.top() > rubberBandRect.bottom())
1346                 || (!scrollVertical && widgetRect.left() > rubberBandRect.right());
1347         } else {
1348             --index;
1349             selectionFinished = (index < 0) || (scrollVertical && widgetRect.bottom() < rubberBandRect.top())
1350                 || (!scrollVertical && widgetRect.right() < rubberBandRect.left());
1351         }
1352     } while (!selectionFinished);
1353 
1354     if ((QApplication::keyboardModifiers() & Qt::ControlModifier) || m_selectionMode) {
1355         // If Control is pressed, the selection state of all items in the rubberband is toggled.
1356         // Therefore, the new selection contains:
1357         // 1. All previously selected items which are not inside the rubberband, and
1358         // 2. all items inside the rubberband which have not been selected previously.
1359         m_selectionManager->setSelectedItems(m_oldSelection ^ selectedItems);
1360     } else {
1361         m_selectionManager->setSelectedItems(selectedItems + m_oldSelection);
1362     }
1363 }
1364 
1365 void KItemListController::startDragging()
1366 {
1367     if (!m_view || !m_model) {
1368         return;
1369     }
1370 
1371     const KItemSet selectedItems = m_selectionManager->selectedItems();
1372     if (selectedItems.isEmpty()) {
1373         return;
1374     }
1375 
1376     QMimeData *data = m_model->createMimeData(selectedItems);
1377     if (!data) {
1378         return;
1379     }
1380     KUrlMimeData::exportUrlsToPortal(data);
1381 
1382     // The created drag object will be owned and deleted
1383     // by QApplication::activeWindow().
1384     QDrag *drag = new QDrag(QApplication::activeWindow());
1385     drag->setMimeData(data);
1386 
1387     const QPixmap pixmap = m_view->createDragPixmap(selectedItems);
1388     drag->setPixmap(pixmap);
1389 
1390     const QPoint hotSpot((pixmap.width() / pixmap.devicePixelRatio()) / 2, 0);
1391     drag->setHotSpot(hotSpot);
1392 
1393     drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction);
1394 
1395     QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropStart);
1396     QAccessible::updateAccessibility(&accessibilityEvent);
1397 }
1398 
1399 KItemListWidget *KItemListController::hoveredWidget() const
1400 {
1401     Q_ASSERT(m_view);
1402 
1403     const auto widgets = m_view->visibleItemListWidgets();
1404     for (KItemListWidget *widget : widgets) {
1405         if (widget->isHovered()) {
1406             return widget;
1407         }
1408     }
1409 
1410     return nullptr;
1411 }
1412 
1413 KItemListWidget *KItemListController::widgetForPos(const QPointF &pos) const
1414 {
1415     Q_ASSERT(m_view);
1416 
1417     if (m_view->headerBoundaries().contains(pos)) {
1418         return nullptr;
1419     }
1420 
1421     const auto widgets = m_view->visibleItemListWidgets();
1422     for (KItemListWidget *widget : widgets) {
1423         const QPointF mappedPos = widget->mapFromItem(m_view, pos);
1424         if (widget->contains(mappedPos) || widget->selectionRect().contains(mappedPos)) {
1425             return widget;
1426         }
1427     }
1428 
1429     return nullptr;
1430 }
1431 
1432 KItemListWidget *KItemListController::widgetForDropPos(const QPointF &pos) const
1433 {
1434     Q_ASSERT(m_view);
1435 
1436     if (m_view->headerBoundaries().contains(pos)) {
1437         return nullptr;
1438     }
1439 
1440     const auto widgets = m_view->visibleItemListWidgets();
1441     for (KItemListWidget *widget : widgets) {
1442         const QPointF mappedPos = widget->mapFromItem(m_view, pos);
1443         if (widget->contains(mappedPos)) {
1444             return widget;
1445         }
1446     }
1447 
1448     return nullptr;
1449 }
1450 
1451 void KItemListController::updateKeyboardAnchor()
1452 {
1453     const bool validAnchor =
1454         m_keyboardAnchorIndex >= 0 && m_keyboardAnchorIndex < m_model->count() && keyboardAnchorPos(m_keyboardAnchorIndex) == m_keyboardAnchorPos;
1455     if (!validAnchor) {
1456         const int index = m_selectionManager->currentItem();
1457         m_keyboardAnchorIndex = index;
1458         m_keyboardAnchorPos = keyboardAnchorPos(index);
1459     }
1460 }
1461 
1462 int KItemListController::nextRowIndex(int index) const
1463 {
1464     if (m_keyboardAnchorIndex < 0) {
1465         return index;
1466     }
1467 
1468     const int maxIndex = m_model->count() - 1;
1469     if (index == maxIndex) {
1470         return index;
1471     }
1472 
1473     // Calculate the index of the last column inside the row of the current index
1474     int lastColumnIndex = index;
1475     while (keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex)) {
1476         ++lastColumnIndex;
1477         if (lastColumnIndex >= maxIndex) {
1478             return index;
1479         }
1480     }
1481 
1482     // Based on the last column index go to the next row and calculate the nearest index
1483     // that is below the current index
1484     int nextRowIndex = lastColumnIndex + 1;
1485     int searchIndex = nextRowIndex;
1486     qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(nextRowIndex));
1487     while (searchIndex < maxIndex && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex)) {
1488         ++searchIndex;
1489         const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1490         if (searchDiff < minDiff) {
1491             minDiff = searchDiff;
1492             nextRowIndex = searchIndex;
1493         }
1494     }
1495 
1496     return nextRowIndex;
1497 }
1498 
1499 int KItemListController::previousRowIndex(int index) const
1500 {
1501     if (m_keyboardAnchorIndex < 0 || index == 0) {
1502         return index;
1503     }
1504 
1505     // Calculate the index of the first column inside the row of the current index
1506     int firstColumnIndex = index;
1507     while (keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex)) {
1508         --firstColumnIndex;
1509         if (firstColumnIndex <= 0) {
1510             return index;
1511         }
1512     }
1513 
1514     // Based on the first column index go to the previous row and calculate the nearest index
1515     // that is above the current index
1516     int previousRowIndex = firstColumnIndex - 1;
1517     int searchIndex = previousRowIndex;
1518     qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(previousRowIndex));
1519     while (searchIndex > 0 && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex)) {
1520         --searchIndex;
1521         const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1522         if (searchDiff < minDiff) {
1523             minDiff = searchDiff;
1524             previousRowIndex = searchIndex;
1525         }
1526     }
1527 
1528     return previousRowIndex;
1529 }
1530 
1531 qreal KItemListController::keyboardAnchorPos(int index) const
1532 {
1533     const QRectF itemRect = m_view->itemRect(index);
1534     if (!itemRect.isEmpty()) {
1535         return (m_view->scrollOrientation() == Qt::Vertical) ? itemRect.x() : itemRect.y();
1536     }
1537 
1538     return 0;
1539 }
1540 
1541 void KItemListController::updateExtendedSelectionRegion()
1542 {
1543     if (m_view) {
1544         const bool extend = (m_selectionBehavior != MultiSelection);
1545         KItemListStyleOption option = m_view->styleOption();
1546         if (option.extendedSelectionRegion != extend) {
1547             option.extendedSelectionRegion = extend;
1548             m_view->setStyleOption(option);
1549         }
1550     }
1551 }
1552 
1553 bool KItemListController::onPress(const QPoint &screenPos, const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons)
1554 {
1555     Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), buttons);
1556 
1557     if (buttons & (Qt::BackButton | Qt::ForwardButton)) {
1558         // Do not select items when clicking the back/forward buttons, see
1559         // https://bugs.kde.org/show_bug.cgi?id=327412.
1560         return true;
1561     }
1562 
1563     const QPointF pressedMousePos = m_view->transform().map(pos);
1564 
1565     if (m_view->isAboveExpansionToggle(m_pressedIndex.value_or(-1), pressedMousePos)) {
1566         m_selectionManager->endAnchoredSelection();
1567         m_selectionManager->setCurrentItem(m_pressedIndex.value());
1568         m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1569         return true;
1570     }
1571 
1572     m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), pressedMousePos);
1573     if (m_selectionTogglePressed) {
1574         m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1575         // The previous anchored selection has been finished already in
1576         // KItemListSelectionManager::setSelected(). We can safely change
1577         // the current item and start a new anchored selection now.
1578         m_selectionManager->setCurrentItem(m_pressedIndex.value());
1579         m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1580         return true;
1581     }
1582 
1583     const bool shiftPressed = modifiers & Qt::ShiftModifier;
1584     const bool controlPressed = (modifiers & Qt::ControlModifier) || m_selectionMode; // Keeping selectionMode similar to pressing control will hopefully
1585                                                                                       // simplify the overall logic and possibilities both for users and devs.
1586     const bool leftClick = buttons & Qt::LeftButton;
1587     const bool rightClick = buttons & Qt::RightButton;
1588 
1589     // The previous selection is cleared if either
1590     // 1. The selection mode is SingleSelection, or
1591     // 2. the selection mode is MultiSelection, and *none* of the following conditions are met:
1592     //    a) Shift or Control are pressed.
1593     //    b) The clicked item is selected already. In that case, the user might want to:
1594     //       - start dragging multiple items, or
1595     //       - open the context menu and perform an action for all selected items.
1596     const bool shiftOrControlPressed = shiftPressed || controlPressed;
1597     const bool pressedItemAlreadySelected = m_pressedIndex.has_value() && m_selectionManager->isSelected(m_pressedIndex.value());
1598     const bool clearSelection = m_selectionBehavior == SingleSelection || (!shiftOrControlPressed && !pressedItemAlreadySelected);
1599 
1600     // When this method returns false, a rubberBand selection is created using KItemListController::startRubberBand via the caller.
1601     if (clearSelection) {
1602         const int selectedItemsCount = m_selectionManager->selectedItems().count();
1603         m_selectionManager->clearSelection();
1604         // clear and bail when we got an existing multi-selection
1605         if (selectedItemsCount > 1 && m_pressedIndex.has_value()) {
1606             const auto row = m_view->m_visibleItems.value(m_pressedIndex.value());
1607             const auto mappedPos = row->mapFromItem(m_view, pos);
1608             if (pressedItemAlreadySelected || row->iconRect().contains(mappedPos) || row->textRect().contains(mappedPos)) {
1609                 // we are indeed inside the text/icon rect, keep m_pressedIndex what it is
1610                 // and short-circuit for single-click activation (it will then propagate to onRelease and activate the item)
1611                 // or we just keep going for double-click activation
1612                 if (m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) {
1613                     if (!pressedItemAlreadySelected) {
1614                         // An unselected item was clicked directly while deselecting multiple other items so we select it.
1615                         m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1616                         m_selectionManager->setCurrentItem(m_pressedIndex.value());
1617                         m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1618                     }
1619                     return true; // event handled, don't create rubber band
1620                 }
1621             } else {
1622                 // we're not inside the text/icon rect, as we've already cleared the selection
1623                 // we can just stop here and make sure handlers down the line (i.e. onRelease) don't activate
1624                 m_pressedIndex.reset();
1625                 // we don't stop event propagation and proceed to create a rubber band and let onRelease
1626                 // decide (based on m_pressedIndex) whether we're in a drag (drag => new rubber band, click => don't select the item)
1627                 return false;
1628             }
1629         }
1630     } else if (pressedItemAlreadySelected && !shiftOrControlPressed && leftClick) {
1631         // The user might want to start dragging multiple items, but if he clicks the item
1632         // in order to trigger it instead, the other selected items must be deselected.
1633         // However, we do not know yet what the user is going to do.
1634         // -> remember that the user pressed an item which had been selected already and
1635         //    clear the selection in mouseReleaseEvent(), unless the items are dragged.
1636         m_clearSelectionIfItemsAreNotDragged = true;
1637 
1638         if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex.value_or(-1), pressedMousePos)) {
1639             Q_EMIT selectedItemTextPressed(m_pressedIndex.value_or(-1));
1640         }
1641     }
1642 
1643     if (!shiftPressed) {
1644         // Finish the anchored selection before the current index is changed
1645         m_selectionManager->endAnchoredSelection();
1646     }
1647 
1648     if (rightClick) {
1649         // Stop rubber band from persisting after right-clicks
1650         KItemListRubberBand *rubberBand = m_view->rubberBand();
1651         if (rubberBand->isActive()) {
1652             disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1653             rubberBand->setActive(false);
1654             m_view->setAutoScroll(false);
1655         }
1656     }
1657 
1658     if (m_pressedIndex.has_value()) {
1659         // The hover highlight area of an item is being pressed.
1660         const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); // anything outside of row.contains() will be the empty region of the row rect
1661         const bool hitTargetIsRowEmptyRegion = !row->contains(row->mapFromItem(m_view, pos));
1662         // again, when this method returns false, a rubberBand selection is created as the event is not consumed;
1663         // createRubberBand here tells us whether to return true or false.
1664         bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty());
1665 
1666         if (rightClick && hitTargetIsRowEmptyRegion) {
1667             // We have a right click outside the icon and text rect but within the hover highlight area.
1668             // We don't want items to get selected through this, so we return now.
1669             return true;
1670         }
1671 
1672         m_selectionManager->setCurrentItem(m_pressedIndex.value());
1673 
1674         switch (m_selectionBehavior) {
1675         case NoSelection:
1676             break;
1677 
1678         case SingleSelection:
1679             m_selectionManager->setSelected(m_pressedIndex.value());
1680             break;
1681 
1682         case MultiSelection:
1683             if (controlPressed && !shiftPressed && leftClick) {
1684                 // A left mouse button press is happening on an item while control is pressed. This either means a user wants to:
1685                 // - toggle the selection of item(s) or
1686                 // - they want to begin a drag on the item(s) to copy them.
1687                 // We rule out the latter, if the item is not clicked directly and was unselected previously.
1688                 const auto row = m_view->m_visibleItems.value(m_pressedIndex.value());
1689                 const auto mappedPos = row->mapFromItem(m_view, pos);
1690                 if (!row->iconRect().contains(mappedPos) && !row->textRect().contains(mappedPos) && !pressedItemAlreadySelected) {
1691                     createRubberBand = true;
1692                 } else {
1693                     m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1694                     m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1695                     createRubberBand = false; // multi selection, don't propagate any further
1696                     // This will be the start of an item drag-to-copy operation if the user now moves the mouse before releasing the mouse button.
1697                 }
1698             } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
1699                 // Select the pressed item and start a new anchored selection
1700                 m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select);
1701                 m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1702             }
1703             break;
1704 
1705         default:
1706             Q_ASSERT(false);
1707             break;
1708         }
1709 
1710         return !createRubberBand;
1711     }
1712 
1713     return false;
1714 }
1715 
1716 bool KItemListController::onRelease(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch)
1717 {
1718     const QPointF pressedMousePos = pos;
1719     const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), pressedMousePos);
1720     if (isAboveSelectionToggle) {
1721         m_selectionTogglePressed = false;
1722         return true;
1723     }
1724 
1725     if (!isAboveSelectionToggle && m_selectionTogglePressed) {
1726         m_selectionManager->setSelected(m_pressedIndex.value_or(-1), 1, KItemListSelectionManager::Toggle);
1727         m_selectionTogglePressed = false;
1728         return true;
1729     }
1730 
1731     const bool controlPressed = modifiers & Qt::ControlModifier;
1732     const bool shiftOrControlPressed = modifiers & Qt::ShiftModifier || controlPressed;
1733 
1734     const std::optional<int> index = m_view->itemAt(pos);
1735 
1736     KItemListRubberBand *rubberBand = m_view->rubberBand();
1737     bool rubberBandRelease = false;
1738     if (rubberBand->isActive()) {
1739         disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1740         rubberBand->setActive(false);
1741         m_oldSelection.clear();
1742         m_view->setAutoScroll(false);
1743         rubberBandRelease = true;
1744         // We check for actual rubber band drag here: if delta between start and end is less than drag threshold,
1745         // then we have a single click on one of the rows
1746         if ((rubberBand->endPosition() - rubberBand->startPosition()).manhattanLength() < QApplication::startDragDistance()) {
1747             rubberBandRelease = false; // since we're only selecting, unmark rubber band release flag
1748             // m_pressedIndex will have no value if we came from a multi-selection clearing onPress
1749             // in that case, we don't select anything
1750             if (index.has_value() && m_pressedIndex.has_value()) {
1751                 if (controlPressed && m_selectionBehavior == MultiSelection) {
1752                     m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
1753                 } else {
1754                     m_selectionManager->setSelected(index.value());
1755                 }
1756                 if (!m_selectionManager->isAnchoredSelectionActive()) {
1757                     m_selectionManager->beginAnchoredSelection(index.value());
1758                 }
1759             }
1760         }
1761     }
1762 
1763     if (index.has_value() && index == m_pressedIndex) {
1764         // The release event is done above the same item as the press event
1765 
1766         if (m_clearSelectionIfItemsAreNotDragged) {
1767             // A selected item has been clicked, but no drag operation has been started
1768             // -> clear the rest of the selection.
1769             m_selectionManager->clearSelection();
1770             m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select);
1771             m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
1772         }
1773 
1774         if (buttons & Qt::LeftButton) {
1775             bool emitItemActivated = true;
1776             if (m_view->isAboveExpansionToggle(index.value(), pos)) {
1777                 const bool expanded = m_model->isExpanded(index.value());
1778                 m_model->setExpanded(index.value(), !expanded);
1779 
1780                 Q_EMIT itemExpansionToggleClicked(index.value());
1781                 emitItemActivated = false;
1782             } else if (shiftOrControlPressed && m_selectionBehavior != SingleSelection) {
1783                 // The mouse click should only update the selection, not trigger the item, except when
1784                 // we are in single selection mode
1785                 emitItemActivated = false;
1786             } else {
1787                 const bool singleClickActivation = m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced;
1788                 if (!singleClickActivation) {
1789                     emitItemActivated = touch && !m_selectionMode;
1790                 } else {
1791                     // activate on single click only if we didn't come from a rubber band release
1792                     emitItemActivated = !rubberBandRelease;
1793                 }
1794             }
1795             if (emitItemActivated) {
1796                 Q_EMIT itemActivated(index.value());
1797             }
1798         } else if (buttons & Qt::MiddleButton) {
1799             Q_EMIT itemMiddleClicked(index.value());
1800         }
1801     }
1802 
1803     m_pressedMouseGlobalPos = QPointF();
1804     m_pressedIndex = std::nullopt;
1805     m_clearSelectionIfItemsAreNotDragged = false;
1806     return false;
1807 }
1808 
1809 void KItemListController::startRubberBand()
1810 {
1811     if (m_selectionBehavior == MultiSelection) {
1812         QPoint startPos = m_view->transform().map(m_view->scene()->views().first()->mapFromGlobal(m_pressedMouseGlobalPos.toPoint()));
1813         if (m_view->scrollOrientation() == Qt::Vertical) {
1814             startPos.ry() += m_view->scrollOffset();
1815         } else {
1816             startPos.rx() += m_view->scrollOffset();
1817         }
1818 
1819         m_oldSelection = m_selectionManager->selectedItems();
1820         KItemListRubberBand *rubberBand = m_view->rubberBand();
1821         rubberBand->setStartPosition(startPos);
1822         rubberBand->setEndPosition(startPos);
1823         rubberBand->setActive(true);
1824         connect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1825         m_view->setAutoScroll(true);
1826     }
1827 }
1828 
1829 void KItemListController::slotStateChanged(QScroller::State newState)
1830 {
1831     if (newState == QScroller::Scrolling) {
1832         m_scrollerIsScrolling = true;
1833     } else if (newState == QScroller::Inactive) {
1834         m_scrollerIsScrolling = false;
1835     }
1836 }
1837 
1838 #include "moc_kitemlistcontroller.cpp"