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 <robin.burchell@viroteck.net>
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
This commit is contained in:
J-P Nurmi 2016-10-22 07:31:37 +02:00
parent e21a699dca
commit a7fd83cd0c
5 changed files with 230 additions and 38 deletions

View File

@ -70,6 +70,9 @@
} }
\endqml \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 \sa Keys
*/ */
@ -121,14 +124,23 @@ Q_QUICK_PRIVATE_EXPORT void qt_quick_set_shortcut_context_matcher(ContextMatcher
QT_BEGIN_NAMESPACE 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<QKeySequence::StandardKey>(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) m_enabled(true), m_completed(false), m_autorepeat(true), m_context(Qt::WindowShortcut)
{ {
} }
QQuickShortcut::~QQuickShortcut() QQuickShortcut::~QQuickShortcut()
{ {
ungrabShortcut(); ungrabShortcut(m_shortcut);
for (Shortcut &shortcut : m_shortcuts)
ungrabShortcut(shortcut);
} }
/*! /*!
@ -147,30 +159,78 @@ QQuickShortcut::~QQuickShortcut()
onActivated: edit.wrapMode = TextEdit.Wrap onActivated: edit.wrapMode = TextEdit.Wrap
} }
\endqml \endqml
\sa sequences
*/ */
QVariant QQuickShortcut::sequence() const 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; return;
QKeySequence shortcut; QKeySequence keySequence = valueToKeySequence(value);
if (sequence.type() == QVariant::Int)
shortcut = QKeySequence(static_cast<QKeySequence::StandardKey>(sequence.toInt()));
else
shortcut = QKeySequence::fromString(sequence.toString());
grabShortcut(shortcut, m_context); ungrabShortcut(m_shortcut);
m_shortcut.userValue = value;
m_sequence = sequence; m_shortcut.keySequence = keySequence;
m_shortcut = shortcut; grabShortcut(m_shortcut, m_context);
emit sequenceChanged(); emit sequenceChanged();
} }
/*!
\qmlproperty list<keysequence> 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<Shortcut> 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 \qmlproperty string QtQuick::Shortcut::nativeText
\since 5.6 \since 5.6
@ -184,7 +244,7 @@ void QQuickShortcut::setSequence(const QVariant &sequence)
*/ */
QString QQuickShortcut::nativeText() const 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 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) if (enabled == m_enabled)
return; return;
if (m_id) setEnabled(m_shortcut, enabled);
QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enabled, m_id, this); for (Shortcut &shortcut : m_shortcuts)
setEnabled(shortcut, enabled);
m_enabled = enabled; m_enabled = enabled;
emit enabledChanged(); emit enabledChanged();
@ -243,8 +304,9 @@ void QQuickShortcut::setAutoRepeat(bool repeat)
if (repeat == m_autorepeat) if (repeat == m_autorepeat)
return; return;
if (m_id) setAutoRepeat(m_shortcut, repeat);
QGuiApplicationPrivate::instance()->shortcutMap.setShortcutAutoRepeat(repeat, m_id, this); for (Shortcut &shortcut : m_shortcuts)
setAutoRepeat(shortcut, repeat);
m_autorepeat = repeat; m_autorepeat = repeat;
emit autoRepeatChanged(); emit autoRepeatChanged();
@ -279,9 +341,9 @@ void QQuickShortcut::setContext(Qt::ShortcutContext context)
if (context == m_context) if (context == m_context)
return; return;
grabShortcut(m_shortcut, context); ungrabShortcut(m_shortcut);
m_context = context; m_context = context;
grabShortcut(m_shortcut, context);
emit contextChanged(); emit contextChanged();
} }
@ -293,13 +355,19 @@ void QQuickShortcut::componentComplete()
{ {
m_completed = true; m_completed = true;
grabShortcut(m_shortcut, m_context); grabShortcut(m_shortcut, m_context);
for (Shortcut &shortcut : m_shortcuts)
grabShortcut(shortcut, m_context);
} }
bool QQuickShortcut::event(QEvent *event) bool QQuickShortcut::event(QEvent *event)
{ {
if (m_enabled && event->type() == QEvent::Shortcut) { if (m_enabled && event->type() == QEvent::Shortcut) {
QShortcutEvent *se = static_cast<QShortcutEvent *>(event); QShortcutEvent *se = static_cast<QShortcutEvent *>(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()) if (se->isAmbiguous())
emit activatedAmbiguously(); emit activatedAmbiguously();
else else
@ -310,25 +378,40 @@ bool QQuickShortcut::event(QEvent *event)
return false; 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;
}
if (m_completed && !sequence.isEmpty()) { void QQuickShortcut::setEnabled(QQuickShortcut::Shortcut &shortcut, bool enabled)
{
if (shortcut.id)
QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enabled, shortcut.id, this);
}
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(); 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) if (!m_enabled)
pApp->shortcutMap.setShortcutEnabled(false, m_id, this); pApp->shortcutMap.setShortcutEnabled(false, shortcut.id, this);
if (!m_autorepeat) 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) { if (shortcut.id) {
QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(m_id, this); QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(shortcut.id, this);
m_id = 0; shortcut.id = 0;
} }
} }

