From e3d44b60a5e68ae509e7daea0de4a8de977d4fed Mon Sep 17 00:00:00 2001 From: Dmitri Ovodok Date: Mon, 20 Jan 2020 11:25:24 +0200 Subject: [PATCH] Plugin API: merge changes from onScoreStateChanged with user action in undo stack --- libmscore/score.cpp | 2 +- libmscore/score.h | 13 ++++++++++++- mscore/musescore.cpp | 6 +++--- mscore/musescore.h | 3 ++- mscore/plugin/api/score.cpp | 22 ++++++++++++++++++++++ mscore/plugin/api/score.h | 2 +- mscore/plugin/qmlpluginengine.cpp | 30 ++++++++++++++++++++++++++---- mscore/plugin/qmlpluginengine.h | 9 ++++++++- 8 files changed, 75 insertions(+), 12 deletions(-) diff --git a/libmscore/score.cpp b/libmscore/score.cpp index a219016834312..da7877d9d419d 100644 --- a/libmscore/score.cpp +++ b/libmscore/score.cpp @@ -818,7 +818,7 @@ bool Score::dirty() const ScoreContentState Score::state() const { - return std::make_pair(this, undoStack()->state()); + return ScoreContentState(this, undoStack()->state()); } //--------------------------------------------------------- diff --git a/libmscore/score.h b/libmscore/score.h index d9a3cdf9d32a7..51b2c89fe952d 100644 --- a/libmscore/score.h +++ b/libmscore/score.h @@ -315,7 +315,18 @@ class UpdateState { // ScoreContentState //--------------------------------------------------------- -typedef std::pair ScoreContentState; +class ScoreContentState { + const Score* score; + int num; + public: + ScoreContentState() : score(nullptr), num(0) {} + ScoreContentState(const Score* s, int stateNum) : score(s), num(stateNum) {} + + bool operator==(const ScoreContentState& s2) const { return score == s2.score && num == s2.num; } + bool operator!=(const ScoreContentState& s2) const { return !(*this == s2); } + + bool isNewerThan(const ScoreContentState& s2) const { return score == s2.score && num > s2.num; } + }; class MasterScore; diff --git a/mscore/musescore.cpp b/mscore/musescore.cpp index 2f1a64590a972..52af058149327 100644 --- a/mscore/musescore.cpp +++ b/mscore/musescore.cpp @@ -4832,7 +4832,7 @@ void MuseScore::undoRedo(bool undo) cv->changeState(ViewState::NORMAL); cv->startUndoRedo(undo); updateInputState(cs); - endCmd(); + endCmd(/* undoRedo */ true); if (pianorollEditor) pianorollEditor->update(); } @@ -5813,10 +5813,10 @@ void MuseScore::cmd(QAction* a) // Updates the UI after a possible score change. //--------------------------------------------------------- -void MuseScore::endCmd() +void MuseScore::endCmd(bool undoRedo) { #ifdef SCRIPT_INTERFACE - getPluginEngine()->beginEndCmd(this); + getPluginEngine()->beginEndCmd(this, undoRedo); #endif if (timeline()) timeline()->updateGrid(); diff --git a/mscore/musescore.h b/mscore/musescore.h index 1bd2430b91d7f..2279c1a0af3dc 100644 --- a/mscore/musescore.h +++ b/mscore/musescore.h @@ -697,7 +697,8 @@ class MuseScore : public QMainWindow, public MuseScoreCore { Q_INVOKABLE void openExternalLink(const QString&); - virtual void endCmd() override; + void endCmd(bool undoRedo); + void endCmd() override { endCmd(false); }; void printFile(); void exportFile(); bool exportParts(); diff --git a/mscore/plugin/api/score.cpp b/mscore/plugin/api/score.cpp index b8843cf15e15c..01ffbe72a62d3 100644 --- a/mscore/plugin/api/score.cpp +++ b/mscore/plugin/api/score.cpp @@ -18,6 +18,9 @@ #include "libmscore/segment.h" #include "libmscore/text.h" +#include "musescore.h" +#include "../qmlpluginengine.h" + namespace Ms { namespace PluginAPI { @@ -118,5 +121,24 @@ Measure* Score::lastMeasureMM() return wrap(score()->lastMeasureMM(), Ownership::SCORE); } +//--------------------------------------------------------- +// Score::startCmd +//--------------------------------------------------------- + +void Score::startCmd() + { + // TODO: should better use qmlEngine(this) (need to set context for wrappers then) + const QmlPluginEngine* engine = mscore->getPluginEngine(); + if (engine->inScoreChangeActionHandler()) { + // Plugin-originated changes made while handling onScoreStateChanged + // should be grouped together with the action which caused this change + // (if it was caused by actual score change). + if (!score()->undoStack()->active()) + score()->undoStack()->reopen(); + } + else { + score()->startCmd(); + } + } } } diff --git a/mscore/plugin/api/score.h b/mscore/plugin/api/score.h index 414bfbb3f7179..32674b8d2b07d 100644 --- a/mscore/plugin/api/score.h +++ b/mscore/plugin/api/score.h @@ -154,7 +154,7 @@ class Score : public Ms::PluginAPI::ScoreElement { * least once by "dock" type plugins in case they * modify the score. */ - Q_INVOKABLE void startCmd() { score()->startCmd(); } + Q_INVOKABLE void startCmd(); /** * For "dock" type plugins: to be used after score * modifications to make them undoable. diff --git a/mscore/plugin/qmlpluginengine.cpp b/mscore/plugin/qmlpluginengine.cpp index 68b0625f8c06d..5f164d544abf7 100644 --- a/mscore/plugin/qmlpluginengine.cpp +++ b/mscore/plugin/qmlpluginengine.cpp @@ -40,22 +40,28 @@ QmlPluginEngine::QmlPluginEngine(QObject* parent) // QmlPluginEngine::beginEndCmd //--------------------------------------------------------- -void QmlPluginEngine::beginEndCmd(MuseScore* ms) +void QmlPluginEngine::beginEndCmd(MuseScore* ms, bool inUndoRedo) { ++cmdCount; + if (inUndoRedo) + undoRedo = true; + + const Score* cs = ms->currentScore(); + currScoreState = cs->masterScore()->state(); // score and excerpts have united undo stack so we are better to track master score + // TODO: most of plugins are never deleted so receivers usually never decrease if (!receivers(SIGNAL(endCmd(const QMap&)))) return; - const Score* cs = ms->currentScore(); - endCmdInfo["selectionChanged"] = !cs || cs->selectionChanged(); endCmdInfo["excerptsChanged"] = !cs || cs->masterScore()->excerptsChanged(); endCmdInfo["instrumentsChanged"] = !cs || cs->masterScore()->instrumentsChanged(); endCmdInfo["startLayoutTick"] = cs ? cs->cmdState().startTick().ticks() : -1; endCmdInfo["endLayoutTick"] = cs ? cs->cmdState().endTick().ticks() : -1; + + endCmdInfo["undoRedo"] = undoRedo; } //--------------------------------------------------------- @@ -73,7 +79,23 @@ void QmlPluginEngine::endEndCmd(MuseScore*) emit endCmd(endCmdInfo); --cmdCount; - if (!cmdCount) + if (!cmdCount) { recursion = false; + undoRedo = false; + lastScoreState = currScoreState; + } + } + +//--------------------------------------------------------- +// QmlPluginEngine::inScoreChangeActionHandler +/// Returns \p true if the engine is in process of +/// handling endCmd() call which is a result of score +/// change user action (not undo/redo or simple selection +/// changes/mouse clicks etc.) +//--------------------------------------------------------- + +bool QmlPluginEngine::inScoreChangeActionHandler() const + { + return cmdCount > 0 && !undoRedo && currScoreState.isNewerThan(lastScoreState); } } diff --git a/mscore/plugin/qmlpluginengine.h b/mscore/plugin/qmlpluginengine.h index 4e16ee4bd7ba5..38e3bbaee7537 100644 --- a/mscore/plugin/qmlpluginengine.h +++ b/mscore/plugin/qmlpluginengine.h @@ -21,6 +21,7 @@ #define __QMLPLUGINENGINE_H__ #include "../qml/msqmlengine.h" +#include "libmscore/score.h" namespace Ms { @@ -36,14 +37,20 @@ class QmlPluginEngine : public MsQmlEngine { QMap endCmdInfo; int cmdCount = 0; bool recursion = false; + bool undoRedo = false; + + ScoreContentState lastScoreState; + ScoreContentState currScoreState; signals: void endCmd(const QMap& changes); public: QmlPluginEngine(QObject* parent = nullptr); - void beginEndCmd(MuseScore*); + void beginEndCmd(MuseScore*, bool undoRedo); void endEndCmd(MuseScore*); + + bool inScoreChangeActionHandler() const; }; } // namespace Ms