aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Rutledge <[email protected]>2025-04-28 19:09:40 +0200
committerShawn Rutledge <[email protected]>2025-05-02 07:27:07 +0200
commit31ca3936d38ecbc3aa93411654099f5a45b16c4f (patch)
tree9b32f879c957161452ab38105f580798a6543183
parentcc98876fea54e973f552ec33747c2e2a2fd32e08 (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.cpp12
-rw-r--r--src/quick/items/qquicktextedit_p.h3
-rw-r--r--src/quick/items/qquicktextedit_p_p.h3
-rw-r--r--src/quick/items/qquicktextinput.cpp12
-rw-r--r--src/quick/items/qquicktextinput_p.h3
-rw-r--r--src/quick/items/qquicktextinput_p_p.h3
-rw-r--r--src/quick/util/qquickdeliveryagent.cpp5
-rw-r--r--tests/auto/quickcontrols/qquickcontextmenu/data/textControlsAndParentMenus.qml38
-rw-r--r--tests/auto/quickcontrols/qquickcontextmenu/tst_qquickcontextmenu.cpp48
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"