From 5ca8ac68e26781e63824700edb60f095a15bd6f4 Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Thu, 10 Mar 2022 16:47:34 +0800 Subject: Fix sub-menu shortcuts not being triggered When QQuickShortcutContext::matcher is checking who should get a shortcut, it starts with the object that registered the shortcut and climbs up its parent hierarchy until it finds a window. Sub-menus, unlike top-level menus, will not have an associated window until their parent menu is opened. So, check if the popup is a sub-menu so that actions within it can grab shortcuts. Change-Id: I07172b1038af1e9fffd9983f7bf0a155b11bb1c3 Fixes: QTBUG-101034 Reviewed-by: Richard Moe Gustavsen (cherry picked from commit 51ad5091929c7f2c4bab94fab9fa1c20996a6efa) Reviewed-by: Qt Cherry-pick Bot --- src/quicktemplates2/qquickshortcutcontext.cpp | 14 ++++ .../qquickmenu/data/actionShortcuts.qml | 97 ++++++++++++++++++++++ .../quickcontrols2/qquickmenu/tst_qquickmenu.cpp | 56 +++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 tests/auto/quickcontrols2/qquickmenu/data/actionShortcuts.qml diff --git a/src/quicktemplates2/qquickshortcutcontext.cpp b/src/quicktemplates2/qquickshortcutcontext.cpp index 0c583169bb..70dc912fa8 100644 --- a/src/quicktemplates2/qquickshortcutcontext.cpp +++ b/src/quicktemplates2/qquickshortcutcontext.cpp @@ -40,6 +40,8 @@ #include "qquickshortcutcontext_p_p.h" #include "qquickoverlay_p_p.h" #include "qquicktooltip_p.h" +#include "qquickmenu_p.h" +#include "qquickmenu_p_p.h" #include "qquickpopup_p.h" #include @@ -80,6 +82,18 @@ bool QQuickShortcutContext::matcher(QObject *obj, Qt::ShortcutContext context) } else if (QQuickPopup *popup = qobject_cast(obj)) { obj = popup->window(); item = popup->popupItem(); + + if (!obj) { + // The popup has no associated window (yet). However, sub-menus, + // unlike top-level menus, will not have an associated window + // until their parent menu is opened. So, check if this is a sub-menu + // so that actions within it can grab shortcuts. + if (auto *menu = qobject_cast(popup)) { + auto parentMenu = QQuickMenuPrivate::get(menu)->parentMenu; + while (!obj && parentMenu) + obj = parentMenu->window(); + } + } break; } obj = obj->parent(); diff --git a/tests/auto/quickcontrols2/qquickmenu/data/actionShortcuts.qml b/tests/auto/quickcontrols2/qquickmenu/data/actionShortcuts.qml new file mode 100644 index 0000000000..87ad41c8c5 --- /dev/null +++ b/tests/auto/quickcontrols2/qquickmenu/data/actionShortcuts.qml @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 400 + height: 400 + + property alias menu: menu + property alias subMenu: subMenu + property alias buttonMenu: buttonMenu + + Menu { + id: menu + objectName: "menu" + + Action { + objectName: text + text: "action1" + shortcut: "A" + } + + Menu { + id: subMenu + objectName: "subMenu" + + Action { + objectName: text + text: "subAction1" + shortcut: "B" + } + } + } + + Button { + text: "Menu button" + + Menu { + id: buttonMenu + + Action { + objectName: text + text: "buttonMenuAction1" + shortcut: "C" + } + } + } +} diff --git a/tests/auto/quickcontrols2/qquickmenu/tst_qquickmenu.cpp b/tests/auto/quickcontrols2/qquickmenu/tst_qquickmenu.cpp index bd17b77fd8..6ae8ae38f7 100644 --- a/tests/auto/quickcontrols2/qquickmenu/tst_qquickmenu.cpp +++ b/tests/auto/quickcontrols2/qquickmenu/tst_qquickmenu.cpp @@ -29,6 +29,9 @@ #include #include #include +#if QT_CONFIG(shortcut) +#include +#endif #include #include #include @@ -73,6 +76,9 @@ private slots: void order(); void popup(); void actions(); +#if QT_CONFIG(shortcut) + void actionShortcuts(); +#endif void removeTakeItem(); void subMenuMouse_data(); void subMenuMouse(); @@ -999,6 +1005,56 @@ void tst_QQuickMenu::actions() QVERIFY(menuItem1.isNull()); } +#if QT_CONFIG(shortcut) +void tst_QQuickMenu::actionShortcuts() +{ + QQuickControlsApplicationHelper helper(this, QLatin1String("actionShortcuts.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowActive(window)); + + // Try the menu's shortcut. + QQuickMenu *menu = window->property("menu").value(); + QVERIFY(menu); + QPointer action1 = menu->actionAt(0); + QVERIFY(action1); + QCOMPARE(action1->shortcut(), QKeySequence(Qt::Key_A)); + + QSignalSpy action1TriggeredSpy(action1, SIGNAL(triggered())); + QVERIFY(action1TriggeredSpy.isValid()); + + QTest::keyClick(window, Qt::Key_A); + QCOMPARE(action1TriggeredSpy.count(), 1); + + // Try the sub-menu. + QQuickMenu *subMenu = window->property("subMenu").value(); + QVERIFY(subMenu); + QPointer subMenuAction1 = subMenu->actionAt(0); + QVERIFY(subMenuAction1); + QCOMPARE(subMenuAction1->shortcut(), QKeySequence(Qt::Key_B)); + + QSignalSpy subMenuAction1TriggeredSpy(subMenuAction1, SIGNAL(triggered())); + QVERIFY(subMenuAction1TriggeredSpy.isValid()); + + QTest::keyClick(window, Qt::Key_B); + QCOMPARE(subMenuAction1TriggeredSpy.count(), 1); + + // Try the button menu. + QQuickMenu *buttonMenu = window->property("buttonMenu").value(); + QVERIFY(buttonMenu); + QPointer buttonMenuAction1 = buttonMenu->actionAt(0); + QVERIFY(buttonMenuAction1); + QCOMPARE(buttonMenuAction1->shortcut(), QKeySequence(Qt::Key_C)); + + QSignalSpy buttonMenuAction1TriggeredSpy(buttonMenuAction1, SIGNAL(triggered())); + QVERIFY(buttonMenuAction1TriggeredSpy.isValid()); + + QTest::keyClick(window, Qt::Key_C); + QCOMPARE(buttonMenuAction1TriggeredSpy.count(), 1); +} +#endif + void tst_QQuickMenu::removeTakeItem() { QQuickControlsApplicationHelper helper(this, QLatin1String("removeTakeItem.qml")); -- cgit v1.2.3