Skip to content
Daniel G. Haas edited this page Jan 7, 2025 · 5 revisions

Interact workflow

In MuseScore 3, most of the interactions between the user and the model are done through commands. There is a list of shortcuts, and actions are created from them (QActions). An action contains a command, and when the action is triggered, the command is first passed to the main window (MuseScore), where it may be processed. The command is then passed to the current view (ScoreView), where it may be processed. Finally, the command is passed to the model (libmscore), where the rest of the commands are processed.

mue_interaction_workflow_mu3

In MuseScore 4, the main window (AppShell) defines only the UI structure of the application and does not contain any action handlers. One new feature in MuseScore 4 is that there may be a model object (notation/libmscore) when the notation (score) view may not be loaded. For example, if we are currently working in the sequencer mode, then the notation (score) cannot process actions. Also, several handlers of the same action may appear in different places, which leads to complexity and code duplication. For these reasons, a different workflow interaction is implemented in MuseScore 4.

mue_interaction_workflow_mu4

This architecture is very similar to Flux architecture

Example of sending and processing actions

Submit action

void NotationToolBarModel::click(const QString& action)
{
    dispatcher()->dispatch(actions::namefromQString(action));
}

Register handlers

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

Handlers

void NotationActionController::toggleNoteInput()
{
    auto notation = currentNotation();
    if (!notation) {
        return;
    }

    if (notation->inputState()->isNoteEnterMode()) {
        notation->endNoteEntry();
    } else {
        notation->startNoteEntry();
    }
}

void NotationActionController::padNote(const Pad& pad)
{
    auto notation = currentNotation();
    if (!notation) {
        return;
    }

    notation->padNote(pad);
}

Send notifications

void Notation::startNoteEntry()
{
    ...
    m_inputStateChanged.notify();
}

void Notation::endNoteEntry()
{
    ...
    m_inputStateChanged.notify();
}

void Notation::padNote(const Pad& pad)
{
    ...
    m_inputStateChanged.notify();
}

Subscription and notification processing

void NotationToolBarModel::onNotationChanged()
{
    std::shared_ptr<INotation> notation = globalContext()->currentNotation();

    //! NOTE Unsubscribe from previous notation, if it was
    m_notationChanged.resetOnNotify(this);
    m_inputStateChanged.resetOnNotify(this);

    if (notation) {
        m_inputStateChanged = notation->inputStateChanged();
        m_inputStateChanged.onNotify(this, [this]() {
            updateState();
        });
    }

    updateState();
}

void NotationPaintView::open()
{
    ...
    m_notation->inputStateChanged().onNotify(this, [this]() {
        onInputStateChanged();
    });
}

Testing

Translation

Compilation

  1. Set up developer environment
  2. Install Qt and Qt Creator
  3. Get MuseScore's source code
  4. Install dependencies
  5. Compile on the command line
  6. Compile in Qt Creator

Beyond compiling

  1. Find your way around the code
  2. Submit a Pull Request
  3. Fix the CI checks

Misc. development

Architecture general

Audio

Engraving

Extensions

Google Summer of Code

References

Clone this wiki locally