File indexing completed on 2024-04-28 05:45:07

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