3

I have a QAction which I've assigned multiple shortcuts to it

   test = new QAction();
   this->addAction(test);
   QList<QKeySequence> shortcuts;
   shortcuts << QKeySequence(Qt::Key_N) << QKeySequence(Qt::Key_T);
   test->setShortcuts(shortcuts);
   connect(test,SIGNAL(triggered()),this,SLOT(SomeFucntion()))

In SomeFucntion I need to know which shortcut was pressed....Is there anyway of knowing that ?

Tamim Boubou
  • 75
  • 11

2 Answers2

4

You could try a more elaborate pattern with QSignalMapper that avoids the need to define as many actions as many shortcut you need, but requires c++11 (at least this implementation).

In the constructor of your window use the following code to declare your QShortcut objects and a QSignalMapper:

QSignalMapper* signalMapper = new QSignalMapper(this);
QShortcut* sc1 = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_N), this);
QShortcut* sc2 = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_T), this);

connect(sc1, &QShortcut::activated, signalMapper, static_cast<void (QSignalMapper::*)(void)>(&QSignalMapper::map));
connect(sc2, &QShortcut::activated, signalMapper, static_cast<void (QSignalMapper::*)(void)>(&QSignalMapper::map));

signalMapper->setMapping(sc1, sc1);
signalMapper->setMapping(sc2, sc2);

QAction* action = new QAction();
connect(signalMapper, static_cast<void (QSignalMapper::*)(QObject*)>(&QSignalMapper::mapped),
    [action](QObject *object){

    QShortcut* sc = qobject_cast<QShortcut*>(object);
    if (sc)
    {
        action->setData(sc->key().toString());
        action->trigger();
    }
});

connect(action, &QAction::triggered, this, &MainWindow::doStuff);

The 3rd connection is required because of the way QSignalMapper works: when a shortcut is activated, it will be notified to the QSignalMapper thanks to the 1st and 2nd connections, that will trigger the map() slot.

The QSignalMapper::map() slot will scan its mappings, made with the setMapping() API, whose first argument is the mapped object, and the second one is the parameter that will be used to emit the mapped() slot of the QSignalMapper, once the emitting object is identified. To do so, it uses the sender() method and simply compares the pointer returned to the mapped QObject pointers you provided as mappings.

Once the QObject is identified, QSignalMapper will emit the QSignalMapper::mapped(QObject*) signal, whose argument is the second argument given to setMapping, and in this case it the same as the first one, that is again a pointer to the QShortcut that was activated.

I used a lambda to catch this signal, and inside this lambda I simply check that the parameter given is a QShortcut pointer, and store its key sequence inside the data member of the QAction before triggering the action itself. The QAction::trigger() slot will then emit the QAction::triggered() signal that will in turn invoke your custom slot, in this case doStuff(). There you can retrieve the key sequence and do what you want with it.

So your slot implementation should look similar to this one:

void MainWindow::doStuff()
{
    // use sender() to fetch data from action
    QAction* act = qobject_cast<QAction*>(sender());
    if (act)
    {
        QString sequence = act->data().toString();

        // debug output will show you the triggering key sequence
        qDebug() << sequence;

        // use sequence string to determine which shortcut was used

        // On Mike hint: better to reset data after use :)
        act.setData(QVariant());
    }
}

Note that I'm using a mapping based on QObject pointers. In this way you can reuse the signalMapper instance to connect events from other kind of QObjects (e.g. QPushButtons) and identify them in your custom slot as well setting a proper value for the QAction data member, that can store a generic QVariant istance.

Also when using QShortcut, be aware of their contex, that is when they are active, as it could be at widget or a window scope.

Unfortunately this pattern violates pure oop principles, but could be better than managing many actions (icon, text, tooltip etc...) for the same purpose.

EDIT: to answer comments

First of all, let me clarify that you can of course skip the use of QSignalMapper at all. This is just a possible solution (not the better, maybe an overkill... but not really worse in terms of performance).

A simpler way, as pointed by Mike in the comments consists in using lambdas for each QShotcut::activated signal, but this will result in copy/paste code, that I always try to avoid. You can instead define a custom slot inside the MainWindow and use sender() to catch the QShortcut and prepare the action before triggering it.

Anyway, QSignalMapper IMHO, better explains what you are doing (from a semantic point of view) and is more flexible in case you need to expand the connection to other QObjects, supporting also other type of mappings.

Furthermore, but this is related to my personal taste, I like the idea to have fragments of code that are logically tied condensed into small snippets, instead of have it sparse among several slot/functions because it makes it easier to read and to trace back when I need to change it, of course only if this does not hurt the quality of code itself.

Gabriella Giordano
  • 1,188
  • 9
  • 10
  • Thanks...it's working ....although I didn't understand the third "connect" statement very well...is it ok if you explained – Tamim Boubou Jun 07 '18 at 06:56
  • You're welcome. I edited my answer to explain better how it works. Yes it's a long story...I hope it is clearer :) – Gabriella Giordano Jun 07 '18 at 08:01
  • @GabriellaGiordano, Great answer :), Is there any reason why you don't use `QObject::sender()` instead of using `QSignalMapper` to map the object to itself? (or even better just capture whatever you want [using lambdas](https://stackoverflow.com/a/22411267/2666212)). Another small nitpick, you might want to clear `act->data()` after reading it, because `data()` might be otherwise retained when the action gets invoked by means other than these two shortcuts (e.g. another shortcut, or a being pressed from a `QToolBar`/`QMenu`...) – Mike Jun 07 '18 at 08:35
  • @Mike, thanks for your comment. All your observations are correct and I tried to improve my answer with my last edit. Thanks for the hint on the data reset after use. Anyway if you have time why don't you post your answer? I think it could be useful and maybe accepted instead of mine :) – Gabriella Giordano Jun 07 '18 at 09:05
  • @GabriellaGiordano, I just had these two small notes. It is really not worth a new answer. Your answer is great :), I tried to answer this question before you did, but I didn't know about `QShortcut`, so I thought that the only way to go about this if one wanted to keep using one `QAction` is to have some sort of weird event filter... you did quite a better job! :D – Mike Jun 07 '18 at 09:16
  • @Mike ahah so we were both overkilling it a little bit... that's why I have the rule *Check if there is a class for that, darling...* but as you can see sometimes it is not enough... :) Again, thanks for your suggestions. – Gabriella Giordano Jun 07 '18 at 10:05
2

You should create a separate QAction for each shortcut and group them using a QSignalMapper.

Andrii
  • 1,788
  • 11
  • 20