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"