diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-09-29 13:09:19 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-11-04 10:32:38 +0200 |
commit | b26fa9722f9e8c81406259f6db8044e8bbc2d50b (patch) | |
tree | 30d443ac43663e1670c82621d88052e3be29141b | |
parent | 8fa93272f0a1526e06105ea02c7ae5ca8f8c52c0 (diff) |
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>
-rw-r--r-- | examples/widgets/itemviews/storageview/main.cpp | 2 | ||||
-rw-r--r-- | src/gui/kernel/qshortcut.cpp | 188 | ||||
-rw-r--r-- | src/gui/kernel/qshortcut.h | 69 | ||||
-rw-r--r-- | src/gui/kernel/qshortcut_p.h | 6 | ||||
-rw-r--r-- | src/widgets/dialogs/qfiledialog.cpp | 6 | ||||
-rw-r--r-- | src/widgets/dialogs/qprogressdialog.cpp | 1 | ||||
-rw-r--r-- | tests/auto/widgets/kernel/qshortcut/tst_qshortcut.cpp | 20 |
7 files changed, 259 insertions, 33 deletions
diff --git a/examples/widgets/itemviews/storageview/main.cpp b/examples/widgets/itemviews/storageview/main.cpp index fe916b039d..5505d8a984 100644 --- a/examples/widgets/itemviews/storageview/main.cpp +++ b/examples/widgets/itemviews/storageview/main.cpp @@ -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); diff --git a/src/gui/kernel/qshortcut.cpp b/src/gui/kernel/qshortcut.cpp index b927f2435e..524062ef30 100644 --- a/src/gui/kernel/qshortcut.cpp +++ b/src/gui/kernel/qshortcut.cpp @@ -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); @@ -276,18 +311,76 @@ QShortcut::QShortcut(const QKeySequence &key, QObject *parent, */ /*! + \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) { + if (key.isEmpty()) + setKeys({}); + else + setKeys({ key }); +} + +QKeySequence QShortcut::key() const +{ + Q_D(const QShortcut); + 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_sequence == key) + if (d->sc_sequences == keys) return; - QAPP_CHECK("setKey"); - d->sc_sequence = key; + QAPP_CHECK("setKeys"); + d->sc_sequences = keys; d->redoGrab(QGuiApplicationPrivate::instance()->shortcutMap); } -QKeySequence QShortcut::key() const +/*! + 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_sequence; + 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 diff --git a/src/gui/kernel/qshortcut.h b/src/gui/kernel/qshortcut.h index 1b90a21925..d3db30fd4d 100644 --- a/src/gui/kernel/qshortcut.h +++ b/src/gui/kernel/qshortcut.h @@ -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; diff --git a/src/gui/kernel/qshortcut_p.h b/src/gui/kernel/qshortcut_p.h index 254745960f..f62bab0297 100644 --- a/src/gui/kernel/qshortcut_p.h +++ b/src/gui/kernel/qshortcut_p.h @@ -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); }; diff --git a/src/widgets/dialogs/qfiledialog.cpp b/src/widgets/dialogs/qfiledialog.cpp index adbf655750..ff884d925e 100644 --- a/src/widgets/dialogs/qfiledialog.cpp +++ b/src/widgets/dialogs/qfiledialog.cpp @@ -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 diff --git a/src/widgets/dialogs/qprogressdialog.cpp b/src/widgets/dialogs/qprogressdialog.cpp index 645ca1013a..32834291b8 100644 --- a/src/widgets/dialogs/qprogressdialog.cpp +++ b/src/widgets/dialogs/qprogressdialog.cpp @@ -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 { diff --git a/tests/auto/widgets/kernel/qshortcut/tst_qshortcut.cpp b/tests/auto/widgets/kernel/qshortcut/tst_qshortcut.cpp index 64e054f7f4..3abec08f09 100644 --- a/tests/auto/widgets/kernel/qshortcut/tst_qshortcut.cpp +++ b/tests/auto/widgets/kernel/qshortcut/tst_qshortcut.cpp @@ -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" |