View File

@ -52,17 +52,21 @@
// //
#include <QtCore/qobject.h> #include <QtCore/qobject.h>
#include <QtCore/qvector.h>
#include <QtCore/qvariant.h> #include <QtCore/qvariant.h>
#include <QtGui/qkeysequence.h> #include <QtGui/qkeysequence.h>
#include <QtQml/qqmlparserstatus.h> #include <QtQml/qqmlparserstatus.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QShortcutEvent;
class QQuickShortcut : public QObject, public QQmlParserStatus class QQuickShortcut : public QObject, public QQmlParserStatus
{ {
Q_OBJECT Q_OBJECT
Q_INTERFACES(QQmlParserStatus) Q_INTERFACES(QQmlParserStatus)
Q_PROPERTY(QVariant sequence READ sequence WRITE setSequence NOTIFY sequenceChanged FINAL) 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 nativeText READ nativeText NOTIFY sequenceChanged FINAL REVISION 1)
Q_PROPERTY(QString portableText READ portableText 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) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged FINAL)
@ -76,6 +80,9 @@ public:
QVariant sequence() const; QVariant sequence() const;
void setSequence(const QVariant &sequence); void setSequence(const QVariant &sequence);
QVariantList sequences() const;
void setSequences(const QVariantList &sequences);
QString nativeText() const; QString nativeText() const;
QString portableText() const; QString portableText() const;
@ -90,6 +97,7 @@ public:
Q_SIGNALS: Q_SIGNALS:
void sequenceChanged(); void sequenceChanged();
Q_REVISION(9) void sequencesChanged();
void enabledChanged(); void enabledChanged();
void autoRepeatChanged(); void autoRepeatChanged();
void contextChanged(); void contextChanged();
@ -102,17 +110,26 @@ protected:
void componentComplete() Q_DECL_OVERRIDE; void componentComplete() Q_DECL_OVERRIDE;
bool event(QEvent *event) Q_DECL_OVERRIDE; bool event(QEvent *event) Q_DECL_OVERRIDE;
void grabShortcut(const QKeySequence &sequence, Qt::ShortcutContext context); struct Shortcut {
void ungrabShortcut(); 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: private:
int m_id;
bool m_enabled; bool m_enabled;
bool m_completed; bool m_completed;
bool m_autorepeat; bool m_autorepeat;
QKeySequence m_shortcut;
Qt::ShortcutContext m_context; Qt::ShortcutContext m_context;
QVariant m_sequence; Shortcut m_shortcut;
QVector<Shortcut> m_shortcuts;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -123,4 +123,6 @@ void QQuickUtilModule::defineModule()
qmlRegisterType<QQuickShortcut>("QtQuick", 2, 5, "Shortcut"); qmlRegisterType<QQuickShortcut>("QtQuick", 2, 5, "Shortcut");
qmlRegisterType<QQuickShortcut,1>("QtQuick", 2, 6, "Shortcut"); qmlRegisterType<QQuickShortcut,1>("QtQuick", 2, 6, "Shortcut");
qmlRegisterType<QQuickShortcut,9>("QtQuick", 2, 9, "Shortcut");
} }

View File

@ -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
}
}

View File

@ -45,6 +45,8 @@ private slots:
void context(); void context();
void matcher_data(); void matcher_data();
void matcher(); void matcher();
void multiple_data();
void multiple();
}; };
Q_DECLARE_METATYPE(Qt::Key) Q_DECLARE_METATYPE(Qt::Key)
@ -408,6 +410,49 @@ void tst_QQuickShortcut::matcher()
qt_quick_set_shortcut_context_matcher(defaultMatcher); qt_quick_set_shortcut_context_matcher(defaultMatcher);
} }
void tst_QQuickShortcut::multiple_data()
{
QTest::addColumn<QStringList>("sequences");
QTest::addColumn<Qt::Key>("key");
QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
QTest::addColumn<bool>("enabled");
QTest::addColumn<bool>("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<QQuickWindow *>(engine.rootObjects().value(0));
QVERIFY(window);
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window));
QObject *shortcut = window->property("shortcut").value<QObject *>();
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) QTEST_MAIN(tst_QQuickShortcut)
#include "tst_qquickshortcut.moc" #include "tst_qquickshortcut.moc"