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"