Skip to content

Shortcuts_binding

Paweł Salawa edited this page Aug 29, 2022 · 7 revisions

Brief

Shortcuts binding mechanism is mostly based on Model-view binding. It used configuration model to store shortcuts and updates shortcuts across whole application when the configuration dialog is accepted by the user.

Use of Model-view binding ensures that shortcuts are persisted in the configuration file.

Actions added with ExtActionContainer::createAction() are managed by the ExtActionContainer in various ways, one of them being support for configurable shortcuts. It takes care of a problem when you have multiple actions defined with the same key combination - only one action will be triggered (unlike originally in Qt, where all conflicting actions will be triggered alternately, no matter if one shortcut is limited in context or not). To decide which action should be called use shortcut context limitation described in the Additional API section.

To make your own shortcut available (and configurable) in the configuration dialog, you need to define and use the shortcut in the way described below.

Defining configurable shortcuts

  1. Your own class that will use shortcuts has to extend ExtActionContainer class and implement its abstract methods. Implementation details for setupDefShortcuts() and createActions() methods will be specified below.
  2. Declare Actions public enum in your class. For each enum value create action (see next step).
  3. Annotate Actions enum with Q_ENUM() macro.
  4. All actions (that will be used with shortcuts) in the class have to be created using ExtActionContainer::createAction() methods family. Those methods take Action enum value as first argument. All actions should be created in createActions() method implementation.
  5. If particular actions is supposed to be fired by hotkey, it has to be created with "owner" parameter passed to the createAction(). The "owner" should be the class that will receive hotkey event (usually the class that extends ExtActionContainer and at the same time the object that contains toolbar for this action - but that's is not mandatory).
  6. Define shortcuts model using CFG_KEY_LIST and CFG_KEY_ENTRY macros. Names (first argument to CFG_KEY_ENTRY) of particular shortcuts must match enum values specified in the Action enum, otherwise shortcuts will not be linked to their actions.
  7. Implementation of method setupDefShortcuts() needs to call: BIND_SHORTCUTS(shortcuts_model_name, Action); (where "Action" is enum name for actions, defined in step 2).
  8. Add CFG_KEYS_DEFINE(shortcuts_model_name)) macro call in the implementation file (cpp).

Example

Header:

CFG_KEY_LIST(MyShortcuts, QObject::tr("Category title for shortcuts"),
     CFG_KEY_ENTRY(ACTION_1, Qt::Key_F1, QObject::tr("Does action1"))
     CFG_KEY_ENTRY(ACTION_2, Qt::Key_F2, QObject::tr("Does action2"))
)

class MyPluginClass : public GenericPlugin, public GeneralPurposePlugin, public ExtActionContainer
{
        Q_OBJECT
        Q_ENUMS(Action)

    public:
        enum Action
        {
            ACTION_1,
            ACTION_2
        };

        // ...

    private:
        void createActions();
        void setupDefShortcuts();

        // ...

    private slots:
        void doAction1();
        void doAction2();
};

Implementation:

CFG_KEYS_DEFINE(MyShortcuts)

void MyPluginClass::createActions()
{
    createAction(ACTION_1, QIcon(":/myPlugin/action1.png"), tr("Action 1"), this, SLOT(doAction1()), ui->toolBar);
    createAction(ACTION_2, QIcon(":/myPlugin/action2.png"), tr("Action 2"), this, SLOT(doAction2()), ui->toolBar);
}

void MyPluginClass::setupDefShortcuts()
{
    BIND_SHORTCUTS(MyShortcuts, Action);
}

void MyPluginClass::doAction1()
{
    // ...
}

void MyPluginClass::doAction2()
{
    // ...
}

Let's go through the example:

CFG_KEY_LIST(MyShortcuts, QObject::tr("Category title for shortcuts"),
     CFG_KEY_ENTRY(ACTION_1, Qt::Key_F1, QObject::tr("Does action1"))
     CFG_KEY_ENTRY(ACTION_2, Qt::Key_F2, QObject::tr("Does action2"))
)

