From 046749b073422646b5ee7f5cbc1646519e471698 Mon Sep 17 00:00:00 2001 From: Felix Ernst Date: Mon, 13 Nov 2023 17:50:24 +0100 Subject: [PATCH] Make main view react to context menu events Before this commit, Dolphin's main view would not react to any context menu events. It only showed context menus based on hard-coded mouse or keyboard events i.e. mouse right-click and presses of the "Menu" key. This commit removes those hard-coded reactions and instead makes it so the view shows a context menu whenever a QContextMenuEvent is received. Therefore, a context menu will now be opened when any platform- or system-specific context menu triggers are invoked e.g. the Shift+F10 keyboard shortcut. Aside from this, the only side-effect is a partial removal of an unrelated bug: Previously, the hover highlight on items was never cleared when the header column in details view mode was hovered. With this commit, the hover is now correctly cleared most of the time. --- src/kitemviews/kitemlistcontainer.cpp | 15 +++ src/kitemviews/kitemlistcontainer.h | 1 + src/kitemviews/kitemlistcontroller.cpp | 129 +++++++++++++------------ src/kitemviews/kitemlistcontroller.h | 2 + src/kitemviews/kitemlistview.cpp | 4 + 5 files changed, 90 insertions(+), 61 deletions(-) diff --git a/src/kitemviews/kitemlistcontainer.cpp b/src/kitemviews/kitemlistcontainer.cpp index f89a6c8ce3..1cac8f7a60 100644 --- a/src/kitemviews/kitemlistcontainer.cpp +++ b/src/kitemviews/kitemlistcontainer.cpp @@ -138,6 +138,21 @@ void KItemListContainer::keyPressEvent(QKeyEvent *event) } } +void KItemListContainer::contextMenuEvent(QContextMenuEvent *event) +{ + // Note copied from the keyPressEvent() method above because the same reasons probably also apply here. + // TODO: We should find a better way to handle the context menu events in the view. + // The reasons why we need this hack are: + // 1. Without reimplementing contextMenuEvent() here, the event would not reach the QGraphicsView. + // 2. By default, the KItemListView does not have the keyboard focus in the QGraphicsScene, so + // simply sending the event to the QGraphicsView which is the KItemListContainer's viewport + // does not work. + KItemListView *view = m_controller->view(); + if (view) { + QApplication::sendEvent(view, event); + } +} + void KItemListContainer::showEvent(QShowEvent *event) { QAbstractScrollArea::showEvent(event); diff --git a/src/kitemviews/kitemlistcontainer.h b/src/kitemviews/kitemlistcontainer.h index 40b0753764..93016ab82b 100644 --- a/src/kitemviews/kitemlistcontainer.h +++ b/src/kitemviews/kitemlistcontainer.h @@ -46,6 +46,7 @@ public: protected: void keyPressEvent(QKeyEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; void showEvent(QShowEvent *event) override; void resizeEvent(QResizeEvent *event) override; void scrollContentsBy(int dx, int dy) override; diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp index 5abf1830be..60f23c1dbe 100644 --- a/src/kitemviews/kitemlistcontroller.cpp +++ b/src/kitemviews/kitemlistcontroller.cpp @@ -425,28 +425,6 @@ bool KItemListController::keyPressEvent(QKeyEvent *event) break; } - case Qt::Key_Menu: { - // Emit the signal itemContextMenuRequested() in case if at least one - // item is selected. Otherwise the signal viewContextMenuRequested() will be emitted. - const KItemSet selectedItems = m_selectionManager->selectedItems(); - int index = -1; - if (selectedItems.count() >= 2) { - const int currentItemIndex = m_selectionManager->currentItem(); - index = selectedItems.contains(currentItemIndex) ? currentItemIndex : selectedItems.first(); - } else if (selectedItems.count() == 1) { - index = selectedItems.first(); - } - - if (index >= 0) { - const QRectF contextRect = m_view->itemContextRect(index); - const QPointF pos(m_view->scene()->views().first()->mapToGlobal(contextRect.bottomRight().toPoint())); - Q_EMIT itemContextMenuRequested(index, pos); - } else { - Q_EMIT viewContextMenuRequested(QCursor::pos()); - } - break; - } - case Qt::Key_Escape: if (m_selectionMode) { Q_EMIT selectionModeChangeRequested(false); @@ -701,19 +679,7 @@ bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event, } if (event->button() & Qt::RightButton) { - m_selectionManager->clearSelection(); - if (index.has_value()) { - m_selectionManager->setSelected(index.value()); - Q_EMIT itemContextMenuRequested(index.value(), event->screenPos()); - } else { - const QRectF headerBounds = m_view->headerBoundaries(); - if (headerBounds.contains(event->pos())) { - Q_EMIT headerContextMenuRequested(event->screenPos()); - } else { - Q_EMIT viewContextMenuRequested(event->screenPos()); - } - } - return true; + return false; } bool emitItemActivated = !(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) @@ -724,6 +690,59 @@ bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event, return false; } +bool KItemListController::contextMenuEvent(QContextMenuEvent *event) +{ + if (event->reason() == QContextMenuEvent::Keyboard) { + // Emit the signal itemContextMenuRequested() if at least one item is selected. + // Otherwise the signal viewContextMenuRequested() will be emitted. + const KItemSet selectedItems = m_selectionManager->selectedItems(); + int index = -1; + if (selectedItems.count() >= 2) { + const int currentItemIndex = m_selectionManager->currentItem(); + index = selectedItems.contains(currentItemIndex) ? currentItemIndex : selectedItems.first(); + } else if (selectedItems.count() == 1) { + index = selectedItems.first(); + } + + if (index >= 0) { + const QRectF contextRect = m_view->itemContextRect(index); + const QPointF pos(m_view->scene()->views().first()->mapToGlobal(contextRect.bottomRight().toPoint())); + Q_EMIT itemContextMenuRequested(index, pos); + } else { + Q_EMIT viewContextMenuRequested(event->globalPos()); + } + return true; + } + + const auto pos = event->pos(); + const auto globalPos = event->globalPos(); + + if (m_view->headerBoundaries().contains(pos)) { + Q_EMIT headerContextMenuRequested(globalPos); + return true; + } + + const auto pressedItem = m_view->itemAt(pos); + // We only open a context menu for the pressed item if it is selected. + // 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. + if (pressedItem && m_selectionManager->selectedItems().contains(pressedItem.value())) { + // The selection rectangle for an item was clicked + Q_EMIT itemContextMenuRequested(m_pressedIndex.value(), globalPos); + return true; + } + + // Remove any hover highlights so the context menu doesn't look like it applies to a row. + const auto widgets = m_view->visibleItemListWidgets(); + for (KItemListWidget *widget : widgets) { + if (widget->isHovered()) { + widget->setHovered(false); + Q_EMIT itemUnhovered(widget->index()); + } + } + Q_EMIT viewContextMenuRequested(globalPos); + return true; +} + bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform) { Q_UNUSED(event) @@ -1208,6 +1227,8 @@ bool KItemListController::processEvent(QEvent *event, const QTransform &transfor return mouseReleaseEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneMouseDoubleClick: return mouseDoubleClickEvent(static_cast(event), QTransform()); + case QEvent::ContextMenu: + return contextMenuEvent(static_cast(event)); case QEvent::GraphicsSceneWheel: return wheelEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneDragEnter: @@ -1393,6 +1414,10 @@ KItemListWidget *KItemListController::widgetForPos(const QPointF &pos) const { Q_ASSERT(m_view); + if (m_view->headerBoundaries().contains(pos)) { + return nullptr; + } + const auto widgets = m_view->visibleItemListWidgets(); for (KItemListWidget *widget : widgets) { const QPointF mappedPos = widget->mapFromItem(m_view, pos); @@ -1408,6 +1433,10 @@ KItemListWidget *KItemListController::widgetForDropPos(const QPointF &pos) const { Q_ASSERT(m_view); + if (m_view->headerBoundaries().contains(pos)) { + return nullptr; + } + const auto widgets = m_view->visibleItemListWidgets(); for (KItemListWidget *widget : widgets) { const QPointF mappedPos = widget->mapFromItem(m_view, pos); @@ -1617,12 +1646,6 @@ bool KItemListController::onPress(const QPoint &screenPos, const QPointF &pos, c } if (rightClick) { - // Do header hit check and short circuit before commencing any state changing effects - if (m_view->headerBoundaries().contains(pos)) { - Q_EMIT headerContextMenuRequested(screenPos); - return true; - } - // Stop rubber band from persisting after right-clicks KItemListRubberBand *rubberBand = m_view->rubberBand(); if (rubberBand->isActive()) { @@ -1634,7 +1657,6 @@ bool KItemListController::onPress(const QPoint &screenPos, const QPointF &pos, c if (m_pressedIndex.has_value()) { // The hover highlight area of an item is being pressed. - m_selectionManager->setCurrentItem(m_pressedIndex.value()); 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 const bool hitTargetIsRowEmptyRegion = !row->contains(row->mapFromItem(m_view, pos)); // again, when this method returns false, a rubberBand selection is created as the event is not consumed; @@ -1642,18 +1664,13 @@ bool KItemListController::onPress(const QPoint &screenPos, const QPointF &pos, c bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty()); if (rightClick && hitTargetIsRowEmptyRegion) { - // We have a right click outside the icon and text rect but within the hover highlight area - // but it is unclear if this means that a selection rectangle for an item was clicked or the background of the view. - if (m_selectionManager->selectedItems().contains(m_pressedIndex.value())) { - // The selection rectangle for an item was clicked - Q_EMIT itemContextMenuRequested(m_pressedIndex.value(), screenPos); - } else { - row->setHovered(false); // Removes the hover highlight so the context menu doesn't look like it applies to the row. - Q_EMIT viewContextMenuRequested(screenPos); - } + // We have a right click outside the icon and text rect but within the hover highlight area. + // We don't want items to get selected through this, so we return now. return true; } + m_selectionManager->setCurrentItem(m_pressedIndex.value()); + switch (m_selectionBehavior) { case NoSelection: break; @@ -1690,19 +1707,9 @@ bool KItemListController::onPress(const QPoint &screenPos, const QPointF &pos, c break; } - if (rightClick) { - Q_EMIT itemContextMenuRequested(m_pressedIndex.value(), screenPos); - } return !createRubberBand; } - if (rightClick) { - // header right click handling would have been done before this so just normal context - // menu here is fine - Q_EMIT viewContextMenuRequested(screenPos); - return true; - } - return false; } diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h index 0576fc7fd6..9c3a003d35 100644 --- a/src/kitemviews/kitemlistcontroller.h +++ b/src/kitemviews/kitemlistcontroller.h @@ -24,6 +24,7 @@ class KItemListKeyboardSearchManager; class KItemListSelectionManager; class KItemListView; class KItemListWidget; +class QContextMenuEvent; class QGestureEvent; class QGraphicsSceneHoverEvent; class QGraphicsSceneDragDropEvent; @@ -313,6 +314,7 @@ private: bool mouseMoveEvent(QGraphicsSceneMouseEvent *event, const QTransform &transform); bool mouseReleaseEvent(QGraphicsSceneMouseEvent *event, const QTransform &transform); bool mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event, const QTransform &transform); + bool contextMenuEvent(QContextMenuEvent *event); bool dragEnterEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform); bool dragLeaveEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform); bool dragMoveEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform); diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index cf14836592..89376ab204 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -402,6 +402,10 @@ qreal KItemListView::verticalPageStep() const std::optional KItemListView::itemAt(const QPointF &pos) const { + if (headerBoundaries().contains(pos)) { + return std::nullopt; + } + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next();