From 93d7c26653757755ae65f14d06d6df6f9ec2fc07 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sun, 16 Oct 2016 19:21:29 +0200 Subject: QQuickShortcut: allow setting a custom context matcher This allows Qt Quick Controls 2 to register a shortcut context matcher that makes shortcuts behave well with QQC2's item-based popups, which QQuickShortcut is not aware of. Task-number: QTBUG-56562 Change-Id: Ia2518fd6ac7140f60aa38c7d9af557007e9db23b Reviewed-by: Mitch Curtis Reviewed-by: Qt CI Bot --- src/quick/util/qquickshortcut.cpp | 55 ++++++++++++------- .../quick/qquickshortcut/tst_qquickshortcut.cpp | 64 ++++++++++++++++++++++ 2 files changed, 99 insertions(+), 20 deletions(-) diff --git a/src/quick/util/qquickshortcut.cpp b/src/quick/util/qquickshortcut.cpp index 9725ddd671..3e04161639 100644 --- a/src/quick/util/qquickshortcut.cpp +++ b/src/quick/util/qquickshortcut.cpp @@ -41,10 +41,9 @@ #include #include +#include #include -QT_BEGIN_NAMESPACE - /*! \qmltype Shortcut \instantiates QQuickShortcut @@ -89,6 +88,39 @@ QT_BEGIN_NAMESPACE The corresponding handler is \c onActivatedAmbiguously. */ +static bool qQuickShortcutContextMatcher(QObject *obj, Qt::ShortcutContext context) +{ + switch (context) { + case Qt::ApplicationShortcut: + return true; + case Qt::WindowShortcut: + while (obj && !obj->isWindowType()) { + obj = obj->parent(); + if (QQuickItem *item = qobject_cast(obj)) + obj = item->window(); + } + return obj && obj == QGuiApplication::focusWindow(); + default: + return false; + } +} + +typedef bool (*ContextMatcher)(QObject *, Qt::ShortcutContext); + +Q_GLOBAL_STATIC_WITH_ARGS(ContextMatcher, ctxMatcher, (qQuickShortcutContextMatcher)) + +Q_QUICK_PRIVATE_EXPORT ContextMatcher qt_quick_shortcut_context_matcher() +{ + return *ctxMatcher(); +} + +Q_QUICK_PRIVATE_EXPORT void qt_quick_set_shortcut_context_matcher(ContextMatcher matcher) +{ + *ctxMatcher() = matcher; +} + +QT_BEGIN_NAMESPACE + QQuickShortcut::QQuickShortcut(QObject *parent) : QObject(parent), m_id(0), m_enabled(true), m_completed(false), m_autorepeat(true), m_context(Qt::WindowShortcut) { @@ -278,30 +310,13 @@ bool QQuickShortcut::event(QEvent *event) return false; } -static bool qQuickShortcutContextMatcher(QObject *obj, Qt::ShortcutContext context) -{ - switch (context) { - case Qt::ApplicationShortcut: - return true; - case Qt::WindowShortcut: - while (obj && !obj->isWindowType()) { - obj = obj->parent(); - if (QQuickItem *item = qobject_cast(obj)) - obj = item->window(); - } - return obj && obj == QGuiApplication::focusWindow(); - default: - return false; - } -} - void QQuickShortcut::grabShortcut(const QKeySequence &sequence, Qt::ShortcutContext context) { ungrabShortcut(); if (m_completed && !sequence.isEmpty()) { QGuiApplicationPrivate *pApp = QGuiApplicationPrivate::instance(); - m_id = pApp->shortcutMap.addShortcut(this, sequence, context, qQuickShortcutContextMatcher); + m_id = pApp->shortcutMap.addShortcut(this, sequence, context, *ctxMatcher()); if (!m_enabled) pApp->shortcutMap.setShortcutEnabled(false, m_id, this); if (!m_autorepeat) diff --git a/tests/auto/quick/qquickshortcut/tst_qquickshortcut.cpp b/tests/auto/quick/qquickshortcut/tst_qquickshortcut.cpp index 35a1aa8757..2df94bb84a 100644 --- a/tests/auto/quick/qquickshortcut/tst_qquickshortcut.cpp +++ b/tests/auto/quick/qquickshortcut/tst_qquickshortcut.cpp @@ -43,6 +43,8 @@ private slots: void sequence(); void context_data(); void context(); + void matcher_data(); + void matcher(); }; Q_DECLARE_METATYPE(Qt::Key) @@ -344,6 +346,68 @@ void tst_QQuickShortcut::context() || inactiveWindow->property("ambiguousShortcut").toString() == ambiguousShortcut); } +typedef bool (*ShortcutContextMatcher)(QObject *, Qt::ShortcutContext); +extern ShortcutContextMatcher qt_quick_shortcut_context_matcher(); +extern void qt_quick_set_shortcut_context_matcher(ShortcutContextMatcher matcher); + +static ShortcutContextMatcher lastMatcher = nullptr; + +static bool trueMatcher(QObject *, Qt::ShortcutContext) +{ + lastMatcher = trueMatcher; + return true; +} + +static bool falseMatcher(QObject *, Qt::ShortcutContext) +{ + lastMatcher = falseMatcher; + return false; +} + +Q_DECLARE_METATYPE(ShortcutContextMatcher) + +void tst_QQuickShortcut::matcher_data() +{ + QTest::addColumn("matcher"); + QTest::addColumn("key"); + QTest::addColumn("shortcut"); + QTest::addColumn("activatedShortcut"); + + ShortcutContextMatcher tm = trueMatcher; + ShortcutContextMatcher fm = falseMatcher; + + QTest::newRow("F1") << tm << Qt::Key_F1 << shortcutMap("F1", Qt::ApplicationShortcut) << "F1"; + QTest::newRow("F2") << fm << Qt::Key_F2 << shortcutMap("F2", Qt::ApplicationShortcut) << ""; +} + +void tst_QQuickShortcut::matcher() +{ + QFETCH(ShortcutContextMatcher, matcher); + QFETCH(Qt::Key, key); + QFETCH(QVariant, shortcut); + QFETCH(QString, activatedShortcut); + + ShortcutContextMatcher defaultMatcher = qt_quick_shortcut_context_matcher(); + QVERIFY(defaultMatcher); + + qt_quick_set_shortcut_context_matcher(matcher); + QVERIFY(qt_quick_shortcut_context_matcher() == matcher); + + QQmlApplicationEngine engine(testFileUrl("shortcuts.qml")); + QQuickWindow *window = qobject_cast(engine.rootObjects().value(0)); + QVERIFY(window); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + window->setProperty("shortcuts", QVariantList() << shortcut); + QTest::keyClick(window, key); + + QVERIFY(lastMatcher == matcher); + QCOMPARE(window->property("activatedShortcut").toString(), activatedShortcut); + + qt_quick_set_shortcut_context_matcher(defaultMatcher); +} + QTEST_MAIN(tst_QQuickShortcut) #include "tst_qquickshortcut.moc" -- cgit v1.2.3