The code above defines configuration model for our shortcuts. The CFG_KEY_LIST defines shortcuts category. First argument here is our unique name of the category (it will be later used in call to BIND_SHORTCUTS).

Then we define 2 shortcuts. Each call to CFG_KEY_ENTRY takes a name that has to match the name of Action enum value (the enum that is defined later on in the class). If name passed to CFG_KEY_ENTRY and Action enum value do not match, the shortcut will not be linked with the corresponding action. Second argument is the default shortcut value. Third argument is title to be used in configuration dialog for this shortcut. It ususally is the same as the action name itself, but can be different. It may appear to be a redundant naming, but keep in mind, that there can be zero or numerous instances of the action, while we always need exactly one (not less, not more) instances of shortcut object, that will tell its title to the configuration dialog.

**A good practice is to use class name (MyPluginClass in this example) as the name for shortcuts model, but it's not mandatory. In this example we used MyShortcuts for shortcuts model name to demonstrate that this is also possible. **

Let's move to the next part of the code:

class MyPluginClass : public GenericPlugin, public GeneralPurposePlugin, public ExtActionContainer
{
        Q_OBJECT
        Q_ENUMS(Action)

    public:
        enum Action
        {
            ACTION_1,
            ACTION_2
        };

        // ...

    private:
        void createActions();
        void setupDefShortcuts();

        // ...

    private slots:
        void doAction1();
        void doAction2();
};

In this case we're implementing a general purpose plugin. Important fragments are: Q_ENUMS(Action), Action enum and two mentioned private methods. Those methods are declared as pure virtual in the ExtActionContainer. The Action enum should contain actions that you expect to create in this action container. The Q_ENUMS(Action) declaration makes the enum available to Qt's meta subsystem, which is required for the BIND_SHORTCUTS (called later on).

We also declare slots for our actions.

Don't use C++11's "enum class" declaration for the actions enum, because this solution expects implict casting to/from integer for action enum values

Now, the implementation:

CFG_KEYS_DEFINE(MyShortcuts)

This defines instance of the shortcuts model, that we declared in the header earlier.

createAction(ACTION_1, QIcon(":/myPlugin/action1.png"), tr("Action 1"), this, SLOT(doAction1()), ui->toolBar);

This line creates action for ExtActionContainer, assigning it to Action enum value ACTION_1. The action is given the icon, name, trigger signal receiver (this), slot for trigger signal and the owner/container for the action (where the QAction will actually be inserted). The icon path points to the action1.png file in myPlugin prefix of Qt resource file.

The last part is:

void MyPluginClass::setupDefShortcuts()
{
    BIND_SHORTCUTS(MyShortcuts, Action);
}

The BIND_SHORTCUTS goes through all Action enum (or whatever enum name is passed in the second argument) values and matches them with shortcuts defined in the MyShortcuts model (or whatever model is passed in the first argument). When the match is found, the shortcut is assigned to the action for application runtime.

Additional API

Limiting shortcut's context

For some cases it's necessary to declare the shortcut to be limited to some widget or window, because otherwise it's very likely to interfere with the same shortcut value configured for different action, but in different part of the application.

This is for example how the "Esc" key is limited - it does different things depending on where the keyboard focus is currently in. If it's somewhere in the databases list, an action from databases list is fired (clearing database list filter on top). If it's somewhere else, an action from main window is fired (to hide status field).

To limit shortcut context, add following statement to setupDefShortcuts() method implementation:

setShortcutContext({ACTION_1, ACTION_2}, Qt::WidgetWithChildrenShortcut);

First argument is a list (initialized in C++11's fashion) of actions that we're defining limit for.

It takes Qt::ShortcutContext in second argument. The Qt::WidgetWithChildrenShortcut is usual value when you want to limit shortcut range to the current widget, including child widgets.