From a7fd83cd0cecb789006baecabfc6a49c49b7f48c Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 22 Oct 2016 07:31:37 +0200 Subject: Shortcut: add support for multiple key sequences [ChangeLog][QtQuick][Shortcut] Added support for multiple shortcut sequences. Previously it was possible to specify a single sequence that could consist of up to four key presses. Now it is possible to specify multiple sequences that can each consist of multiple key presses. Change-Id: Id12f25da2f352cc542ec776049d8e81593951d41 Reviewed-by: Robin Burchell Reviewed-by: Mitch Curtis --- src/quick/util/qquickshortcut.cpp | 149 ++++++++++++++++----- src/quick/util/qquickshortcut_p.h | 27 +++- src/quick/util/qquickutilmodule.cpp | 2 + tests/auto/quick/qquickshortcut/data/multiple.qml | 45 +++++++ .../quick/qquickshortcut/tst_qquickshortcut.cpp | 45 +++++++ 5 files changed, 230 insertions(+), 38 deletions(-) create mode 100644 tests/auto/quick/qquickshortcut/data/multiple.qml diff --git a/src/quick/util/qquickshortcut.cpp b/src/quick/util/qquickshortcut.cpp index 3e04161639..a0a58f2e02 100644 --- a/src/quick/util/qquickshortcut.cpp +++ b/src/quick/util/qquickshortcut.cpp @@ -70,6 +70,9 @@ } \endqml + It is also possible to set multiple shortcut \l sequences, so that the shortcut + can be \l activated via several different sequences of key presses. + \sa Keys */ @@ -121,14 +124,23 @@ Q_QUICK_PRIVATE_EXPORT void qt_quick_set_shortcut_context_matcher(ContextMatcher QT_BEGIN_NAMESPACE -QQuickShortcut::QQuickShortcut(QObject *parent) : QObject(parent), m_id(0), +static QKeySequence valueToKeySequence(const QVariant &value) +{ + if (value.type() == QVariant::Int) + return QKeySequence(static_cast(value.toInt())); + return QKeySequence::fromString(value.toString()); +} + +QQuickShortcut::QQuickShortcut(QObject *parent) : QObject(parent), m_enabled(true), m_completed(false), m_autorepeat(true), m_context(Qt::WindowShortcut) { } QQuickShortcut::~QQuickShortcut() { - ungrabShortcut(); + ungrabShortcut(m_shortcut); + for (Shortcut &shortcut : m_shortcuts) + ungrabShortcut(shortcut); } /*! @@ -147,30 +159,78 @@ QQuickShortcut::~QQuickShortcut() onActivated: edit.wrapMode = TextEdit.Wrap } \endqml + + \sa sequences */ QVariant QQuickShortcut::sequence() const { - return m_sequence; + return m_shortcut.userValue; } -void QQuickShortcut::setSequence(const QVariant &sequence) +void QQuickShortcut::setSequence(const QVariant &value) { - if (sequence == m_sequence) + if (value == m_shortcut.userValue) return; - QKeySequence shortcut; - if (sequence.type() == QVariant::Int) - shortcut = QKeySequence(static_cast(sequence.toInt())); - else - shortcut = QKeySequence::fromString(sequence.toString()); + QKeySequence keySequence = valueToKeySequence(value); - grabShortcut(shortcut, m_context); - - m_sequence = sequence; - m_shortcut = shortcut; + ungrabShortcut(m_shortcut); + m_shortcut.userValue = value; + m_shortcut.keySequence = keySequence; + grabShortcut(m_shortcut, m_context); emit sequenceChanged(); } +/*! + \qmlproperty list QtQuick::Shortcut::sequences + \since 5.9 + + This property holds multiple key sequences for the shortcut. The key sequences + can be set to one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts}, + or they can be described with strings containing sequences of up to four key + presses that are needed to \l{Shortcut::activated}{activate} the shortcut. + + \qml + Shortcut { + sequences: [StandardKey.Cut, "Ctrl+X", "Shift+Del"] + onActivated: edit.cut() + } + \endqml +*/ +QVariantList QQuickShortcut::sequences() const +{ + QVariantList values; + for (const Shortcut &shortcut : m_shortcuts) + values += shortcut.userValue; + return values; +} + +void QQuickShortcut::setSequences(const QVariantList &values) +{ + QVector remainder = m_shortcuts.mid(values.count()); + m_shortcuts.resize(values.count()); + + bool changed = !remainder.isEmpty(); + for (int i = 0; i < values.count(); ++i) { + QVariant value = values.at(i); + Shortcut& shortcut = m_shortcuts[i]; + if (value == shortcut.userValue) + continue; + + QKeySequence keySequence = valueToKeySequence(value); + + ungrabShortcut(shortcut); + shortcut.userValue = value; + shortcut.keySequence = keySequence; + grabShortcut(shortcut, m_context); + + changed = true; + } + + if (changed) + emit sequencesChanged(); +} + /*! \qmlproperty string QtQuick::Shortcut::nativeText \since 5.6 @@ -184,7 +244,7 @@ void QQuickShortcut::setSequence(const QVariant &sequence) */ QString QQuickShortcut::nativeText() const { - return m_shortcut.toString(QKeySequence::NativeText); + return m_shortcut.keySequence.toString(QKeySequence::NativeText); } /*! @@ -199,7 +259,7 @@ QString QQuickShortcut::nativeText() const */ QString QQuickShortcut::portableText() const { - return m_shortcut.toString(QKeySequence::PortableText); + return m_shortcut.keySequence.toString(QKeySequence::PortableText); } /*! @@ -219,8 +279,9 @@ void QQuickShortcut::setEnabled(bool enabled) if (enabled == m_enabled) return; - if (m_id) - QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enabled, m_id, this); + setEnabled(m_shortcut, enabled); + for (Shortcut &shortcut : m_shortcuts) + setEnabled(shortcut, enabled); m_enabled = enabled; emit enabledChanged(); @@ -243,8 +304,9 @@ void QQuickShortcut::setAutoRepeat(bool repeat) if (repeat == m_autorepeat) return; - if (m_id) - QGuiApplicationPrivate::instance()->shortcutMap.setShortcutAutoRepeat(repeat, m_id, this); + setAutoRepeat(m_shortcut, repeat); + for (Shortcut &shortcut : m_shortcuts) + setAutoRepeat(shortcut, repeat); m_autorepeat = repeat; emit autoRepeatChanged(); @@ -279,9 +341,9 @@ void QQuickShortcut::setContext(Qt::ShortcutContext context) if (context == m_context) return; - grabShortcut(m_shortcut, context); - + ungrabShortcut(m_shortcut); m_context = context; + grabShortcut(m_shortcut, context); emit contextChanged(); } @@ -293,13 +355,19 @@ void QQuickShortcut::componentComplete() { m_completed = true; grabShortcut(m_shortcut, m_context); + for (Shortcut &shortcut : m_shortcuts) + grabShortcut(shortcut, m_context); } bool QQuickShortcut::event(QEvent *event) { if (m_enabled && event->type() == QEvent::Shortcut) { QShortcutEvent *se = static_cast(event); - if (se->shortcutId() == m_id && se->key() == m_shortcut){ + bool match = m_shortcut.matches(se); + int i = 0; + while (!match && i < m_shortcuts.count()) + match |= m_shortcuts.at(i++).matches(se); + if (match) { if (se->isAmbiguous()) emit activatedAmbiguously(); else @@ -310,25 +378,40 @@ bool QQuickShortcut::event(QEvent *event) return false; } -void QQuickShortcut::grabShortcut(const QKeySequence &sequence, Qt::ShortcutContext context) +bool QQuickShortcut::Shortcut::matches(QShortcutEvent *event) const { - ungrabShortcut(); + return event->shortcutId() == id && event->key() == keySequence; +} + +void QQuickShortcut::setEnabled(QQuickShortcut::Shortcut &shortcut, bool enabled) +{ + if (shortcut.id) + QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enabled, shortcut.id, this); +} - if (m_completed && !sequence.isEmpty()) { +void QQuickShortcut::setAutoRepeat(QQuickShortcut::Shortcut &shortcut, bool repeat) +{ + if (shortcut.id) + QGuiApplicationPrivate::instance()->shortcutMap.setShortcutAutoRepeat(repeat, shortcut.id, this); +} + +void QQuickShortcut::grabShortcut(Shortcut &shortcut, Qt::ShortcutContext context) +{ + if (m_completed && !shortcut.keySequence.isEmpty()) { QGuiApplicationPrivate *pApp = QGuiApplicationPrivate::instance(); - m_id = pApp->shortcutMap.addShortcut(this, sequence, context, *ctxMatcher()); + shortcut.id = pApp->shortcutMap.addShortcut(this, shortcut.keySequence, context, *ctxMatcher()); if (!m_enabled) - pApp->shortcutMap.setShortcutEnabled(false, m_id, this); + pApp->shortcutMap.setShortcutEnabled(false, shortcut.id, this); if (!m_autorepeat) - pApp->shortcutMap.setShortcutAutoRepeat(false, m_id, this); + pApp->shortcutMap.setShortcutAutoRepeat(false, shortcut.id, this); } } -void QQuickShortcut::ungrabShortcut() +void QQuickShortcut::ungrabShortcut(Shortcut &shortcut) { - if (m_id) { - QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(m_id, this); - m_id = 0; + if (shortcut.id) { + QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(shortcut.id, this); + shortcut.id = 0; } } diff --git a/src/quick/util/qquickshortcut_p.h b/src/quick/util/qquickshortcut_p.h index b3f33a33c1..93430ad893 100644 --- a/src/quick/util/qquickshortcut_p.h +++ b/src/quick/util/qquickshortcut_p.h @@ -52,17 +52,21 @@ // #include +#include #include #include #include QT_BEGIN_NAMESPACE +class QShortcutEvent; + class QQuickShortcut : public QObject, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(QVariant sequence READ sequence WRITE setSequence NOTIFY sequenceChanged FINAL) + Q_PROPERTY(QVariantList sequences READ sequences WRITE setSequences NOTIFY sequencesChanged FINAL REVISION 9) Q_PROPERTY(QString nativeText READ nativeText NOTIFY sequenceChanged FINAL REVISION 1) Q_PROPERTY(QString portableText READ portableText NOTIFY sequenceChanged FINAL REVISION 1) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged FINAL) @@ -76,6 +80,9 @@ public: QVariant sequence() const; void setSequence(const QVariant &sequence); + QVariantList sequences() const; + void setSequences(const QVariantList &sequences); + QString nativeText() const; QString portableText() const; @@ -90,6 +97,7 @@ public: Q_SIGNALS: void sequenceChanged(); + Q_REVISION(9) void sequencesChanged(); void enabledChanged(); void autoRepeatChanged(); void contextChanged(); @@ -102,17 +110,26 @@ protected: void componentComplete() Q_DECL_OVERRIDE; bool event(QEvent *event) Q_DECL_OVERRIDE; - void grabShortcut(const QKeySequence &sequence, Qt::ShortcutContext context); - void ungrabShortcut(); + struct Shortcut { + bool matches(QShortcutEvent *event) const; + int id; + QVariant userValue; + QKeySequence keySequence; + }; + + void setEnabled(Shortcut &shortcut, bool enabled); + void setAutoRepeat(Shortcut &shortcut, bool repeat); + + void grabShortcut(Shortcut &shortcut, Qt::ShortcutContext context); + void ungrabShortcut(Shortcut &shortcut); private: - int m_id; bool m_enabled; bool m_completed; bool m_autorepeat; - QKeySequence m_shortcut; Qt::ShortcutContext m_context; - QVariant m_sequence; + Shortcut m_shortcut; + QVector m_shortcuts; }; QT_END_NAMESPACE diff --git a/src/quick/util/qquickutilmodule.cpp b/src/quick/util/qquickutilmodule.cpp index 7e2973a78b..cb586d1565 100644 --- a/src/quick/util/qquickutilmodule.cpp +++ b/src/quick/util/qquickutilmodule.cpp @@ -123,4 +123,6 @@ void QQuickUtilModule::defineModule() qmlRegisterType("QtQuick", 2, 5, "Shortcut"); qmlRegisterType("QtQuick", 2, 6, "Shortcut"); + + qmlRegisterType("QtQuick", 2, 9, "Shortcut"); } diff --git a/tests/auto/quick/qquickshortcut/data/multiple.qml b/tests/auto/quick/qquickshortcut/data/multiple.qml new file mode 100644 index 0000000000..2b58327cf0 --- /dev/null +++ b/tests/auto/quick/qquickshortcut/data/multiple.qml @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.9 +import QtQuick.Window 2.2 + +Window { + id: window + + width: 300 + height: 300 + + property bool activated: false + property alias shortcut: shortcut + + Shortcut { + id: shortcut + onActivated: window.activated = true + } +} diff --git a/tests/auto/quick/qquickshortcut/tst_qquickshortcut.cpp b/tests/auto/quick/qquickshortcut/tst_qquickshortcut.cpp index 2df94bb84a..75ccf26af9 100644 --- a/tests/auto/quick/qquickshortcut/tst_qquickshortcut.cpp +++ b/tests/auto/quick/qquickshortcut/tst_qquickshortcut.cpp @@ -45,6 +45,8 @@ private slots: void context(); void matcher_data(); void matcher(); + void multiple_data(); + void multiple(); }; Q_DECLARE_METATYPE(Qt::Key) @@ -408,6 +410,49 @@ void tst_QQuickShortcut::matcher() qt_quick_set_shortcut_context_matcher(defaultMatcher); } +void tst_QQuickShortcut::multiple_data() +{ + QTest::addColumn("sequences"); + QTest::addColumn("key"); + QTest::addColumn("modifiers"); + QTest::addColumn("enabled"); + QTest::addColumn("activated"); + + // first + QTest::newRow("Ctrl+X,(Shift+Del)") << (QStringList() << "Ctrl+X" << "Shift+Del") << Qt::Key_X << Qt::KeyboardModifiers(Qt::ControlModifier) << true << true; + // second + QTest::newRow("(Ctrl+X),Shift+Del") << (QStringList() << "Ctrl+X" << "Shift+Del") << Qt::Key_Delete << Qt::KeyboardModifiers(Qt::ShiftModifier) << true << true; + // disabled + QTest::newRow("(Ctrl+X,Shift+Del)") << (QStringList() << "Ctrl+X" << "Shift+Del") << Qt::Key_X << Qt::KeyboardModifiers(Qt::ControlModifier) << false << false; +} + +void tst_QQuickShortcut::multiple() +{ + QFETCH(QStringList, sequences); + QFETCH(Qt::Key, key); + QFETCH(Qt::KeyboardModifiers, modifiers); + QFETCH(bool, enabled); + QFETCH(bool, activated); + + QQmlApplicationEngine engine; + + engine.load(testFileUrl("multiple.qml")); + QQuickWindow *window = qobject_cast(engine.rootObjects().value(0)); + QVERIFY(window); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QObject *shortcut = window->property("shortcut").value(); + QVERIFY(shortcut); + + shortcut->setProperty("enabled", enabled); + shortcut->setProperty("sequences", sequences); + + QTest::keyPress(window, key, modifiers); + + QCOMPARE(window->property("activated").toBool(), activated); +} + QTEST_MAIN(tst_QQuickShortcut) #include "tst_qquickshortcut.moc" -- cgit v1.2.3