Actions - musescore/MuseScore GitHub Wiki

Actions

Actions are intentions that are sent from the view (or from somewhere else) to the model. The model (by actions controller) processes the action, changes its state and sends a notification of changes. See interact workflow.

Action

Action consists of a code, that's all

namespace mu::actions {
using ActionCode = std::string;
}

All actions are processed by some specific modules, there are no global actions or actions that do not belong to anyone. Each module can have an action controller to handle the actions associated with that module. Also, some actions can handle the view models of the module (for example, changing the zoom).
Actions can be without arguments (most) and with arguments.
Basically, to add a new action, we just need to register its handler with the action dispatcher.

For example, registering an action:

dispatcher()->reg(this, "do-something", [this]() { ... });

Dispatcher

To send actions, use the Action Dispatcher. At the moment, we can use the dispatcher only in C++ code and we do not plan to add the ability to use the dispatcher in Qml directly. If you want to send an action, when a button is clicked in Qml, then there must be a cpp model, the method of which is called when the button is clicked and an action is dispatched in the method implementation.

Adding a dispatcher dependency:

...
#include "modularity/ioc.h"
#include "actions/iactionsdispatcher.h"
...
class NotationToolBarModel
{
    INJECT(notation_scene, actions::IActionsDispatcher, dispatcher)
    ...
}

Submit Actions

// without arguments
dispatcher()->dispatch("note-input");

// with arguments
dispatcher()->dispatch("put-note", ActionData::make_arg3<QPoint, bool, bool>(logicPos, replace, insert));

Controller

Usually, a module that can handle some kind of actions, have the actions controller.

Controller registers action handlers

void NotationActionController::init()
{
    // without arguments 
    dispatcher()->reg("pad-note-4", [this]() { padNote(Pad::NOTE4); });
    dispatcher()->reg("note-input", this, &NotationActionController::toggleNoteInput);
    ...

    // with argumens
    dispatcher()->reg("put-note", [this](const ActionData& args) { ... });
    dispatcher()->reg("put-note", this, &NotationActionController::putNote);
    ...

    // there are also other possible registration signatures, see IActionsDispatcher
} 

// Where
void NotationActionController::putNote(const actions::ActionData& data)
{
    ...
    QPoint pos = data.arg<QPoint>(0);
    bool replace = data.arg<bool>(1);
    bool insert = data.arg<bool>(2);

    notation->putNote(pos, replace, insert);
}

For a class to register in dispatcher, it must inherit from actions::Actionable

...
#include "actions/actionable.h"
...
class NotationActionController : public actions::Actionable
{
    ...
};

Also, the controller must determine if it is technically possible to handle the action now. For example, if the action depends on the selected notation element, and now nothing is selected, then the action cannot be processed. To do this, the controller must implement the canReceiveAction method For example:

bool NotationActionController::canReceiveAction(const actions::ActionCode& code) const
{
    //! NOTE If the notation is not loaded, we cannot process anything.
    if (!currentNotation()) {
        return false;
    }

    //! NOTE At the moment, if we are in the text editing mode, we can only process the escape
    if (isTextEditting()) {
        return code == ESCAPE_ACTION_CODE;
    }

    if (code == UNDO_ACTION_CODE) {
        return canUndo();
    }

    if (code == REDO_ACTION_CODE) {
        return canRedo();
    }

    ...

    return true;
}

View models can also be action controllers for actions that are associated with these view models (for example, changing the zoom)

⚠️ **GitHub.com Fallback** ⚠️