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); StorageModel *model = new StorageModel(&view);
model->refresh(); 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); QObject::connect(refreshShortcut, &QShortcut::activated, model, &StorageModel::refresh);
view.setModel(model); view.setModel(model);

View File

@ -163,15 +163,23 @@ void QShortcutPrivate::redoGrab(QShortcutMap &map)
return; return;
} }
if (sc_id) for (int id : qAsConst(sc_ids))
map.removeShortcut(sc_id, q); map.removeShortcut(id, q);
if (sc_sequence.isEmpty())
sc_ids.clear();
if (sc_sequences.isEmpty())
return; return;
sc_id = map.addShortcut(q, sc_sequence, sc_context, contextMatcher()); sc_ids.reserve(sc_sequences.count());
if (!sc_enabled) for (const auto &keySequence : qAsConst(sc_sequences)) {
map.setShortcutEnabled(false, sc_id, q); if (keySequence.isEmpty())
if (!sc_autorepeat) continue;
map.setShortcutAutoRepeat(false, sc_id, q); 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 QShortcutPrivate *QGuiApplicationPrivate::createShortcutPrivate() const
@ -210,7 +218,34 @@ QShortcut::QShortcut(const QKeySequence &key, QObject *parent,
{ {
Q_D(QShortcut); Q_D(QShortcut);
d->sc_context = context; 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); d->redoGrab(QGuiApplicationPrivate::instance()->shortcutMap);
if (member) if (member)
connect(this, SIGNAL(activated()), parent, member); connect(this, SIGNAL(activated()), parent, member);
@ -275,19 +310,77 @@ QShortcut::QShortcut(const QKeySequence &key, QObject *parent,
will not be called. 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. Destroys the shortcut.
*/ */
QShortcut::~QShortcut() QShortcut::~QShortcut()
{ {
Q_D(QShortcut); Q_D(QShortcut);
if (qApp) if (qApp) {
QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(d->sc_id, this); for (int id : qAsConst(d->sc_ids))
QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(id, this);
}
} }
/*! /*!
\property QShortcut::key \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, 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: 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) void QShortcut::setKey(const QKeySequence &key)
{ {
Q_D(QShortcut); if (key.isEmpty())
if (d->sc_sequence == key) setKeys({});
return; else
QAPP_CHECK("setKey"); setKeys({ key });
d->sc_sequence = key;
d->redoGrab(QGuiApplicationPrivate::instance()->shortcutMap);
} }
QKeySequence QShortcut::key() const QKeySequence QShortcut::key() const
{ {
Q_D(const QShortcut); 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; return;
QAPP_CHECK("setEnabled"); QAPP_CHECK("setEnabled");
d->sc_enabled = enable; 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 bool QShortcut::isEnabled() const
@ -387,7 +524,8 @@ void QShortcut::setAutoRepeat(bool on)
return; return;
QAPP_CHECK("setAutoRepeat"); QAPP_CHECK("setAutoRepeat");
d->sc_autorepeat = on; 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 bool QShortcut::autoRepeat() const
@ -430,16 +568,20 @@ QString QShortcut::whatsThis() const
return d->sc_whatsthis; 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() \sa QShortcutEvent::shortcutId()
*/ */
int QShortcut::id() const int QShortcut::id() const
{ {
Q_D(const QShortcut); 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 \fn QWidget *QShortcut::parentWidget() const
@ -455,8 +597,8 @@ bool QShortcut::event(QEvent *e)
Q_D(QShortcut); Q_D(QShortcut);
if (d->sc_enabled && e->type() == QEvent::Shortcut) { if (d->sc_enabled && e->type() == QEvent::Shortcut) {
auto se = static_cast<QShortcutEvent *>(e); auto se = static_cast<QShortcutEvent *>(e);
if (se->shortcutId() == d->sc_id && se->key() == d->sc_sequence if (!d->handleWhatsThis()) {
&& !d->handleWhatsThis()) { Q_ASSERT_X(d->sc_ids.contains(se->shortcutId()), "QShortcut::event", "Received shortcut event from wrong shortcut");
if (se->isAmbiguous()) if (se->isAmbiguous())
emit activatedAmbiguously(); emit activatedAmbiguously();
else else

View File

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

View File

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

View File

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

View File

@ -403,7 +403,6 @@ void QProgressDialog::setCancelButton(QPushButton *cancelButton)
if (cancelButton) { if (cancelButton) {
connect(d->cancel, SIGNAL(clicked()), this, SIGNAL(canceled())); connect(d->cancel, SIGNAL(clicked()), this, SIGNAL(canceled()));
#ifndef QT_NO_SHORTCUT #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())); d->escapeShortcut = new QShortcut(QKeySequence::Cancel, this, SIGNAL(canceled()));
#endif #endif
} else { } else {

View File

@ -117,6 +117,7 @@ private slots:
void duplicatedShortcutOverride(); void duplicatedShortcutOverride();
void shortcutToFocusProxy(); void shortcutToFocusProxy();
void deleteLater(); void deleteLater();
void keys();
protected: protected:
static Qt::KeyboardModifiers toButtons( int key ); static Qt::KeyboardModifiers toButtons( int key );
@ -1344,6 +1345,25 @@ void tst_QShortcut::deleteLater()
QTRY_VERIFY(!sc); 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) QTEST_MAIN(tst_QShortcut)
#include "tst_qshortcut.moc" #include "tst_qshortcut.moc"