Add multi key bindings to QShortcut

This makes it feature comparable with QAction, and makes it possible
to use as a backend for QAction, and fixes a few missing alternative
keybindings in qtwidgets.

Change-Id: Iaefc630b96c4743fc5ef429dc841870ddd99fc64
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
This commit is contained in:
Allan Sandfeld Jensen 2020-09-29 13:09:19 +02:00
parent 8fa93272f0
commit b26fa9722f
7 changed files with 261 additions and 35 deletions

View File

@ -66,7 +66,7 @@ int main(int argc, char *argv[])
StorageModel *model = new StorageModel(&view);
model->refresh();
QShortcut *refreshShortcut = new QShortcut(Qt::CTRL | Qt::Key_R, &view);
QShortcut *refreshShortcut = new QShortcut(QKeySequence::Refresh, &view);
QObject::connect(refreshShortcut, &QShortcut::activated, model, &StorageModel::refresh);
view.setModel(model);

View File

@ -163,15 +163,23 @@ void QShortcutPrivate::redoGrab(QShortcutMap &map)
return;
}
if (sc_id)
map.removeShortcut(sc_id, q);
if (sc_sequence.isEmpty())
for (int id : qAsConst(sc_ids))
map.removeShortcut(id, q);
sc_ids.clear();
if (sc_sequences.isEmpty())
return;
sc_id = map.addShortcut(q, sc_sequence, sc_context, contextMatcher());
if (!sc_enabled)
map.setShortcutEnabled(false, sc_id, q);
if (!sc_autorepeat)
map.setShortcutAutoRepeat(false, sc_id, q);
sc_ids.reserve(sc_sequences.count());
for (const auto &keySequence : qAsConst(sc_sequences)) {
if (keySequence.isEmpty())
continue;
int id = map.addShortcut(q, keySequence, sc_context, contextMatcher());
sc_ids.append(id);
if (!sc_enabled)
map.setShortcutEnabled(false, id, q);
if (!sc_autorepeat)
map.setShortcutAutoRepeat(false, id, q);
}
}
QShortcutPrivate *QGuiApplicationPrivate::createShortcutPrivate() const
@ -210,7 +218,34 @@ QShortcut::QShortcut(const QKeySequence &key, QObject *parent,
{
Q_D(QShortcut);
d->sc_context = context;
d->sc_sequence = key;
if (!key.isEmpty()) {
d->sc_sequences = { key };
d->redoGrab(QGuiApplicationPrivate::instance()->shortcutMap);
}
if (member)
connect(this, SIGNAL(activated()), parent, member);
if (ambiguousMember)
connect(this, SIGNAL(activatedAmbiguously()), parent, ambiguousMember);
}
/*!
\since 6.0
Constructs a QShortcut object for the \a parent, which should be a
QWindow or a QWidget.
The shortcut operates on its parent, listening for \l{QShortcutEvent}s that
match the \a standardKey. Depending on the ambiguity of the event, the
shortcut will call the \a member function, or the \a ambiguousMember function,
if the key press was in the shortcut's \a context.
*/
QShortcut::QShortcut(QKeySequence::StandardKey standardKey, QObject *parent,
const char *member, const char *ambiguousMember,
Qt::ShortcutContext context)
: QShortcut(parent)
{
Q_D(QShortcut);
d->sc_context = context;
d->sc_sequences = QKeySequence::keyBindings(standardKey);
d->redoGrab(QGuiApplicationPrivate::instance()->shortcutMap);
if (member)
connect(this, SIGNAL(activated()), parent, member);
@ -275,19 +310,77 @@ QShortcut::QShortcut(const QKeySequence &key, QObject *parent,
will not be called.
*/
/*!
\fn template<typename Functor> QShortcut::QShortcut(QKeySequence::StandardKey key, QObject *parent, Functor functor, Qt::ShortcutContext shortcutContext = Qt::WindowShortcut)
\since 6.0
\overload
This is a QShortcut convenience constructor which connects the shortcut's
\l{QShortcut::activated()}{activated()} signal to the \a functor.
*/
/*!
\fn template<typename Functor> QShortcut::QShortcut(QKeySequence::StandardKey key, QObject *parent, const QObject *context, Functor functor, Qt::ShortcutContext shortcutContext = Qt::WindowShortcut)
\since 6.0
\overload
This is a QShortcut convenience constructor which connects the shortcut's
\l{QShortcut::activated()}{activated()} signal to the \a functor.
The \a functor can be a pointer to a member function of the \a context object.
If the \a context object is destroyed, the \a functor will not be called.
*/
/*!
\fn template<typename Functor, typename FunctorAmbiguous> QShortcut::QShortcut(QKeySequence::StandardKey key, QObject *parent, const QObject *context, Functor functor, FunctorAmbiguous functorAmbiguous, Qt::ShortcutContext shortcutContext = Qt::WindowShortcut)
\since 6.0
\overload
This is a QShortcut convenience constructor which connects the shortcut's
\l{QShortcut::activated()}{activated()} signal to the \a functor and
\l{QShortcut::activatedAmbiguously()}{activatedAmbiguously()}
signal to the \a functorAmbiguous.
The \a functor and \a functorAmbiguous can be a pointer to a member
function of the \a context object.
If the \a context object is destroyed, the \a functor and
\a functorAmbiguous will not be called.
*/
/*!
\fn template<typename Functor, typename FunctorAmbiguous> QShortcut::QShortcut(QKeySequence::StandardKey key, QObject *parent, const QObject *context1, Functor functor, const QObject *context2, FunctorAmbiguous functorAmbiguous, Qt::ShortcutContext shortcutContext = Qt::WindowShortcut)
\since 6.0
\overload
This is a QShortcut convenience constructor which connects the shortcut's
\l{QShortcut::activated()}{activated()} signal to the \a functor and
\l{QShortcut::activatedAmbiguously()}{activatedAmbiguously()}
signal to the \a functorAmbiguous.
The \a functor can be a pointer to a member function of the
\a context1 object.
The \a functorAmbiguous can be a pointer to a member function of the
\a context2 object.
If the \a context1 object is destroyed, the \a functor will not be called.
If the \a context2 object is destroyed, the \a functorAmbiguous
will not be called.
*/
/*!
Destroys the shortcut.
*/
QShortcut::~QShortcut()
{
Q_D(QShortcut);
if (qApp)
QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(d->sc_id, this);
if (qApp) {
for (int id : qAsConst(d->sc_ids))
QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(id, this);
}
}
/*!
\property QShortcut::key
\brief the shortcut's key sequence
\brief the shortcut's primary key sequence
This is a key sequence with an optional combination of Shift, Ctrl,
and Alt. The key sequence may be supplied in a number of ways:
@ -298,18 +391,61 @@ QShortcut::~QShortcut()
*/
void QShortcut::setKey(const QKeySequence &key)
{
Q_D(QShortcut);
if (d->sc_sequence == key)
return;
QAPP_CHECK("setKey");
d->sc_sequence = key;
d->redoGrab(QGuiApplicationPrivate::instance()->shortcutMap);
if (key.isEmpty())
setKeys({});
else
setKeys({ key });
}
QKeySequence QShortcut::key() const
{
Q_D(const QShortcut);
return d->sc_sequence;
if (d->sc_sequences.isEmpty())
return QKeySequence();
return d->sc_sequences.first();
}
/*!
Sets \a keys as the list of key sequences that trigger the
shortcut.
\since 6.0
\sa key, keys()
*/
void QShortcut::setKeys(const QList<QKeySequence> &keys)
{
Q_D(QShortcut);
if (d->sc_sequences == keys)
return;
QAPP_CHECK("setKeys");
d->sc_sequences = keys;
d->redoGrab(QGuiApplicationPrivate::instance()->shortcutMap);
}
/*!
Sets the triggers to those matching the standard key \a key.
\since 6.0
\sa key, keys()
*/
void QShortcut::setKeys(QKeySequence::StandardKey key)
{
setKeys(QKeySequence::keyBindings(key));
}
/*!
Returns the list of key sequences which trigger this
shortcut.
\since 6.0
\sa key, setKeys()
*/
QList<QKeySequence> QShortcut::keys() const
{
Q_D(const QShortcut);
return d->sc_sequences;
}
/*!
@ -334,7 +470,8 @@ void QShortcut::setEnabled(bool enable)
return;
QAPP_CHECK("setEnabled");
d->sc_enabled = enable;
QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enable, d->sc_id, this);
for (int id : d->sc_ids)
QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enable, id, this);
}
bool QShortcut::isEnabled() const
@ -387,7 +524,8 @@ void QShortcut::setAutoRepeat(bool on)
return;
QAPP_CHECK("setAutoRepeat");
d->sc_autorepeat = on;
QGuiApplicationPrivate::instance()->shortcutMap.setShortcutAutoRepeat(on, d->sc_id, this);
for (int id : d->sc_ids)
QGuiApplicationPrivate::instance()->shortcutMap.setShortcutAutoRepeat(on, id, this);
}
bool QShortcut::autoRepeat() const
@ -430,16 +568,20 @@ QString QShortcut::whatsThis() const
return d->sc_whatsthis;
}
#if QT_DEPRECATED_SINCE(6,0)
/*!
Returns the shortcut's ID.
Returns the primary key binding's ID.
\sa QShortcutEvent::shortcutId()
*/
int QShortcut::id() const
{
Q_D(const QShortcut);
return d->sc_id;
if (d->sc_ids.isEmpty())
return 0;
return d->sc_ids.first();
}
#endif
/*!
\fn QWidget *QShortcut::parentWidget() const
@ -455,8 +597,8 @@ bool QShortcut::event(QEvent *e)
Q_D(QShortcut);
if (d->sc_enabled && e->type() == QEvent::Shortcut) {
auto se = static_cast<QShortcutEvent *>(e);
if (se->shortcutId() == d->sc_id && se->key() == d->sc_sequence
&& !d->handleWhatsThis()) {
if (!d->handleWhatsThis()) {
Q_ASSERT_X(d->sc_ids.contains(se->shortcutId()), "QShortcut::event", "Received shortcut event from wrong shortcut");
if (se->isAmbiguous())
emit activatedAmbiguously();
else

View File

@ -64,6 +64,9 @@ public:
explicit QShortcut(const QKeySequence& key, QObject *parent,
const char *member = nullptr, const char *ambiguousMember = nullptr,
Qt::ShortcutContext context = Qt::WindowShortcut);
explicit QShortcut(QKeySequence::StandardKey key, QObject *parent,
const char *member = nullptr, const char *ambiguousMember = nullptr,
Qt::ShortcutContext context = Qt::WindowShortcut);
#ifdef Q_CLANG_QDOC
template<typename Functor>
@ -84,6 +87,25 @@ public:
const QObject *context1, Functor functor,
const QObject *context2, FunctorAmbiguous functorAmbiguous,
Qt::ShortcutContext shortcutContext = Qt::WindowShortcut);
template<typename Functor>
QShortcut(QKeySequence::StandardKey key, QObject *parent,
Functor functor,
Qt::ShortcutContext shortcutContext = Qt::WindowShortcut);
template<typename Functor>
QShortcut(QKeySequence::StandardKey key, QObject *parent,
const QObject *context, Functor functor,
Qt::ShortcutContext shortcutContext = Qt::WindowShortcut);
template<typename Functor, typename FunctorAmbiguous>
QShortcut(QKeySequence::StandardKey key, QObject *parent,
const QObject *context1, Functor functor,
FunctorAmbiguous functorAmbiguous,
Qt::ShortcutContext shortcutContext = Qt::WindowShortcut);
template<typename Functor, typename FunctorAmbiguous>
QShortcut(QKeySequence::StandardKey key, QObject *parent,
const QObject *context1, Functor functor,
const QObject *context2, FunctorAmbiguous functorAmbiguous,
Qt::ShortcutContext shortcutContext = Qt::WindowShortcut);
#else
template<typename Func1>
QShortcut(const QKeySequence &key, QObject *parent,
@ -124,12 +146,55 @@ public:
connect(this, &QShortcut::activated, object1, std::move(slot1));
connect(this, &QShortcut::activatedAmbiguously, object2, std::move(slot2));
}
template<typename Func1>
QShortcut(QKeySequence::StandardKey key, QObject *parent,
Func1 slot1,
Qt::ShortcutContext context = Qt::WindowShortcut)
: QShortcut(key, parent, static_cast<const char*>(nullptr), static_cast<const char*>(nullptr), context)
{
connect(this, &QShortcut::activated, std::move(slot1));
}
template<class Obj1, typename Func1>
QShortcut(QKeySequence::StandardKey key, QObject *parent,
const Obj1 *object1, Func1 slot1,
Qt::ShortcutContext context = Qt::WindowShortcut,
typename std::enable_if<QtPrivate::IsPointerToTypeDerivedFromQObject<Obj1*>::Value>::type* = 0)
: QShortcut(key, parent, static_cast<const char*>(nullptr), static_cast<const char*>(nullptr), context)
{
connect(this, &QShortcut::activated, object1, std::move(slot1));
}
template<class Obj1, typename Func1, typename Func2>
QShortcut(QKeySequence::StandardKey key, QObject *parent,
const Obj1 *object1, Func1 slot1, Func2 slot2,
Qt::ShortcutContext context = Qt::WindowShortcut,
typename std::enable_if<QtPrivate::IsPointerToTypeDerivedFromQObject<Obj1*>::Value>::type* = 0)
: QShortcut(key, parent, static_cast<const char*>(nullptr), static_cast<const char*>(nullptr), context)
{
connect(this, &QShortcut::activated, object1, std::move(slot1));
connect(this, &QShortcut::activatedAmbiguously, object1, std::move(slot2));
}
template<class Obj1, typename Func1, class Obj2, typename Func2>
QShortcut(QKeySequence::StandardKey key, QObject *parent,
const Obj1 *object1, Func1 slot1,
const Obj2 *object2, Func2 slot2,
Qt::ShortcutContext context = Qt::WindowShortcut,
typename std::enable_if<QtPrivate::IsPointerToTypeDerivedFromQObject<Obj1*>::Value>::type* = 0,
typename std::enable_if<QtPrivate::IsPointerToTypeDerivedFromQObject<Obj2*>::Value>::type* = 0)
: QShortcut(key, parent, static_cast<const char*>(nullptr), static_cast<const char*>(nullptr), context)
{
connect(this, &QShortcut::activated, object1, std::move(slot1));
connect(this, &QShortcut::activatedAmbiguously, object2, std::move(slot2));
}
#endif
~QShortcut();
void setKey(const QKeySequence& key);
QKeySequence key() const;
void setKeys(QKeySequence::StandardKey key);
void setKeys(const QList<QKeySequence> &keys);
QList<QKeySequence> keys() const;
void setEnabled(bool enable);
bool isEnabled() const;
@ -140,7 +205,9 @@ public:
void setAutoRepeat(bool on);
bool autoRepeat() const;
int id() const;
#if QT_DEPRECATED_SINCE(6,0)
Q_DECL_DEPRECATED int id() const;
#endif
void setWhatsThis(const QString &text);
QString whatsThis() const;

View File

@ -55,13 +55,13 @@
#include "qshortcut.h"
#include <QtGui/qkeysequence.h>
#include <QtCore/qlist.h>
#include <QtCore/qstring.h>
#include <QtCore/private/qobject_p.h>
#include <private/qshortcutmap_p.h>
QT_BEGIN_NAMESPACE
class QShortcutMap;
@ -79,12 +79,12 @@ public:
virtual QShortcutMap::ContextMatcher contextMatcher() const;
virtual bool handleWhatsThis() { return false; }
QKeySequence sc_sequence;
QList<QKeySequence> sc_sequences;
QString sc_whatsthis;
Qt::ShortcutContext sc_context = Qt::WindowShortcut;
bool sc_enabled = true;
bool sc_autorepeat = true;
int sc_id = 0;
QList<int> sc_ids;
void redoGrab(QShortcutMap &map);
};

View File

@ -3048,8 +3048,7 @@ void QFileDialogPrivate::createWidgets()
QObject::connect(qFileDialogUi->listView, SIGNAL(customContextMenuRequested(QPoint)),
q, SLOT(_q_showContextMenu(QPoint)));
#ifndef QT_NO_SHORTCUT
QShortcut *shortcut = new QShortcut(qFileDialogUi->listView);
shortcut->setKey(QKeySequence(QLatin1String("Delete")));
QShortcut *shortcut = new QShortcut(QKeySequence::Delete, qFileDialogUi->listView);
QObject::connect(shortcut, SIGNAL(activated()), q, SLOT(_q_deleteCurrent()));
#endif
@ -3088,8 +3087,7 @@ void QFileDialogPrivate::createWidgets()
QObject::connect(qFileDialogUi->treeView, SIGNAL(customContextMenuRequested(QPoint)),
q, SLOT(_q_showContextMenu(QPoint)));
#ifndef QT_NO_SHORTCUT
shortcut = new QShortcut(qFileDialogUi->treeView);
shortcut->setKey(QKeySequence(QLatin1String("Delete")));
shortcut = new QShortcut(QKeySequence::Delete, qFileDialogUi->treeView);
QObject::connect(shortcut, SIGNAL(activated()), q, SLOT(_q_deleteCurrent()));
#endif

View File

@ -403,7 +403,6 @@ void QProgressDialog::setCancelButton(QPushButton *cancelButton)
if (cancelButton) {
connect(d->cancel, SIGNAL(clicked()), this, SIGNAL(canceled()));
#ifndef QT_NO_SHORTCUT
// FIXME: This only registers the primary key sequence of the cancel action
d->escapeShortcut = new QShortcut(QKeySequence::Cancel, this, SIGNAL(canceled()));
#endif
} else {

View File

@ -117,6 +117,7 @@ private slots:
void duplicatedShortcutOverride();
void shortcutToFocusProxy();
void deleteLater();
void keys();
protected:
static Qt::KeyboardModifiers toButtons( int key );
@ -1344,6 +1345,25 @@ void tst_QShortcut::deleteLater()
QTRY_VERIFY(!sc);
}
void tst_QShortcut::keys()
{
QLineEdit le;
QShortcut *sc = new QShortcut(QKeySequence::InsertParagraphSeparator, &le);
QVERIFY(sc->keys().contains(QKeySequence(Qt::Key_Enter)));
QVERIFY(sc->keys().contains(QKeySequence(Qt::Key_Return)));
QSignalSpy spy(sc, &QShortcut::activated);
le.setFocus();
le.show();
QVERIFY(QTest::qWaitForWindowActive(&le));
QCOMPARE(QApplication::focusWidget(), &le);
QTest::keyEvent(QTest::Press, QApplication::focusWidget(), Qt::Key_Enter);
QTRY_COMPARE(spy.count(), 1);
QTest::keyEvent(QTest::Press, QApplication::focusWidget(), Qt::Key_Return);
QTRY_COMPARE(spy.count(), 2);
}
QTEST_MAIN(tst_QShortcut)
#include "tst_qshortcut.moc"