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
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<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)
{
}
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<QKeySequence::StandardKey>(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<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
\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<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())
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;
}
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();
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;
}
}

View File

@ -52,17 +52,21 @@
//
#include <QtCore/qobject.h>
#include <QtCore/qvector.h>
#include <QtCore/qvariant.h>
#include <QtGui/qkeysequence.h>
#include <QtQml/qqmlparserstatus.h>
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<Shortcut> m_shortcuts;
};
QT_END_NAMESPACE

View File

@ -123,4 +123,6 @@ void QQuickUtilModule::defineModule()
qmlRegisterType<QQuickShortcut>("QtQuick", 2, 5, "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 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<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)
#include "tst_qquickshortcut.moc"