diff options
author | Shawn Rutledge <[email protected]> | 2025-04-28 19:09:40 +0200 |
---|---|---|
committer | Shawn Rutledge <[email protected]> | 2025-05-02 07:27:07 +0200 |
commit | 31ca3936d38ecbc3aa93411654099f5a45b16c4f (patch) | |
tree | 9b32f879c957161452ab38105f580798a6543183 | |
parent | cc98876fea54e973f552ec33747c2e2a2fd32e08 (diff) |
TextEdit and TextInput: map QContextMenuEvent to text cursor pos
...if the position is not already set. Events that come from a keyboard
menu key or shortcut often have pos() == {0, 0}, but we want the context
menu to be in the context of what the user is editing.
First though, we need QQuickDeliveryAgentPrivate::contextMenuTargets()
to search for items at the correct position. We don't override delivery
order, but activeFocusItem should be in the list that is returned.
Pick-to: 6.9 6.8
Fixes: QTBUG-136253
Change-Id: I7eea03e118a95a1a267f02bd3385cc1ae4cbb0a0
Reviewed-by: Mitch Curtis <[email protected]>
-rw-r--r-- | src/quick/items/qquicktextedit.cpp | 12 | ||||
-rw-r--r-- | src/quick/items/qquicktextedit_p.h | 3 | ||||
-rw-r--r-- | src/quick/items/qquicktextedit_p_p.h | 3 | ||||
-rw-r--r-- | src/quick/items/qquicktextinput.cpp | 12 | ||||
-rw-r--r-- | src/quick/items/qquicktextinput_p.h | 3 | ||||
-rw-r--r-- | src/quick/items/qquicktextinput_p_p.h | 3 | ||||
-rw-r--r-- | src/quick/util/qquickdeliveryagent.cpp | 5 | ||||
-rw-r--r-- | tests/auto/quickcontrols/qquickcontextmenu/data/textControlsAndParentMenus.qml | 38 | ||||
-rw-r--r-- | tests/auto/quickcontrols/qquickcontextmenu/tst_qquickcontextmenu.cpp | 48 |
9 files changed, 126 insertions, 1 deletions
diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index e6ca6be9cb..9417bb9b3f 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -3316,6 +3316,18 @@ void QQuickTextEdit::focusOutEvent(QFocusEvent *event) QQuickImplicitSizeItem::focusOutEvent(event); } +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) +bool QQuickTextEditPrivate::handleContextMenuEvent(QContextMenuEvent *event) +#else +bool QQuickTextEdit::contextMenuEvent(QContextMenuEvent *event) +#endif +{ + Q_Q(QQuickTextEdit); + QContextMenuEvent mapped(event->reason(), q->cursorRectangle().center().toPoint(), + event->globalPos(), event->modifiers()); + return QQuickItemPrivate::handleContextMenuEvent(&mapped); +} + void QQuickTextEditPrivate::handleFocusEvent(QFocusEvent *event) { Q_Q(QQuickTextEdit); diff --git a/src/quick/items/qquicktextedit_p.h b/src/quick/items/qquicktextedit_p.h index f44b373b5c..b3ea8ea183 100644 --- a/src/quick/items/qquicktextedit_p.h +++ b/src/quick/items/qquicktextedit_p.h @@ -406,6 +406,9 @@ protected: void mouseReleaseEvent(QMouseEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; +#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) + bool contextMenuEvent(QContextMenuEvent *event) override; +#endif #if QT_CONFIG(im) void inputMethodEvent(QInputMethodEvent *e) override; #endif diff --git a/src/quick/items/qquicktextedit_p_p.h b/src/quick/items/qquicktextedit_p_p.h index 0a30117ea2..86a72ab0c4 100644 --- a/src/quick/items/qquicktextedit_p_p.h +++ b/src/quick/items/qquicktextedit_p_p.h @@ -129,6 +129,9 @@ public: #endif void setNativeCursorEnabled(bool) {} +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + bool handleContextMenuEvent(QContextMenuEvent *event) override; +#endif void handleFocusEvent(QFocusEvent *event); void addCurrentTextNodeToRoot(QQuickTextNodeEngine *, QSGTransformNode *, QSGInternalTextNode *, TextNodeIterator&, int startPos); QSGInternalTextNode* createTextNode(); diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp index 1df9df6df5..1b8a8d3612 100644 --- a/src/quick/items/qquicktextinput.cpp +++ b/src/quick/items/qquicktextinput.cpp @@ -1718,6 +1718,18 @@ void QQuickTextInput::mouseReleaseEvent(QMouseEvent *event) QQuickImplicitSizeItem::mouseReleaseEvent(event); } +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) +bool QQuickTextInputPrivate::handleContextMenuEvent(QContextMenuEvent *event) +#else +bool QQuickTextInput::contextMenuEvent(QContextMenuEvent *event) +#endif +{ + Q_Q(QQuickTextInput); + QContextMenuEvent mapped(event->reason(), q->cursorRectangle().center().toPoint(), + event->globalPos(), event->modifiers()); + return QQuickItemPrivate::handleContextMenuEvent(&mapped); +} + bool QQuickTextInputPrivate::sendMouseEventToInputContext(QMouseEvent *event) { #if QT_CONFIG(im) diff --git a/src/quick/items/qquicktextinput_p.h b/src/quick/items/qquicktextinput_p.h index 9eab7d1b80..35f505cbde 100644 --- a/src/quick/items/qquicktextinput_p.h +++ b/src/quick/items/qquicktextinput_p.h @@ -359,6 +359,9 @@ protected: void mouseReleaseEvent(QMouseEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override; void keyPressEvent(QKeyEvent* ev) override; +#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) + bool contextMenuEvent(QContextMenuEvent *event) override; +#endif #if QT_CONFIG(im) void inputMethodEvent(QInputMethodEvent *) override; #endif diff --git a/src/quick/items/qquicktextinput_p_p.h b/src/quick/items/qquicktextinput_p_p.h index 63a8901547..028712abd1 100644 --- a/src/quick/items/qquicktextinput_p_p.h +++ b/src/quick/items/qquicktextinput_p_p.h @@ -150,6 +150,9 @@ public: bool determineHorizontalAlignment(); bool setHAlign(QQuickTextInput::HAlignment, bool forceAlign = false); void mirrorChange() override; +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + bool handleContextMenuEvent(QContextMenuEvent *event) override; +#endif bool sendMouseEventToInputContext(QMouseEvent *event); #if QT_CONFIG(im) Qt::InputMethodHints effectiveInputMethodHints() const; diff --git a/src/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp index 1d6924f198..9301f64204 100644 --- a/src/quick/util/qquickdeliveryagent.cpp +++ b/src/quick/util/qquickdeliveryagent.cpp @@ -2943,7 +2943,10 @@ QVector<QQuickItem *> QQuickDeliveryAgentPrivate::contextMenuTargets(QQuickItem return std::nullopt; }; - return eventTargets(item, event, event->pos(), predicate); + const auto pos = event->pos().isNull() ? activeFocusItem->mapToScene({}).toPoint() : event->pos(); + if (event->pos().isNull()) + qCDebug(lcContextMenu) << "for QContextMenuEvent, active focus item is" << activeFocusItem << "@" << pos; + return eventTargets(item, event, pos, predicate); } /*! diff --git a/tests/auto/quickcontrols/qquickcontextmenu/data/textControlsAndParentMenus.qml b/tests/auto/quickcontrols/qquickcontextmenu/data/textControlsAndParentMenus.qml new file mode 100644 index 0000000000..a22d026e0b --- /dev/null +++ b/tests/auto/quickcontrols/qquickcontextmenu/data/textControlsAndParentMenus.qml @@ -0,0 +1,38 @@ +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: window + width: 600 + height: 400 + + TextArea { + id: textArea + objectName: "textArea" + text: qsTr("Some, well, text here (surprise!)") + width: parent.width + height: parent.height / 3 + } + + TextField { + objectName: "textField" + text: qsTr("A not-so-vast partially-open field") + width: parent.width + y: parent.height * 2 / 3 + } + + contentItem.ContextMenu.menu: Menu { + id: windowMenu + objectName: "windowMenu" + + MenuItem { + text: qsTr("Open window") + } + MenuItem { + text: qsTr("Wash window") + } + MenuItem { + text: qsTr("Admire the view") + } + } +} diff --git a/tests/auto/quickcontrols/qquickcontextmenu/tst_qquickcontextmenu.cpp b/tests/auto/quickcontrols/qquickcontextmenu/tst_qquickcontextmenu.cpp index 40a33b3a35..5b40fc56d6 100644 --- a/tests/auto/quickcontrols/qquickcontextmenu/tst_qquickcontextmenu.cpp +++ b/tests/auto/quickcontrols/qquickcontextmenu/tst_qquickcontextmenu.cpp @@ -37,6 +37,7 @@ private slots: void drawerShouldntPreventOpening(); void explicitMenuPreventsBuiltInMenu(); void menuItemShouldntTriggerOnRelease(); + void textControlsMenuKey(); private: bool contextMenuTriggeredOnRelease = false; @@ -350,6 +351,53 @@ void tst_QQuickContextMenu::menuItemShouldntTriggerOnRelease() // QTBUG-133302 QCOMPARE(triggeredSpy.size(), 0); } +void tst_QQuickContextMenu::textControlsMenuKey() +{ + QQuickApplicationHelper helper(this, "textControlsAndParentMenus.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *textArea = window->findChild<QQuickItem *>("textArea"); + QVERIFY(textArea); + auto *textField = window->findChild<QQuickItem *>("textField"); + QVERIFY(textField); + auto *windowMenu = window->findChild<QQuickMenu *>("windowMenu"); + QVERIFY(windowMenu); + const QPoint &windowCenter = mapCenterToWindow(window->contentItem()); + + // give position in the middle of the window: expect the window menu + { + QContextMenuEvent cme(QContextMenuEvent::Keyboard, windowCenter, window->mapToGlobal(windowCenter)); + QGuiApplication::sendEvent(window, &cme); + auto *openMenu = window->findChild<QQuickMenu *>(); + QVERIFY(openMenu); + QCOMPARE(openMenu->objectName(), "windowMenu"); + openMenu->close(); + } + + // focus the TextArea and give position 0, 0: expect the TextArea's menu + { + textArea->forceActiveFocus(); + QContextMenuEvent cme(QContextMenuEvent::Keyboard, {}, window->mapToGlobal(QPoint())); + QGuiApplication::sendEvent(window, &cme); + auto *openMenu = textArea->findChild<QQuickMenu *>(); + QVERIFY(openMenu); + openMenu->close(); + } + + // focus the TextField and give position 0, 0: expect the TextField's menu + { + textField->forceActiveFocus(); + QContextMenuEvent cme(QContextMenuEvent::Keyboard, {}, window->mapToGlobal(QPoint())); + QGuiApplication::sendEvent(window, &cme); + auto *openMenu = textField->findChild<QQuickMenu *>(); + QVERIFY(openMenu); + openMenu->close(); + } +} + QTEST_QUICKCONTROLS_MAIN(tst_QQuickContextMenu) #include "tst_qquickcontextmenu.moc" |