From 60db2d63cdb4a1697764281ec2653d3b4a6489cc Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Sun, 17 May 2026 21:57:09 +0200 Subject: [PATCH 01/18] Separate UndoableTransaction (f.k.a. UndoMacro) from UndoCommand --- src/engraving/api/v1/selection.cpp | 6 +- src/engraving/dom/durationelement.cpp | 2 +- src/engraving/editing/addremoveelement.cpp | 54 +-- src/engraving/editing/addremoveelement.h | 6 +- src/engraving/editing/cmd.cpp | 39 +- src/engraving/editing/edit.cpp | 12 +- src/engraving/editing/editproperty.h | 2 +- src/engraving/editing/textedit.cpp | 13 +- src/engraving/editing/textedit.h | 2 +- src/engraving/editing/undo.cpp | 387 +++++++----------- src/engraving/editing/undo.h | 116 +++--- .../devtools/engravingundostackmodel.cpp | 40 +- .../devtools/engravingundostackmodel.h | 4 +- src/engraving/rw/read400/readcontext.cpp | 2 +- src/notation/internal/inotationundostack.h | 2 +- src/notation/internal/notationinteraction.cpp | 2 +- src/notation/internal/notationundostack.cpp | 4 +- src/notation/internal/notationundostack.h | 2 +- .../elementpopups/partialtiepopupmodel.cpp | 2 +- src/notationscene/widgets/editstaff.cpp | 2 +- 20 files changed, 291 insertions(+), 408 deletions(-) diff --git a/src/engraving/api/v1/selection.cpp b/src/engraving/api/v1/selection.cpp index a31786eea5801..d0cbeacbcbd63 100644 --- a/src/engraving/api/v1/selection.cpp +++ b/src/engraving/api/v1/selection.cpp @@ -83,8 +83,8 @@ bool Selection::select(EngravingItem* elWrapper, bool add) mu::engraving::EngravingItem* e = elWrapper->element(); // Check whether it's safe to select this element: - // use types list from UndoMacro for now - if (!mu::engraving::UndoMacro::canRecordSelectedElement(e)) { + // use types list from UndoableTransaction for now + if (!mu::engraving::UndoableTransaction::canRecordSelectedElement(e)) { LOGW("Cannot select element of type %s", e->typeName()); return false; } @@ -136,7 +136,7 @@ bool Selection::selectRange(int startTick, int endTick, int startStaff, int endS return false; } - if (segEnd && m_selection->score()->undoStack()->hasActiveCommand()) { + if (segEnd && m_selection->score()->undoStack()->hasActiveTransaction()) { m_selection->setRangeTicks(segStart->tick(), segEnd->tick(), startStaff, endStaff); } else { m_selection->setRange(segStart, segEnd, startStaff, endStaff); diff --git a/src/engraving/dom/durationelement.cpp b/src/engraving/dom/durationelement.cpp index b7a01e107cf7d..bf25f24408329 100644 --- a/src/engraving/dom/durationelement.cpp +++ b/src/engraving/dom/durationelement.cpp @@ -123,7 +123,7 @@ Fraction DurationElement::actualTicks() const void DurationElement::readAddTuplet(Tuplet* t) { setTuplet(t); - if (!score()->undoStack()->hasActiveCommand()) { // HACK, also added in Undo::AddElement() + if (!score()->undoStack()->hasActiveTransaction()) { // HACK, also added in Undo::AddElement() t->add(this); } } diff --git a/src/engraving/editing/addremoveelement.cpp b/src/engraving/editing/addremoveelement.cpp index 54148628e56fc..ff0223ceef1c8 100644 --- a/src/engraving/editing/addremoveelement.cpp +++ b/src/engraving/editing/addremoveelement.cpp @@ -83,10 +83,6 @@ AddElement::AddElement(EngravingItem* e) element = e; } -//--------------------------------------------------------- -// AddElement::cleanup -//--------------------------------------------------------- - void AddElement::cleanup(bool undo) { if (!undo) { @@ -95,10 +91,6 @@ void AddElement::cleanup(bool undo) } } -//--------------------------------------------------------- -// endUndoRedo -//--------------------------------------------------------- - void AddElement::endUndoRedo(bool isUndo) const { if (element->isChordRest()) { @@ -116,10 +108,6 @@ void AddElement::endUndoRedo(bool isUndo) const } } -//--------------------------------------------------------- -// undo -//--------------------------------------------------------- - void AddElement::undo(EditData*) { Score* score = element->score(); @@ -135,10 +123,6 @@ void AddElement::undo(EditData*) endUndoRedo(true); } -//--------------------------------------------------------- -// redo -//--------------------------------------------------------- - void AddElement::redo(EditData*) { Score* score = element->score(); @@ -154,10 +138,6 @@ void AddElement::redo(EditData*) endUndoRedo(false); } -//--------------------------------------------------------- -// name -//--------------------------------------------------------- - const char* AddElement::name() const { static char buffer[64]; @@ -172,11 +152,7 @@ const char* AddElement::name() const return buffer; } -//--------------------------------------------------------- -// AddElement::isFiltered -//--------------------------------------------------------- - -bool AddElement::isFiltered(UndoCommand::Filter f, const EngravingItem* target) const +bool AddElement::matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const { using Filter = UndoCommand::Filter; switch (f) { @@ -266,10 +242,6 @@ RemoveElement::RemoveElement(EngravingItem* e) } } -//--------------------------------------------------------- -// RemoveElement::cleanup -//--------------------------------------------------------- - void RemoveElement::cleanup(bool undo) { if (undo) { @@ -278,10 +250,6 @@ void RemoveElement::cleanup(bool undo) } } -//--------------------------------------------------------- -// undo -//--------------------------------------------------------- - void RemoveElement::undo(EditData*) { Score* score = element->score(); @@ -307,10 +275,6 @@ void RemoveElement::undo(EditData*) } } -//--------------------------------------------------------- -// redo -//--------------------------------------------------------- - void RemoveElement::redo(EditData*) { Score* score = element->score(); @@ -336,10 +300,6 @@ void RemoveElement::redo(EditData*) } } -//--------------------------------------------------------- -// name -//--------------------------------------------------------- - const char* RemoveElement::name() const { static char buffer[64]; @@ -354,11 +314,7 @@ const char* RemoveElement::name() const return buffer; } -//--------------------------------------------------------- -// RemoveElement::isFiltered -//--------------------------------------------------------- - -bool RemoveElement::isFiltered(UndoCommand::Filter f, const EngravingItem* target) const +bool RemoveElement::matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const { using Filter = UndoCommand::Filter; switch (f) { @@ -516,11 +472,7 @@ Link::Link(EngravingObject* e1, EngravingObject* e2) e = e1; } -//--------------------------------------------------------- -// Link::isFiltered -//--------------------------------------------------------- - -bool Link::isFiltered(UndoCommand::Filter f, const EngravingItem* target) const +bool Link::matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const { using Filter = UndoCommand::Filter; if (f == Filter::Link) { diff --git a/src/engraving/editing/addremoveelement.h b/src/engraving/editing/addremoveelement.h index bb512cccda2a4..a04d6d98caa1d 100644 --- a/src/engraving/editing/addremoveelement.h +++ b/src/engraving/editing/addremoveelement.h @@ -42,7 +42,7 @@ class AddElement : public UndoCommand void cleanup(bool) override; const char* name() const override; - bool isFiltered(UndoCommand::Filter f, const EngravingItem* target) const override; + bool matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const override; std::vector objectItems() const override; @@ -62,7 +62,7 @@ class RemoveElement : public UndoCommand void cleanup(bool) override; const char* name() const override; - bool isFiltered(UndoCommand::Filter f, const EngravingItem* target) const override; + bool matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const override; std::vector objectItems() const override; @@ -166,6 +166,6 @@ class Link : public LinkUnlink UNDO_TYPE(CommandType::Link) UNDO_NAME("Link") - bool isFiltered(UndoCommand::Filter f, const EngravingItem* target) const override; + bool matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const override; }; } diff --git a/src/engraving/editing/cmd.cpp b/src/engraving/editing/cmd.cpp index b54ed539a1dfe..a32345c9ed2a2 100644 --- a/src/engraving/editing/cmd.cpp +++ b/src/engraving/editing/cmd.cpp @@ -101,28 +101,28 @@ using namespace muse::io; using namespace mu::engraving; namespace mu::engraving { -static UndoMacro::ChangesInfo changesInfo(const UndoStack* stack, bool undo = false) +static UndoableTransaction::ChangesInfo changesInfo(const UndoStack* stack, bool undo = false) { IF_ASSERT_FAILED(stack) { - static UndoMacro::ChangesInfo empty; + static UndoableTransaction::ChangesInfo empty; return empty; } - const UndoMacro* actualMacro = stack->activeCommand(); + const UndoableTransaction* transaction = stack->activeTransaction(); - if (!actualMacro) { - actualMacro = stack->last(); + if (!transaction) { + transaction = stack->last(); } - if (!actualMacro) { - static UndoMacro::ChangesInfo empty; + if (!transaction) { + static UndoableTransaction::ChangesInfo empty; return empty; } - return actualMacro->changesInfo(undo); + return transaction->changesInfo(undo); } -static ScoreChanges buildScoreChanges(const CmdState& cmdState, const UndoMacro::ChangesInfo& changes) +static ScoreChanges buildScoreChanges(const CmdState& cmdState, const UndoableTransaction::ChangesInfo& changes) { int startTick = cmdState.startTick().ticks(); int endTick = cmdState.endTick().ticks(); @@ -351,7 +351,7 @@ void Score::startCmd(const TranslatableString& actionName) LOGD("===startCmd()"); } - if (undoStack()->hasActiveCommand()) { + if (undoStack()->hasActiveTransaction()) { LOGD("Score::startCmd(): cmd already active"); return; } @@ -360,9 +360,8 @@ void Score::startCmd(const TranslatableString& actionName) cmdState().reset(); - // Start collecting low-level undo operations for a - // user-visible undo action. - undoStack()->beginMacro(this, actionName); + // Start collecting low-level undoable operations for a user-visible undoable transaction. + undoStack()->beginTransaction(this, actionName); } //--------------------------------------------------------- @@ -378,7 +377,7 @@ void Score::undoRedo(bool undo, EditData* ed) //! NOTE: the order of operations is very important here //! 1. for the undo operation, the list of changed elements is available before undo() //! 2. for the redo operation, the list of changed elements will be available after redo() - UndoMacro::ChangesInfo changes; + UndoableTransaction::ChangesInfo changes; cmdState().reset(); if (undo) { @@ -409,7 +408,7 @@ void Score::endCmd(bool rollback, bool layoutAllParts) return; } - if (!undoStack()->hasActiveCommand()) { + if (!undoStack()->hasActiveTransaction()) { LOGW() << "no command active"; update(); return; @@ -420,7 +419,7 @@ void Score::endCmd(bool rollback, bool layoutAllParts) } if (rollback) { - undoStack()->activeCommand()->unwind(); + undoStack()->activeTransaction()->unwind(); } update(false, layoutAllParts); @@ -430,10 +429,10 @@ void Score::endCmd(bool rollback, bool layoutAllParts) changes = buildScoreChanges(cmdState(), changesInfo(undoStack())); } - LOGD() << "Undo stack current macro child count: " << undoStack()->activeCommand()->childCount(); + LOGD() << "Undo stack current transaction commands count: " << undoStack()->activeTransaction()->commands().size(); - const bool isCurrentCommandEmpty = undoStack()->activeCommand()->empty(); // nothing to undo? - undoStack()->endMacro(isCurrentCommandEmpty); + const bool isCurrentTransactionEmpty = undoStack()->activeTransaction()->empty(); // nothing to undo? + undoStack()->endTransaction(isCurrentTransactionEmpty); if (dirty()) { masterScore()->setPlaylistDirty(); // TODO: flag individual operations @@ -441,7 +440,7 @@ void Score::endCmd(bool rollback, bool layoutAllParts) cmdState().reset(); - if (!isCurrentCommandEmpty && !rollback) { + if (!isCurrentTransactionEmpty && !rollback) { changesChannel().send(changes); } } diff --git a/src/engraving/editing/edit.cpp b/src/engraving/editing/edit.cpp index 07bb8e7cf4189..810b5df9d820b 100644 --- a/src/engraving/editing/edit.cpp +++ b/src/engraving/editing/edit.cpp @@ -1550,7 +1550,7 @@ void Score::cmdAddTimeSig(Measure* fm, staff_idx_t staffIdx, TimeSig* ts, bool l for (size_t i = 0; i < nstaves(); ++i) { if (staff(i)->timeSig(tick) && staff(i)->timeSig(tick)->isLocal()) { if (!mScore->rewriteMeasures(mf, ns, i)) { - undoStack()->activeCommand()->unwind(); + undoStack()->activeTransaction()->unwind(); return; } } @@ -1566,7 +1566,7 @@ void Score::cmdAddTimeSig(Measure* fm, staff_idx_t staffIdx, TimeSig* ts, bool l auto staffIdxRangeOnMaster = getStaffIdxRange(mScore); if (staffIdxRangeOnMaster.second != staffIdxRangeOnMaster.first && !mScore->rewriteMeasures(mf, ns, local ? staffIdxRangeOnMaster.first : muse::nidx)) { - undoStack()->activeCommand()->unwind(); + undoStack()->activeTransaction()->unwind(); return; } } @@ -1661,7 +1661,7 @@ void Score::cmdRemoveTimeSig(TimeSig* ts) Fraction ns(pm ? pm->timesig() : Fraction(4, 4)); if (!rScore->rewriteMeasures(rm, ns, muse::nidx)) { - undoStack()->activeCommand()->unwind(); + undoStack()->activeTransaction()->unwind(); } else { m = tick2measure(tick); // old m may have been replaced // hack: fix measure rest durations for staves with local time signatures @@ -7554,7 +7554,7 @@ void Score::doUndoRemoveStaleTieJumpPoints(Tie* tie, bool undo) // These changes should be merged with the change in repeat structure which caused the ties to become invalid // currentIndex returns the next empty index for an undo command const size_t penultimateCmdIdx = undoStack()->currentIndex() - 2; - undoStack()->mergeCommands(penultimateCmdIdx); + undoStack()->mergeTransactions(penultimateCmdIdx); } } else { removeElement(tie); @@ -7631,7 +7631,7 @@ void Score::doUndoResetPartialSlur(Slur* slur, bool undo) // These changes should be merged with the change in repeat structure which caused the ties to become invalid // currentIdx returns the next empty index for an undo command const size_t penultimateCmdIdx = undoStack()->currentIndex() - 2; - undoStack()->mergeCommands(penultimateCmdIdx); + undoStack()->mergeTransactions(penultimateCmdIdx); } } @@ -7699,7 +7699,7 @@ void Score::undoRemoveStaleTieJumpPoints(bool undo) undoRemoveElement(incomingPT); endCmd(); if (undoIdx != undoStack()->currentIndex() && undoStack()->currentIndex() >= 2) { - undoStack()->mergeCommands(undoStack()->currentIndex() - 2); + undoStack()->mergeTransactions(undoStack()->currentIndex() - 2); } } else { removeElement(incomingPT); diff --git a/src/engraving/editing/editproperty.h b/src/engraving/editing/editproperty.h index 19a6d758fe7a2..83a378b9437de 100644 --- a/src/engraving/editing/editproperty.h +++ b/src/engraving/editing/editproperty.h @@ -51,7 +51,7 @@ class ChangeProperty : public UndoCommand std::vector objectItems() const override; - bool isFiltered(UndoCommand::Filter f, const EngravingItem* target) const override + bool matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const override { return f == UndoCommand::Filter::ChangePropertyLinked && muse::contains(target->linkList(), element); } diff --git a/src/engraving/editing/textedit.cpp b/src/engraving/editing/textedit.cpp index 86b10774b159f..3fa85a5aadae3 100644 --- a/src/engraving/editing/textedit.cpp +++ b/src/engraving/editing/textedit.cpp @@ -23,6 +23,7 @@ #include "textedit.h" #include "mscoreview.h" +#include "undo.h" #include "iengravingfont.h" #include "types/symnames.h" @@ -111,7 +112,7 @@ void TextBase::startEdit(EditData& ed) ted->e = this; ted->cursor()->startEdit(); - assert(!score()->undoStack()->hasActiveCommand()); // make sure we are not in a Cmd + assert(!score()->undoStack()->hasActiveTransaction()); ted->oldXmlText = xmlText(); ted->startUndoIdx = score()->undoStack()->currentIndex(); @@ -161,8 +162,8 @@ void TextBase::endEdit(EditData& ed) //! undo (the "add element" command will have been popped from the stack before the calling of this method)... const bool textWasEdited = undo->currentIndex() > ted->startUndoIdx; if (textWasEdited) { - undo->mergeCommands(ted->startUndoIdx); - undo->last()->filterChildren(Filter::TextEdit, this); + undo->mergeTransactions(ted->startUndoIdx); + undo->last()->removeCommandsMatchingFilter(Filter::TextEdit, this); } else { // No text changes in "undo" part of undo stack, // hence nothing to merge and filter. @@ -176,11 +177,11 @@ void TextBase::endEdit(EditData& ed) const bool newlyAdded = ted->oldXmlText.isEmpty(); if (newlyAdded) { - UndoCommand* ucmd = textWasEdited ? undo->prev() : undo->last(); - if (ucmd && ucmd->hasFilteredChildren(Filter::AddElement, this)) { + const UndoableTransaction* transaction = textWasEdited ? undo->prev() : undo->last(); + if (transaction && transaction->hasCommandsMatchingFilter(Filter::AddElement, this)) { // We have just added this element to a score. // Combine undo records of text creation with text editing. - undo->mergeCommands(ted->startUndoIdx - 1); + undo->mergeTransactions(ted->startUndoIdx - 1); } } diff --git a/src/engraving/editing/textedit.h b/src/engraving/editing/textedit.h index d33025e3f1b59..0c15723d1b6ef 100644 --- a/src/engraving/editing/textedit.h +++ b/src/engraving/editing/textedit.h @@ -77,7 +77,7 @@ class TextEditUndoCommand : public UndoCommand TextEditUndoCommand(const TextCursor& tc) : m_cursor(tc) {} - bool isFiltered(UndoCommand::Filter f, const EngravingItem* target) const override + bool matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const override { return f == UndoCommand::Filter::TextEdit && m_cursor.text() == target; } diff --git a/src/engraving/editing/undo.cpp b/src/engraving/editing/undo.cpp index 8f46bd982648e..65492cf797720 100644 --- a/src/engraving/editing/undo.cpp +++ b/src/engraving/editing/undo.cpp @@ -125,157 +125,36 @@ void updateStaffTextCache(const StaffTextBase* text, Score* score) } } -//--------------------------------------------------------- -// UndoCommand -//--------------------------------------------------------- - -UndoCommand::~UndoCommand() -{ - for (auto c : m_childCommands) { - delete c; - } -} - -//--------------------------------------------------------- -// UndoCommand::cleanup -//--------------------------------------------------------- - -void UndoCommand::cleanup(bool undo) -{ - for (auto c : m_childCommands) { - c->cleanup(undo); - } -} - -//--------------------------------------------------------- -// undo -//--------------------------------------------------------- - void UndoCommand::undo(EditData* ed) { - for (auto it = m_childCommands.rbegin(); it != m_childCommands.rend(); ++it) { - LOG_UNDO() << "<" << (*it)->name() << ">"; - (*it)->undo(ed); - } flip(ed); } -//--------------------------------------------------------- -// redo -//--------------------------------------------------------- - void UndoCommand::redo(EditData* ed) { - for (UndoCommand* c : m_childCommands) { - LOG_UNDO() << "<" << c->name() << ">"; - c->redo(ed); - } flip(ed); } -//--------------------------------------------------------- -// appendChildren -/// Append children of \p other into this UndoCommand. -/// Ownership over child commands of \p other is -/// transferred to this UndoCommand. -//--------------------------------------------------------- - -void UndoCommand::appendChildren(UndoCommand& other) -{ - m_childCommands.insert(m_childCommands.end(), other.m_childCommands.cbegin(), other.m_childCommands.cend()); - other.m_childCommands.clear(); -} - -//--------------------------------------------------------- -// hasFilteredChildren -//--------------------------------------------------------- - -bool UndoCommand::hasFilteredChildren(UndoCommand::Filter f, const EngravingItem* target) const -{ - for (UndoCommand* cmd : m_childCommands) { - if (cmd->isFiltered(f, target)) { - return true; - } - } - return false; -} - -//--------------------------------------------------------- -// hasUnfilteredChildren -//--------------------------------------------------------- - -bool UndoCommand::hasUnfilteredChildren(const std::vector& filters, const EngravingItem* target) const -{ - for (UndoCommand* cmd : m_childCommands) { - bool filtered = false; - for (UndoCommand::Filter f : filters) { - if (cmd->isFiltered(f, target)) { - filtered = true; - break; - } - } - if (!filtered) { - return true; - } - } - return false; -} - -//--------------------------------------------------------- -// filterChildren -//--------------------------------------------------------- - -void UndoCommand::filterChildren(UndoCommand::Filter f, EngravingItem* target) -{ - std::vector acceptedList; - for (UndoCommand* cmd : m_childCommands) { - if (cmd->isFiltered(f, target)) { - delete cmd; - } else { - acceptedList.push_back(cmd); - } - } - m_childCommands = std::move(acceptedList); -} - -//--------------------------------------------------------- -// unwind -//--------------------------------------------------------- - -void UndoCommand::unwind() -{ - while (!m_childCommands.empty()) { - UndoCommand* c = muse::takeLast(m_childCommands); - LOG_UNDO() << "unwind: " << c->name(); - c->undo(nullptr); - delete c; - } -} - //--------------------------------------------------------- // UndoStack //--------------------------------------------------------- UndoStack::UndoStack() { - m_activeCommand = nullptr; + m_activeTransaction = nullptr; m_currentIndex = 0; m_cleanState = 0; - m_stateList.push_back(m_cleanState); + m_states.push_back(m_cleanState); m_nextState = 1; } -//--------------------------------------------------------- -// UndoStack -//--------------------------------------------------------- - UndoStack::~UndoStack() { size_t idx = 0; - for (auto c : m_macroList) { + for (auto c : m_transactions) { c->cleanup(idx++ < m_currentIndex); } - muse::DeleteAll(m_macroList); + muse::DeleteAll(m_transactions); } bool UndoStack::isLocked() const @@ -288,33 +167,26 @@ void UndoStack::setLocked(bool locked) m_isLocked = locked; } -//--------------------------------------------------------- -// beginMacro -//--------------------------------------------------------- - -void UndoStack::beginMacro(Score* score, const TranslatableString& actionName) +void UndoStack::beginTransaction(Score* score, const TranslatableString& actionName) { if (m_isLocked) { return; } - if (m_activeCommand) { - LOGW("already active"); + IF_ASSERT_FAILED(!m_activeTransaction) { + LOGE() << "Transaction already in progress"; return; } - m_activeCommand = new UndoMacro(score, actionName); -} -//--------------------------------------------------------- -// push -//--------------------------------------------------------- + m_activeTransaction = new UndoableTransaction(score, actionName); +} void UndoStack::pushAndPerform(UndoCommand* cmd, EditData* ed) { - if (!m_activeCommand) { - // this can happen for layout() outside of a command (load) + if (!m_activeTransaction) { + // this can happen for layout() outside of a transaction (load) if (!ScoreLoad::loading()) { - LOG_UNDO() << "no active command, UndoStack"; + LOG_UNDO() << "no active transaction, UndoStack"; } cmd->redo(ed); @@ -329,126 +201,103 @@ void UndoStack::pushAndPerform(UndoCommand* cmd, EditData* ed) LOG_UNDO() << cmd->name(); } #endif - m_activeCommand->appendChild(cmd); + m_activeTransaction->appendCommand(cmd); cmd->redo(ed); } -//--------------------------------------------------------- -// push1 -//--------------------------------------------------------- - void UndoStack::pushWithoutPerforming(UndoCommand* cmd) { - if (!m_activeCommand) { + if (!m_activeTransaction) { if (!ScoreLoad::loading()) { - LOGW("no active command, UndoStack %p", this); + LOGW("no active transaction, UndoStack %p", this); } return; } - m_activeCommand->appendChild(cmd); + m_activeTransaction->appendCommand(cmd); } -//--------------------------------------------------------- -// remove -//--------------------------------------------------------- - void UndoStack::remove(size_t idx) { assert(idx <= m_currentIndex); assert(m_currentIndex != muse::nidx); // remove redo stack - while (m_macroList.size() > m_currentIndex) { - UndoCommand* cmd = muse::takeLast(m_macroList); - m_stateList.pop_back(); - cmd->cleanup(false); // delete elements for which UndoCommand() holds ownership - delete cmd; + while (m_transactions.size() > m_currentIndex) { + UndoableTransaction* transaction = muse::takeLast(m_transactions); + m_states.pop_back(); + transaction->cleanup(false); // delete elements for which UndoCommand() holds ownership + delete transaction; // --curIdx; } - while (m_macroList.size() > idx) { - UndoCommand* cmd = muse::takeLast(m_macroList); - m_stateList.pop_back(); - cmd->cleanup(true); - delete cmd; + while (m_transactions.size() > idx) { + UndoableTransaction* transaction = muse::takeLast(m_transactions); + m_states.pop_back(); + transaction->cleanup(true); + delete transaction; } m_currentIndex = idx; } -//--------------------------------------------------------- -// mergeCommands -//--------------------------------------------------------- - -void UndoStack::mergeCommands(size_t startIdx) +void UndoStack::mergeTransactions(size_t startIdx) { assert(startIdx <= m_currentIndex); - if (startIdx >= m_macroList.size()) { + if (startIdx >= m_transactions.size()) { return; } - UndoMacro* startMacro = m_macroList[startIdx]; + UndoableTransaction* startTransaction = m_transactions[startIdx]; for (size_t idx = startIdx + 1; idx < m_currentIndex; ++idx) { - startMacro->append(std::move(*m_macroList[idx])); + startTransaction->append(std::move(*m_transactions[idx])); } remove(startIdx + 1); // TODO: remove from startIdx to curIdx only } -//--------------------------------------------------------- -// endMacro -//--------------------------------------------------------- - -void UndoStack::endMacro(bool rollback) +void UndoStack::endTransaction(bool rollback) { if (m_isLocked) { return; } - if (m_activeCommand == nullptr) { - LOGW("not active"); + IF_ASSERT_FAILED(m_activeTransaction) { + LOGE() << "No transaction in progress"; return; } + if (rollback) { - delete m_activeCommand; + delete m_activeTransaction; } else { // remove redo stack - while (m_macroList.size() > m_currentIndex) { - UndoCommand* cmd = muse::takeLast(m_macroList); - m_stateList.pop_back(); - cmd->cleanup(false); // delete elements for which UndoCommand() holds ownership - delete cmd; + while (m_transactions.size() > m_currentIndex) { + UndoableTransaction* transaction = muse::takeLast(m_transactions); + m_states.pop_back(); + transaction->cleanup(false); // delete elements for which UndoCommand() holds ownership + delete transaction; } - m_macroList.push_back(m_activeCommand); - m_stateList.push_back(m_nextState++); + m_transactions.push_back(m_activeTransaction); + m_states.push_back(m_nextState++); ++m_currentIndex; } - m_activeCommand = nullptr; + m_activeTransaction = nullptr; } -//--------------------------------------------------------- -// reopen -//--------------------------------------------------------- - void UndoStack::reopen() { if (m_isLocked) { return; } - LOG_UNDO() << "curIdx: " << m_currentIndex << ", size: " << m_macroList.size(); - assert(m_activeCommand == nullptr); + LOG_UNDO() << "curIdx: " << m_currentIndex << ", size: " << m_transactions.size(); + assert(m_activeTransaction == nullptr); assert(m_currentIndex > 0); --m_currentIndex; - m_activeCommand = muse::takeAt(m_macroList, m_currentIndex); - m_stateList.erase(m_stateList.begin() + m_currentIndex); - for (auto i : m_activeCommand->commands()) { + m_activeTransaction = muse::takeAt(m_transactions, m_currentIndex); + m_states.erase(m_states.begin() + m_currentIndex); + for (auto i : m_activeTransaction->commands()) { LOG_UNDO() << " " << i->name(); } } -//--------------------------------------------------------- -// undo -//--------------------------------------------------------- - void UndoStack::undo(EditData* ed) { LOG_UNDO() << "called"; @@ -462,28 +311,101 @@ void UndoStack::undo(EditData* ed) } if (m_currentIndex) { --m_currentIndex; - assert(m_currentIndex < m_macroList.size()); - m_macroList[m_currentIndex]->undo(ed); + assert(m_currentIndex < m_transactions.size()); + m_transactions[m_currentIndex]->undo(ed); } } -//--------------------------------------------------------- -// redo -//--------------------------------------------------------- - void UndoStack::redo(EditData* ed) { LOG_UNDO() << "called"; if (canRedo()) { - m_macroList[m_currentIndex++]->redo(ed); + m_transactions[m_currentIndex++]->redo(ed); } } //--------------------------------------------------------- -// UndoMacro +// UndoableTransaction //--------------------------------------------------------- -bool UndoMacro::canRecordSelectedElement(const EngravingItem* e) +UndoableTransaction::UndoableTransaction(Score* s, const TranslatableString& actionName) + : m_undoInputState(s->inputState()), m_actionName(actionName), m_score(s) +{ + fillSelectionInfo(m_undoSelectionInfo, s->selection()); +} + +UndoableTransaction::~UndoableTransaction() +{ + for (UndoCommand* command : m_commands) { + delete command; + } +} + +void UndoableTransaction::cleanup(bool undo) +{ + for (UndoCommand* command : m_commands) { + command->cleanup(undo); + } +} + +void UndoableTransaction::unwind() +{ + while (!m_commands.empty()) { + UndoCommand* command = muse::takeLast(m_commands); + LOG_UNDO() << "unwind: " << command->name(); + command->undo(nullptr); + delete command; + } +} + +void UndoableTransaction::appendCommands(UndoableTransaction& other) +{ + m_commands.insert(m_commands.end(), other.m_commands.cbegin(), other.m_commands.cend()); + other.m_commands.clear(); +} + +bool UndoableTransaction::hasCommandsMatchingFilter(UndoCommand::Filter f, const EngravingItem* target) const +{ + for (UndoCommand* command : m_commands) { + if (command->matchesFilter(f, target)) { + return true; + } + } + return false; +} + +bool UndoableTransaction::hasCommandsNotMatchingFilters(const std::vector& filters, + const EngravingItem* target) const +{ + for (UndoCommand* command : m_commands) { + bool filtered = false; + for (UndoCommand::Filter f : filters) { + if (command->matchesFilter(f, target)) { + filtered = true; + break; + } + } + if (!filtered) { + return true; + } + } + return false; +} + +void UndoableTransaction::removeCommandsMatchingFilter(UndoCommand::Filter f, EngravingItem* target) +{ + std::vector acceptedList; + for (UndoCommand* command : m_commands) { + if (command->matchesFilter(f, target)) { + delete command; + } else { + acceptedList.push_back(command); + } + } + m_commands = std::move(acceptedList); +} + +bool UndoableTransaction::canRecordSelectedElement(const EngravingItem* e) { if (e->generated()) { return false; @@ -494,7 +416,7 @@ bool UndoMacro::canRecordSelectedElement(const EngravingItem* e) || e->isFretDiagram() || e->isSoundFlag(); } -void UndoMacro::fillSelectionInfo(SelectionInfo& info, const Selection& sel) +void UndoableTransaction::fillSelectionInfo(SelectionInfo& info, const Selection& sel) { info.staffStart = info.staffEnd = muse::nidx; info.elements.clear(); @@ -517,7 +439,7 @@ void UndoMacro::fillSelectionInfo(SelectionInfo& info, const Selection& sel) } } -void UndoMacro::applySelectionInfo(const SelectionInfo& info, Selection& sel) +void UndoableTransaction::applySelectionInfo(const SelectionInfo& info, Selection& sel) { if (!info.elements.empty()) { for (EngravingItem* e : info.elements) { @@ -528,20 +450,16 @@ void UndoMacro::applySelectionInfo(const SelectionInfo& info, Selection& sel) } } -UndoMacro::UndoMacro(Score* s, const TranslatableString& actionName) - : m_undoInputState(s->inputState()), m_actionName(actionName), m_score(s) -{ - fillSelectionInfo(m_undoSelectionInfo, s->selection()); -} - -void UndoMacro::undo(EditData* ed) +void UndoableTransaction::undo(EditData* ed) { m_redoInputState = m_score->inputState(); fillSelectionInfo(m_redoSelectionInfo, m_score->selection()); m_score->deselectAll(); - // Undo for child commands. - UndoCommand::undo(ed); + for (auto it = m_commands.rbegin(); it != m_commands.rend(); ++it) { + LOG_UNDO() << "<" << (*it)->name() << ">"; + (*it)->undo(ed); + } m_score->setInputState(m_undoInputState); if (m_undoSelectionInfo.isValid()) { @@ -550,14 +468,16 @@ void UndoMacro::undo(EditData* ed) } } -void UndoMacro::redo(EditData* ed) +void UndoableTransaction::redo(EditData* ed) { m_undoInputState = m_score->inputState(); fillSelectionInfo(m_undoSelectionInfo, m_score->selection()); m_score->deselectAll(); - // Redo for child commands. - UndoCommand::redo(ed); + for (UndoCommand* command : m_commands) { + LOG_UNDO() << "<" << command->name() << ">"; + command->redo(ed); + } m_score->setInputState(m_redoInputState); if (m_redoSelectionInfo.isValid()) { @@ -566,31 +486,26 @@ void UndoMacro::redo(EditData* ed) } } -bool UndoMacro::empty() const -{ - return childCount() == 0; -} - -void UndoMacro::append(UndoMacro&& other) +void UndoableTransaction::append(UndoableTransaction&& other) { - appendChildren(other); + appendCommands(other); if (m_score == other.m_score) { m_redoInputState = std::move(other.m_redoInputState); m_redoSelectionInfo = std::move(other.m_redoSelectionInfo); } } -const InputState& UndoMacro::undoInputState() const +const InputState& UndoableTransaction::undoInputState() const { return m_undoInputState; } -const InputState& UndoMacro::redoInputState() const +const InputState& UndoableTransaction::redoInputState() const { return m_redoInputState; } -void UndoMacro::excludeElementFromSelectionInfo(EngravingItem* element) +void UndoableTransaction::excludeElementFromSelectionInfo(EngravingItem* element) { if (m_undoSelectionInfo.isValid()) { muse::remove(m_undoSelectionInfo.elements, element); @@ -601,17 +516,17 @@ void UndoMacro::excludeElementFromSelectionInfo(EngravingItem* element) } } -const UndoMacro::SelectionInfo& UndoMacro::undoSelectionInfo() const +const UndoableTransaction::SelectionInfo& UndoableTransaction::undoSelectionInfo() const { return m_undoSelectionInfo; } -const UndoMacro::SelectionInfo& UndoMacro::redoSelectionInfo() const +const UndoableTransaction::SelectionInfo& UndoableTransaction::redoSelectionInfo() const { return m_redoSelectionInfo; } -UndoMacro::ChangesInfo UndoMacro::changesInfo(bool undo) const +UndoableTransaction::ChangesInfo UndoableTransaction::changesInfo(bool undo) const { ChangesInfo result; @@ -654,7 +569,7 @@ UndoMacro::ChangesInfo UndoMacro::changesInfo(bool undo) const return result; } -const TranslatableString& UndoMacro::actionName() const +const TranslatableString& UndoableTransaction::actionName() const { return m_actionName; } diff --git a/src/engraving/editing/undo.h b/src/engraving/editing/undo.h index 47bbb0ba08ade..8bea2ca9b8734 100644 --- a/src/engraving/editing/undo.h +++ b/src/engraving/editing/undo.h @@ -166,6 +166,17 @@ enum class CommandType : signed char { class UndoCommand { public: + virtual ~UndoCommand() = default; + + virtual void undo(EditData*); + virtual void redo(EditData*); + + virtual void cleanup(bool /*undo*/) {} + + virtual std::vector objectItems() const { return {}; } + virtual const char* name() const { return "UndoCommand"; } + virtual CommandType type() const { return CommandType::Unknown; } + enum class Filter : unsigned char { TextEdit, AddElement, @@ -176,40 +187,37 @@ class UndoCommand ChangePropertyLinked, }; - virtual ~UndoCommand(); - virtual void undo(EditData*); - virtual void redo(EditData*); - void appendChild(UndoCommand* cmd) { m_childCommands.push_back(cmd); } - size_t childCount() const { return m_childCommands.size(); } - void unwind(); - const std::vector& commands() const { return m_childCommands; } - virtual std::vector objectItems() const { return {}; } - virtual void cleanup(bool undo); - virtual const char* name() const { return "UndoCommand"; } - virtual CommandType type() const { return CommandType::Unknown; } - - virtual bool isFiltered(Filter, const EngravingItem* /* target */) const { return false; } - bool hasFilteredChildren(Filter, const EngravingItem* target) const; - bool hasUnfilteredChildren(const std::vector& filters, const EngravingItem* target) const; - void filterChildren(UndoCommand::Filter f, EngravingItem* target); + virtual bool matchesFilter(Filter, const EngravingItem* /* target */) const { return false; } protected: virtual void flip(EditData*) {} - void appendChildren(UndoCommand& other); - -private: - std::vector m_childCommands; }; -//--------------------------------------------------------- -// UndoMacro -// A root element for undo macro which is stored -// directly in UndoStack -//--------------------------------------------------------- - -class UndoMacro : public UndoCommand +class UndoableTransaction { public: + UndoableTransaction(Score* s, const muse::TranslatableString& actionName); + ~UndoableTransaction(); + + void undo(EditData*); + void redo(EditData*); + + void appendCommand(UndoCommand* cmd) { m_commands.push_back(cmd); } + void append(UndoableTransaction&& other); + + void unwind(); + void cleanup(bool undo); + + const std::vector& commands() const { return m_commands; } + bool empty() const { return m_commands.empty(); } + + bool hasCommandsMatchingFilter(UndoCommand::Filter, const EngravingItem* target) const; + bool hasCommandsNotMatchingFilters(const std::vector& filters, const EngravingItem* target) const; + void removeCommandsMatchingFilter(UndoCommand::Filter f, EngravingItem* target); + + const InputState& undoInputState() const; + const InputState& redoInputState() const; + struct SelectionInfo { std::vector elements; Fraction tickStart; @@ -220,19 +228,11 @@ class UndoMacro : public UndoCommand bool isValid() const { return !elements.empty() || staffStart != muse::nidx; } }; - UndoMacro(Score* s, const TranslatableString& actionName); - void undo(EditData*) override; - void redo(EditData*) override; - bool empty() const; - void append(UndoMacro&& other); - - const InputState& undoInputState() const; - const InputState& redoInputState() const; - const SelectionInfo& undoSelectionInfo() const; const SelectionInfo& redoSelectionInfo() const; void excludeElementFromSelectionInfo(EngravingItem* element); + static bool canRecordSelectedElement(const EngravingItem* e); struct ChangesInfo { ElementTypeSet changedObjectTypes; @@ -243,18 +243,18 @@ class UndoMacro : public UndoCommand }; ChangesInfo changesInfo(bool undo = false) const; - const TranslatableString& actionName() const; + const muse::TranslatableString& actionName() const; - static bool canRecordSelectedElement(const EngravingItem* e); +private: + void appendCommands(UndoableTransaction& other); - UNDO_NAME("UndoMacro") + std::vector m_commands; -private: InputState m_undoInputState; InputState m_redoInputState; SelectionInfo m_undoSelectionInfo; SelectionInfo m_redoSelectionInfo; - TranslatableString m_actionName; + muse::TranslatableString m_actionName; Score* m_score = nullptr; @@ -271,48 +271,48 @@ class UndoStack bool isLocked() const; void setLocked(bool locked); - bool hasActiveCommand() const { return m_activeCommand != nullptr; } + bool hasActiveTransaction() const { return m_activeTransaction != nullptr; } - void beginMacro(Score*, const TranslatableString& actionName); - void endMacro(bool rollback); + void beginTransaction(Score*, const muse::TranslatableString& actionName); + void endTransaction(bool rollback); void pushAndPerform(UndoCommand*, EditData*); void pushWithoutPerforming(UndoCommand*); bool canUndo() const { return m_currentIndex > 0; } - bool canRedo() const { return m_currentIndex < m_macroList.size(); } - bool isClean() const { return m_cleanState == m_stateList[m_currentIndex]; } + bool canRedo() const { return m_currentIndex < m_transactions.size(); } + bool isClean() const { return m_cleanState == m_states[m_currentIndex]; } - size_t size() const { return m_macroList.size(); } + size_t size() const { return m_transactions.size(); } size_t currentIndex() const { return m_currentIndex; } - UndoMacro* activeCommand() const { return m_activeCommand; } + UndoableTransaction* activeTransaction() const { return m_activeTransaction; } - UndoMacro* last() const { return m_currentIndex > 0 ? m_macroList[m_currentIndex - 1] : nullptr; } - UndoMacro* prev() const { return m_currentIndex > 1 ? m_macroList[m_currentIndex - 2] : nullptr; } - UndoMacro* next() const { return canRedo() ? m_macroList[m_currentIndex] : nullptr; } + UndoableTransaction* last() const { return m_currentIndex > 0 ? m_transactions[m_currentIndex - 1] : nullptr; } + UndoableTransaction* prev() const { return m_currentIndex > 1 ? m_transactions[m_currentIndex - 2] : nullptr; } + UndoableTransaction* next() const { return canRedo() ? m_transactions[m_currentIndex] : nullptr; } - /// Returns the command that led to the state with the given `idx`. + /// Returns the transaction that led to the state with the given `idx`. /// For further discussion of the indices involved in UndoStack, see: /// https://github.com/musescore/MuseScore/pull/25389#discussion_r1825782176 - UndoMacro* lastAtIndex(size_t idx) const + UndoableTransaction* lastAtIndex(size_t idx) const { - return idx > 0 && idx - 1 < m_macroList.size() ? m_macroList[idx - 1] : nullptr; + return idx > 0 && idx - 1 < m_transactions.size() ? m_transactions[idx - 1] : nullptr; } void undo(EditData*); void redo(EditData*); void reopen(); - void mergeCommands(size_t startIdx); + void mergeTransactions(size_t startIdx); void cleanRedoStack() { remove(m_currentIndex); } private: void remove(size_t idx); - UndoMacro* m_activeCommand = nullptr; - std::vector m_macroList; - std::vector m_stateList; + UndoableTransaction* m_activeTransaction = nullptr; + std::vector m_transactions; + std::vector m_states; int m_nextState = 0; int m_cleanState = 0; size_t m_currentIndex = 0; diff --git a/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.cpp b/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.cpp index 7dc950d6576a9..9b8cf991a2567 100644 --- a/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.cpp +++ b/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.cpp @@ -59,28 +59,28 @@ void EngravingUndoStackModel::reload() m_allItems.clear(); delete m_rootItem; - m_rootItem = createItem(nullptr, nullptr); + m_rootItem = new Item(nullptr); + m_allItems.insert(m_rootItem->key(), m_rootItem); if (INotationPtr notation = context()->currentNotation()) { const UndoStack* undoStack = notation->elements()->msScore()->undoStack(); for (size_t i = 1; i <= undoStack->size(); ++i) { - const UndoCommand* cmd = undoStack->lastAtIndex(i); - Item* item = createItem(m_rootItem, cmd, cmd == undoStack->last()); - load(cmd, item); + const UndoableTransaction* transaction = undoStack->lastAtIndex(i); + Item* item = createItem(m_rootItem, transaction, transaction == undoStack->last()); + load(transaction, item); } } endResetModel(); } -void EngravingUndoStackModel::load(const UndoCommand* undoCommand, Item* parent) +void EngravingUndoStackModel::load(const UndoableTransaction* transaction, Item* parent) { TRACEFUNC; - for (const UndoCommand* childCommand : undoCommand->commands()) { - Item* item = createItem(parent, childCommand); - load(childCommand, item); + for (const UndoCommand* childCommand : transaction->commands()) { + createItem(parent, childCommand); } } @@ -201,6 +201,24 @@ static QColor colorForPointer(const void* ptr) return QColor::fromRgb(r, g, b, 128); } +EngravingUndoStackModel::Item* EngravingUndoStackModel::createItem( + Item* parent, const UndoableTransaction* transaction, bool isCurrent) +{ + Item* item = new Item(parent); + m_allItems.insert(item->key(), item); + + if (transaction) { + QVariantMap data; + data["text"] = transaction->actionName().qTranslated(); + data["color"] = colorForPointer(transaction); + data["isCurrent"] = isCurrent; + + item->setData(data); + } + + return item; +} + EngravingUndoStackModel::Item* EngravingUndoStackModel::createItem( Item* parent, const UndoCommand* undoCommand, bool isCurrent) { @@ -209,11 +227,7 @@ EngravingUndoStackModel::Item* EngravingUndoStackModel::createItem( if (undoCommand) { QVariantMap data; - if (const UndoMacro* macro = dynamic_cast(undoCommand)) { - data["text"] = macro->actionName().qTranslated(); - } else { - data["text"] = QString(undoCommand->name()); - } + data["text"] = QString(undoCommand->name()); data["color"] = colorForPointer(undoCommand); data["isCurrent"] = isCurrent; diff --git a/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.h b/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.h index e2dd2f86ad4fd..8a8f0a460b8e5 100644 --- a/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.h +++ b/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.h @@ -32,6 +32,7 @@ namespace mu::engraving { class UndoCommand; +class UndoableTransaction; class EngravingUndoStackModel : public QAbstractItemModel, public muse::async::Asyncable, public muse::Contextable { @@ -81,11 +82,12 @@ class EngravingUndoStackModel : public QAbstractItemModel, public muse::async::A void onNotationChanged(); + Item* createItem(Item* parent, const engraving::UndoableTransaction* transaction, bool isCurrent = false); Item* createItem(Item* parent, const engraving::UndoCommand* undoCommand, bool isCurrent = false); Item* itemByModelIndex(const QModelIndex& index) const; QVariantMap makeData(const mu::engraving::EngravingObject* el) const; - void load(const engraving::UndoCommand* undoCommand, Item* parent); + void load(const engraving::UndoableTransaction* transaction, Item* parent); Item* m_rootItem = nullptr; QHash m_allItems; diff --git a/src/engraving/rw/read400/readcontext.cpp b/src/engraving/rw/read400/readcontext.cpp index 8485967a2b197..41f183d1d3916 100644 --- a/src/engraving/rw/read400/readcontext.cpp +++ b/src/engraving/rw/read400/readcontext.cpp @@ -133,7 +133,7 @@ void ReadContext::addSpanner(Spanner* s) bool ReadContext::undoStackActive() const { - return m_score->undoStack()->hasActiveCommand(); + return m_score->undoStack()->hasActiveTransaction(); } bool ReadContext::isSameScore(const EngravingObject* obj) const diff --git a/src/notation/internal/inotationundostack.h b/src/notation/internal/inotationundostack.h index 50f05cfca6b65..185ff49988305 100644 --- a/src/notation/internal/inotationundostack.h +++ b/src/notation/internal/inotationundostack.h @@ -50,7 +50,7 @@ class INotationUndoStack virtual void rollbackChanges() = 0; virtual void commitChanges() = 0; - virtual void mergeCommands(const size_t startIdx) = 0; + virtual void mergeTransactions(const size_t startIdx) = 0; virtual bool isStackClean() const = 0; diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index 775fd5dce0765..014ea89977f19 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -1388,7 +1388,7 @@ void NotationInteraction::endDrag() if (m_editData.isHairpinDragCreatedFromDynamic) { // Merge the two actions of hairpin creation + hairpin drag - m_undoStack->mergeCommands(m_undoStack->currentStateIndex() - 2); + m_undoStack->mergeTransactions(m_undoStack->currentStateIndex() - 2); } notifyAboutDragChanged(); diff --git a/src/notation/internal/notationundostack.cpp b/src/notation/internal/notationundostack.cpp index ddd4bd27ad870..9becf06a976ab 100644 --- a/src/notation/internal/notationundostack.cpp +++ b/src/notation/internal/notationundostack.cpp @@ -153,13 +153,13 @@ bool NotationUndoStack::isStackClean() const return undoStack()->isClean(); } -void NotationUndoStack::mergeCommands(size_t startIdx) +void NotationUndoStack::mergeTransactions(size_t startIdx) { IF_ASSERT_FAILED(undoStack()) { return; } - undoStack()->mergeCommands(startIdx); + undoStack()->mergeTransactions(startIdx); } void NotationUndoStack::lock() diff --git a/src/notation/internal/notationundostack.h b/src/notation/internal/notationundostack.h index 5d0e5b56538eb..d97c867dbbbd5 100644 --- a/src/notation/internal/notationundostack.h +++ b/src/notation/internal/notationundostack.h @@ -52,7 +52,7 @@ class NotationUndoStack : public INotationUndoStack bool isStackClean() const override; - void mergeCommands(size_t startIdx) override; + void mergeTransactions(size_t startIdx) override; void lock() override; void unlock() override; diff --git a/src/notationscene/qml/MuseScore/NotationScene/elementpopups/partialtiepopupmodel.cpp b/src/notationscene/qml/MuseScore/NotationScene/elementpopups/partialtiepopupmodel.cpp index 4563add84aba8..8d6c6cd4c34c3 100644 --- a/src/notationscene/qml/MuseScore/NotationScene/elementpopups/partialtiepopupmodel.cpp +++ b/src/notationscene/qml/MuseScore/NotationScene/elementpopups/partialtiepopupmodel.cpp @@ -226,6 +226,6 @@ void mu::notation::PartialTiePopupModel::onClosed() } // Combine this with the last undoable action (which will be to remove a tie) so the user cannot undo to get a translucent tie - undoStack()->mergeCommands(undoStack()->currentStateIndex() - 2); + undoStack()->mergeTransactions(undoStack()->currentStateIndex() - 2); } } diff --git a/src/notationscene/widgets/editstaff.cpp b/src/notationscene/widgets/editstaff.cpp index dcd7fd8d59aed..31a09ac743ade 100644 --- a/src/notationscene/widgets/editstaff.cpp +++ b/src/notationscene/widgets/editstaff.cpp @@ -364,7 +364,7 @@ void EditStaff::apply() size_t index = m_staff->score()->undoStack()->currentIndex(); applyStaffProperties(); applyPartProperties(); - m_staff->score()->undoStack()->mergeCommands(index); + m_staff->score()->undoStack()->mergeTransactions(index); } void EditStaff::minPitchAClicked() From 28c025e05ef1f2fa6e159955f7a83afb08841551 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Mon, 18 May 2026 00:06:30 +0200 Subject: [PATCH 02/18] Remove StaffTextBase-specific code from undo logic --- src/engraving/dom/capo.cpp | 25 +++++++++++ src/engraving/dom/capo.h | 10 ++--- src/engraving/dom/stafftextbase.cpp | 44 +++++++++---------- src/engraving/dom/stafftextbase.h | 11 +++-- src/engraving/dom/stringtunings.cpp | 1 + src/engraving/editing/addremoveelement.cpp | 20 +-------- src/engraving/editing/editproperty.cpp | 4 -- src/engraving/editing/undo.cpp | 16 +------ src/engraving/editing/undo.h | 3 -- src/engraving/playback/playbackcontext.cpp | 10 ++--- .../rendering/score/arpeggiolayout.cpp | 1 + .../rendering/score/harmonylayout.cpp | 13 +++--- .../rendering/score/lyricslayout.cpp | 3 +- .../rendering/score/measurenumberlayout.cpp | 1 + src/engraving/rendering/score/restlayout.cpp | 1 + .../rendering/score/segmentlayout.cpp | 1 + .../rendering/score/systemheaderlayout.cpp | 3 +- src/engraving/rendering/score/tlayout.h | 2 +- src/engraving/rendering/single/singledraw.cpp | 2 + .../rendering/single/singlelayout.cpp | 1 + src/engraving/rw/compat/compatutils.cpp | 31 +++++++------ src/engraving/rw/read410/tread.cpp | 1 + src/engraving/rw/read460/tread.cpp | 2 +- src/engraving/rw/read500/tread.cpp | 2 +- .../tabledit/internal/importtef.cpp | 2 + src/palette/internal/palettecreator.cpp | 1 + 26 files changed, 107 insertions(+), 104 deletions(-) diff --git a/src/engraving/dom/capo.cpp b/src/engraving/dom/capo.cpp index 9464f3e1f106f..a7591921422f2 100644 --- a/src/engraving/dom/capo.cpp +++ b/src/engraving/dom/capo.cpp @@ -22,6 +22,8 @@ #include "capo.h" +#include "score.h" + #include "translation.h" using namespace mu::engraving; @@ -109,6 +111,11 @@ bool Capo::setProperty(Pid id, const PropertyValue& val) } triggerLayout(); + + if (Score* s = score()) { + s->updateCapo(); + } + return true; } @@ -171,3 +178,21 @@ muse::String Capo::generateText(size_t stringCount) const return text; } + +void Capo::added() +{ + StaffTextBase::added(); + + if (Score* s = score()) { + s->updateCapo(); + } +} + +void Capo::removed() +{ + StaffTextBase::removed(); + + if (Score* s = score()) { + s->updateCapo(); + } +} diff --git a/src/engraving/dom/capo.h b/src/engraving/dom/capo.h index 37692a613a924..463c531c71a53 100644 --- a/src/engraving/dom/capo.h +++ b/src/engraving/dom/capo.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_CAPO_H -#define MU_ENGRAVING_CAPO_H +#pragma once #include "stafftextbase.h" @@ -49,11 +48,12 @@ class Capo final : public StaffTextBase bool shouldAutomaticallyGenerateText() const; String generateText(size_t stringCount) const; + void added() override; + void removed() override; + private: CapoParams m_params; bool m_shouldAutomaticallyGenerateText = true; String m_customText; }; -} // namespace mu::engraving - -#endif // MU_ENGRAVING_CAPO_H +} diff --git a/src/engraving/dom/stafftextbase.cpp b/src/engraving/dom/stafftextbase.cpp index e76832aa568bd..4bd09af9be04f 100644 --- a/src/engraving/dom/stafftextbase.cpp +++ b/src/engraving/dom/stafftextbase.cpp @@ -22,21 +22,14 @@ #include "stafftextbase.h" -#include "types/typesconv.h" - +#include "score.h" #include "segment.h" #include "staff.h" #include "log.h" -using namespace mu; using namespace mu::engraving; -namespace mu::engraving { -//--------------------------------------------------------- -// StaffTextBase -//--------------------------------------------------------- - StaffTextBase::StaffTextBase(const ElementType& type, Segment* parent, TextStyleType tid, ElementFlags flags) : TextBase(type, parent, tid, flags) { @@ -51,10 +44,6 @@ void StaffTextBase::clear() clearAeolusStops(); } -//--------------------------------------------------------- -// clearAeolusStops -//--------------------------------------------------------- - void StaffTextBase::clearAeolusStops() { for (int i = 0; i < 4; ++i) { @@ -62,10 +51,6 @@ void StaffTextBase::clearAeolusStops() } } -//--------------------------------------------------------- -// setAeolusStop -//--------------------------------------------------------- - void StaffTextBase::setAeolusStop(int group, int idx, bool val) { if (val) { @@ -80,10 +65,6 @@ void StaffTextBase::setAeolusStop(int group, int val) m_aeolusStops[group] = val; } -//--------------------------------------------------------- -// getAeolusStop -//--------------------------------------------------------- - bool StaffTextBase::getAeolusStop(int group, int idx) const { return m_aeolusStops[group] & (1 << idx); @@ -94,10 +75,6 @@ int StaffTextBase::aeolusStop(int group) const return m_aeolusStops[group]; } -//--------------------------------------------------------- -// segment -//--------------------------------------------------------- - Segment* StaffTextBase::segment() const { if (!explicitParent()->isSegment()) { @@ -107,4 +84,23 @@ Segment* StaffTextBase::segment() const Segment* s = toSegment(explicitParent()); return s; } + +void StaffTextBase::added() +{ + TextBase::added(); + + Score* s = score(); + if (s && swing()) { + s->updateSwing(); + } +} + +void StaffTextBase::removed() +{ + TextBase::removed(); + + Score* s = score(); + if (s && swing()) { + s->updateSwing(); + } } diff --git a/src/engraving/dom/stafftextbase.h b/src/engraving/dom/stafftextbase.h index ac6789d4a1645..82413a632a464 100644 --- a/src/engraving/dom/stafftextbase.h +++ b/src/engraving/dom/stafftextbase.h @@ -20,11 +20,9 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_STAFFTEXTBASE_H -#define MU_ENGRAVING_STAFFTEXTBASE_H +#pragma once #include "textbase.h" -#include "staff.h" namespace mu::engraving { //--------------------------------------------------------- @@ -75,8 +73,10 @@ class StaffTextBase : public TextBase bool positionRelativeToNoteheadRest() const override { return true; } -private: + void added() override; + void removed() override; +private: String m_channelNames[4]; std::vector m_channelActions; SwingParameters m_swingParameters; @@ -85,5 +85,4 @@ class StaffTextBase : public TextBase bool m_swing = false; int m_capo = 0; }; -} // namespace mu::engraving -#endif +} diff --git a/src/engraving/dom/stringtunings.cpp b/src/engraving/dom/stringtunings.cpp index 264926114bbf7..1c09844759b02 100644 --- a/src/engraving/dom/stringtunings.cpp +++ b/src/engraving/dom/stringtunings.cpp @@ -28,6 +28,7 @@ #include "part.h" #include "score.h" #include "segment.h" +#include "staff.h" #include "../editing/editpart.h" #include "containers.h" diff --git a/src/engraving/editing/addremoveelement.cpp b/src/engraving/editing/addremoveelement.cpp index ff0223ceef1c8..e6993e9a2fcb4 100644 --- a/src/engraving/editing/addremoveelement.cpp +++ b/src/engraving/editing/addremoveelement.cpp @@ -116,10 +116,6 @@ void AddElement::undo(EditData*) score->removeElement(element); } - if (element->isStaffTextBase()) { - updateStaffTextCache(toStaffTextBase(element), score); - } - endUndoRedo(true); } @@ -131,10 +127,6 @@ void AddElement::redo(EditData*) score->addElement(element); } - if (element->isStaffTextBase()) { - updateStaffTextCache(toStaffTextBase(element), score); - } - endUndoRedo(false); } @@ -258,9 +250,7 @@ void RemoveElement::undo(EditData*) score->addElement(element); } - if (element->isStaffTextBase()) { - updateStaffTextCache(toStaffTextBase(element), score); - } else if (element->isChordRest()) { + if (element->isChordRest()) { if (element->isChord()) { Chord* chord = toChord(element); for (Note* note : chord->notes()) { @@ -283,9 +273,7 @@ void RemoveElement::redo(EditData*) score->removeElement(element); } - if (element->isStaffTextBase()) { - updateStaffTextCache(toStaffTextBase(element), score); - } else if (element->isChordRest()) { + if (element->isChordRest()) { undoRemoveTuplet(toChordRest(element)); if (element->isChord()) { Chord* chord = toChord(element); @@ -395,10 +383,6 @@ void ChangeElement::flip(EditData*) } } - if (newElement->isStaffTextBase()) { - updateStaffTextCache(toStaffTextBase(newElement), score); - } - std::swap(oldElement, newElement); oldElement->triggerLayout(); newElement->triggerLayout(); diff --git a/src/engraving/editing/editproperty.cpp b/src/engraving/editing/editproperty.cpp index 5fbb2346c7157..79ba86f9e7c6c 100644 --- a/src/engraving/editing/editproperty.cpp +++ b/src/engraving/editing/editproperty.cpp @@ -46,10 +46,6 @@ void ChangeProperty::flip(EditData*) element->setProperty(id, property); element->setPropertyFlags(id, flags); - if (element->isStaffTextBase()) { - updateStaffTextCache(toStaffTextBase(element), element->score()); - } - property = v; flags = ps; } diff --git a/src/engraving/editing/undo.cpp b/src/engraving/editing/undo.cpp index 65492cf797720..ffa5e22663ec9 100644 --- a/src/engraving/editing/undo.cpp +++ b/src/engraving/editing/undo.cpp @@ -43,12 +43,11 @@ #include "../dom/fret.h" #include "../dom/harmony.h" #include "../dom/note.h" -#include "../dom/stafftextbase.h" #include "log.h" -#define LOG_UNDO() if (0) LOGD() -using namespace mu; +#define LOG_UNDO() if constexpr (false) LOGD() + using namespace mu::engraving; namespace mu::engraving { @@ -114,17 +113,6 @@ std::vector compoundObjects(EngravingObject* object) return objects; } -void updateStaffTextCache(const StaffTextBase* text, Score* score) -{ - TRACEFUNC; - - if (text->isCapo()) { - score->updateCapo(); - } else if (text->swing()) { - score->updateSwing(); - } -} - void UndoCommand::undo(EditData* ed) { flip(ed); diff --git a/src/engraving/editing/undo.h b/src/engraving/editing/undo.h index 8bea2ca9b8734..ccb2be354e9da 100644 --- a/src/engraving/editing/undo.h +++ b/src/engraving/editing/undo.h @@ -320,7 +320,4 @@ class UndoStack }; std::vector compoundObjects(EngravingObject* object); - -class StaffTextBase; -void updateStaffTextCache(const StaffTextBase* text, Score* score); } diff --git a/src/engraving/playback/playbackcontext.cpp b/src/engraving/playback/playbackcontext.cpp index 993067aa40a51..49c0bc53c6999 100644 --- a/src/engraving/playback/playbackcontext.cpp +++ b/src/engraving/playback/playbackcontext.cpp @@ -24,18 +24,18 @@ #include "dom/dynamic.h" #include "dom/hairpin.h" +#include "dom/lyrics.h" #include "dom/measure.h" +#include "dom/measurerepeat.h" #include "dom/part.h" #include "dom/playtechannotation.h" -#include "dom/stafftext.h" -#include "dom/soundflag.h" #include "dom/repeatlist.h" #include "dom/score.h" #include "dom/segment.h" +#include "dom/soundflag.h" #include "dom/spanner.h" -#include "dom/measurerepeat.h" -#include "dom/lyrics.h" -#include "dom/sticking.h" +#include "dom/staff.h" +#include "dom/stafftext.h" #include "utils/arrangementutils.h" #include "utils/expressionutils.h" diff --git a/src/engraving/rendering/score/arpeggiolayout.cpp b/src/engraving/rendering/score/arpeggiolayout.cpp index 22aa36121560d..5706947dc7022 100644 --- a/src/engraving/rendering/score/arpeggiolayout.cpp +++ b/src/engraving/rendering/score/arpeggiolayout.cpp @@ -27,6 +27,7 @@ #include "dom/part.h" #include "dom/score.h" #include "dom/segment.h" +#include "dom/staff.h" #include "tlayout.h" diff --git a/src/engraving/rendering/score/harmonylayout.cpp b/src/engraving/rendering/score/harmonylayout.cpp index ddc6f1788a2de..acbcd907de1ba 100644 --- a/src/engraving/rendering/score/harmonylayout.cpp +++ b/src/engraving/rendering/score/harmonylayout.cpp @@ -21,14 +21,17 @@ */ #include "harmonylayout.h" -#include "rendering/score/parenthesislayout.h" -#include "tlayout.h" -#include "textlayout.h" -#include "dom/fret.h" -#include "dom/harmony.h" #include "draw/fontmetrics.h" + #include "dom/factory.h" +#include "dom/fret.h" +#include "dom/harmony.h" +#include "dom/staff.h" + +#include "parenthesislayout.h" +#include "textlayout.h" +#include "tlayout.h" using namespace muse::draw; using namespace mu::engraving; diff --git a/src/engraving/rendering/score/lyricslayout.cpp b/src/engraving/rendering/score/lyricslayout.cpp index 137c8465af2f5..ed753017d9606 100644 --- a/src/engraving/rendering/score/lyricslayout.cpp +++ b/src/engraving/rendering/score/lyricslayout.cpp @@ -21,7 +21,6 @@ */ #include "lyricslayout.h" -#include "dom/masterscore.h" #include "dom/repeatlist.h" #include "style/styledef.h" @@ -30,12 +29,12 @@ #include "dom/measure.h" #include "dom/score.h" #include "dom/segment.h" +#include "dom/staff.h" #include "dom/stafftype.h" #include "dom/system.h" #include "tlayout.h" #include "textlayout.h" -#include "autoplace.h" using namespace mu; using namespace mu::engraving; diff --git a/src/engraving/rendering/score/measurenumberlayout.cpp b/src/engraving/rendering/score/measurenumberlayout.cpp index 945d12bc183f7..fa502a4c53f6c 100644 --- a/src/engraving/rendering/score/measurenumberlayout.cpp +++ b/src/engraving/rendering/score/measurenumberlayout.cpp @@ -28,6 +28,7 @@ #include "dom/measure.h" #include "dom/score.h" #include "dom/segment.h" +#include "dom/staff.h" #include "dom/system.h" using namespace mu::engraving; diff --git a/src/engraving/rendering/score/restlayout.cpp b/src/engraving/rendering/score/restlayout.cpp index ee2c5fbfac8d2..2113c6b8b1e40 100644 --- a/src/engraving/rendering/score/restlayout.cpp +++ b/src/engraving/rendering/score/restlayout.cpp @@ -27,6 +27,7 @@ #include "tlayout.h" #include "dom/beam.h" +#include "dom/staff.h" #include "dom/system.h" using namespace muse; diff --git a/src/engraving/rendering/score/segmentlayout.cpp b/src/engraving/rendering/score/segmentlayout.cpp index 4e44e874f2e8f..591c437e45255 100644 --- a/src/engraving/rendering/score/segmentlayout.cpp +++ b/src/engraving/rendering/score/segmentlayout.cpp @@ -24,6 +24,7 @@ #include "dom/part.h" #include "dom/drumset.h" #include "dom/parenthesis.h" +#include "dom/staff.h" #include "tlayout.h" #include "chordlayout.h" diff --git a/src/engraving/rendering/score/systemheaderlayout.cpp b/src/engraving/rendering/score/systemheaderlayout.cpp index ff35ded3273c0..bc9838d840f96 100644 --- a/src/engraving/rendering/score/systemheaderlayout.cpp +++ b/src/engraving/rendering/score/systemheaderlayout.cpp @@ -28,8 +28,9 @@ #include "dom/bracket.h" #include "dom/factory.h" #include "dom/part.h" -#include "style/defaultstyle.h" +#include "dom/staff.h" #include "dom/system.h" +#include "style/defaultstyle.h" using namespace mu::engraving; using namespace mu::engraving::rendering::score; diff --git a/src/engraving/rendering/score/tlayout.h b/src/engraving/rendering/score/tlayout.h index 3aa445a743181..b4340ad3e568a 100644 --- a/src/engraving/rendering/score/tlayout.h +++ b/src/engraving/rendering/score/tlayout.h @@ -73,7 +73,6 @@ #include "../../dom/marker.h" #include "../../dom/measurebase.h" #include "../../dom/measurenumber.h" -#include "../../dom/measurenumberbase.h" #include "../../dom/measurerepeat.h" #include "../../dom/mmrest.h" #include "../../dom/mmrestrange.h" @@ -90,6 +89,7 @@ #include "../../dom/staffstate.h" #include "../../dom/stafftext.h" +#include "../../dom/stafftype.h" #include "../../dom/stafftypechange.h" #include "../../dom/stem.h" #include "../../dom/stemslash.h" diff --git a/src/engraving/rendering/single/singledraw.cpp b/src/engraving/rendering/single/singledraw.cpp index ab0b7e4480cf9..3733ce875bf5d 100644 --- a/src/engraving/rendering/single/singledraw.cpp +++ b/src/engraving/rendering/single/singledraw.cpp @@ -107,9 +107,11 @@ #include "dom/slur.h" #include "dom/soundflag.h" #include "dom/spacer.h" +#include "dom/staff.h" #include "dom/stafflines.h" #include "dom/staffstate.h" #include "dom/stafftext.h" +#include "dom/stafftype.h" #include "dom/stafftypechange.h" #include "dom/stem.h" #include "dom/stemslash.h" diff --git a/src/engraving/rendering/single/singlelayout.cpp b/src/engraving/rendering/single/singlelayout.cpp index e8fde10eab70b..20ed00ab8566f 100644 --- a/src/engraving/rendering/single/singlelayout.cpp +++ b/src/engraving/rendering/single/singlelayout.cpp @@ -80,6 +80,7 @@ #include "dom/slur.h" #include "dom/soundflag.h" #include "dom/spacer.h" +#include "dom/staff.h" #include "dom/stafftext.h" #include "dom/stafftypechange.h" #include "dom/sticking.h" diff --git a/src/engraving/rw/compat/compatutils.cpp b/src/engraving/rw/compat/compatutils.cpp index 73ea52c3dac1f..8d985b00dd5e0 100644 --- a/src/engraving/rw/compat/compatutils.cpp +++ b/src/engraving/rw/compat/compatutils.cpp @@ -23,37 +23,40 @@ #include "compatutils.h" #include "dom/articulation.h" +#include "dom/capo.h" #include "dom/chord.h" #include "dom/dynamic.h" +#include "dom/excerpt.h" #include "dom/expression.h" +#include "dom/factory.h" #include "dom/harmony.h" #include "dom/image.h" +#include "dom/key.h" +#include "dom/keylist.h" #include "dom/laissezvib.h" -#include "dom/masterscore.h" -#include "dom/note.h" -#include "dom/score.h" -#include "dom/excerpt.h" -#include "dom/part.h" -#include "dom/stem.h" #include "dom/linkedobjects.h" +#include "dom/masterscore.h" #include "dom/measure.h" -#include "dom/factory.h" +#include "dom/note.h" +#include "dom/noteline.h" #include "dom/ornament.h" +#include "dom/part.h" +#include "dom/playtechannotation.h" #include "dom/rest.h" +#include "dom/score.h" +#include "dom/staff.h" #include "dom/stafftext.h" #include "dom/stafftextbase.h" -#include "dom/playtechannotation.h" -#include "dom/capo.h" -#include "dom/noteline.h" -#include "dom/textline.h" -#include "style/styledef.h" -#include "style/defaultstyle.h" +#include "dom/stem.h" #include "dom/tempotext.h" +#include "dom/textline.h" #include "editing/editchord.h" #include "editing/transpose.h" -#include "engraving/style/textstyle.h" +#include "style/defaultstyle.h" +#include "style/styledef.h" +#include "style/textstyle.h" #include "types/string.h" diff --git a/src/engraving/rw/read410/tread.cpp b/src/engraving/rw/read410/tread.cpp index f3c0d7e011721..15b176a82b4dd 100644 --- a/src/engraving/rw/read410/tread.cpp +++ b/src/engraving/rw/read410/tread.cpp @@ -105,6 +105,7 @@ #include "../../dom/soundflag.h" #include "../../dom/spacer.h" #include "../../dom/spanner.h" +#include "../../dom/staff.h" #include "../../dom/staffstate.h" #include "../../dom/stafftext.h" #include "../../dom/stafftextbase.h" diff --git a/src/engraving/rw/read460/tread.cpp b/src/engraving/rw/read460/tread.cpp index 0106ab20e1a21..9911446adbfc0 100644 --- a/src/engraving/rw/read460/tread.cpp +++ b/src/engraving/rw/read460/tread.cpp @@ -92,7 +92,6 @@ #include "../../dom/palmmute.h" #include "../../dom/parenthesis.h" #include "../../dom/part.h" -#include "../../dom/part.h" #include "../../dom/partialtie.h" #include "../../dom/pedal.h" #include "../../dom/playcounttext.h" @@ -107,6 +106,7 @@ #include "../../dom/soundflag.h" #include "../../dom/spacer.h" #include "../../dom/spanner.h" +#include "../../dom/staff.h" #include "../../dom/staffstate.h" #include "../../dom/stafftext.h" #include "../../dom/stafftextbase.h" diff --git a/src/engraving/rw/read500/tread.cpp b/src/engraving/rw/read500/tread.cpp index cba62f9224f0e..303bf72ce7753 100644 --- a/src/engraving/rw/read500/tread.cpp +++ b/src/engraving/rw/read500/tread.cpp @@ -92,7 +92,6 @@ #include "../../dom/palmmute.h" #include "../../dom/parenthesis.h" #include "../../dom/part.h" -#include "../../dom/part.h" #include "../../dom/partialtie.h" #include "../../dom/pedal.h" #include "../../dom/playcounttext.h" @@ -108,6 +107,7 @@ #include "../../dom/soundflag.h" #include "../../dom/spacer.h" #include "../../dom/spanner.h" +#include "../../dom/staff.h" #include "../../dom/staffstate.h" #include "../../dom/stafftext.h" #include "../../dom/stafftextbase.h" diff --git a/src/importexport/tabledit/internal/importtef.cpp b/src/importexport/tabledit/internal/importtef.cpp index 283de1cd6d6de..60a8016e632ea 100644 --- a/src/importexport/tabledit/internal/importtef.cpp +++ b/src/importexport/tabledit/internal/importtef.cpp @@ -35,7 +35,9 @@ #include "engraving/dom/part.h" #include "engraving/dom/playcounttext.h" #include "engraving/dom/rest.h" +#include "engraving/dom/staff.h" #include "engraving/dom/stafftext.h" +#include "engraving/dom/stafftype.h" #include "engraving/dom/tempotext.h" #include "engraving/dom/text.h" #include "engraving/dom/timesig.h" diff --git a/src/palette/internal/palettecreator.cpp b/src/palette/internal/palettecreator.cpp index 2e4a6a6d36489..c88988ba33360 100644 --- a/src/palette/internal/palettecreator.cpp +++ b/src/palette/internal/palettecreator.cpp @@ -75,6 +75,7 @@ #include "engraving/dom/segment.h" #include "engraving/dom/slur.h" #include "engraving/dom/spacer.h" +#include "engraving/dom/staff.h" #include "engraving/dom/stafftext.h" #include "engraving/dom/stringtunings.h" #include "engraving/dom/systemtext.h" From 581ba2d9ebfbe7d0a9d33e5aea9fcadfc758d515 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Sun, 24 May 2026 01:35:34 +0200 Subject: [PATCH 03/18] Split UndoStack and UndoableCommand into separate files --- src/engraving/CMakeLists.txt | 6 +- src/engraving/api/v1/elements.h | 2 - src/engraving/api/v1/selection.cpp | 2 +- .../compat/midi/compatmidirender.cpp | 1 - src/engraving/dom/arpeggio.cpp | 1 - src/engraving/dom/barline.cpp | 1 - src/engraving/dom/box.cpp | 1 - src/engraving/dom/clef.cpp | 1 - src/engraving/dom/durationelement.cpp | 4 +- src/engraving/dom/dynamichairpingroup.cpp | 2 - src/engraving/dom/engravingobject.cpp | 2 +- src/engraving/dom/figuredbass.cpp | 1 - src/engraving/dom/harmony.cpp | 4 - src/engraving/dom/lyrics.cpp | 1 - src/engraving/dom/masterscore.cpp | 1 + src/engraving/dom/playcounttext.cpp | 3 +- src/engraving/dom/score.cpp | 3 +- src/engraving/dom/score.h | 4 +- src/engraving/dom/slur.cpp | 1 + src/engraving/dom/system.cpp | 5 - src/engraving/dom/textbase.cpp | 3 - src/engraving/dom/tie.cpp | 5 - src/engraving/editing/addremoveelement.cpp | 19 +- src/engraving/editing/addremoveelement.h | 21 +- src/engraving/editing/cmd.cpp | 4 +- src/engraving/editing/edit.cpp | 1 + src/engraving/editing/editbrackets.h | 6 +- src/engraving/editing/editchord.h | 16 +- src/engraving/editing/editclef.h | 4 +- src/engraving/editing/editexcerpt.h | 12 +- src/engraving/editing/editfretboarddiagram.h | 16 +- src/engraving/editing/editharppedaldiagram.h | 6 +- src/engraving/editing/editinstrumentchange.h | 4 +- src/engraving/editing/editkeysig.h | 4 +- src/engraving/editing/editmeasures.h | 10 +- src/engraving/editing/editnote.cpp | 7 +- src/engraving/editing/editnote.h | 11 +- src/engraving/editing/editpart.h | 26 +-- src/engraving/editing/editproperty.h | 8 +- src/engraving/editing/editscoreproperties.h | 10 +- src/engraving/editing/editsoundflag.h | 4 +- src/engraving/editing/editspanner.h | 6 +- src/engraving/editing/editstaff.h | 28 +-- src/engraving/editing/editstavesharing.cpp | 87 +++++--- src/engraving/editing/editstavesharing.h | 41 +--- src/engraving/editing/editstyle.cpp | 2 +- src/engraving/editing/editstyle.h | 6 +- src/engraving/editing/editsystemlocks.cpp | 6 +- src/engraving/editing/edittie.h | 12 +- src/engraving/editing/edittremolo.h | 4 +- src/engraving/editing/exchangevoices.cpp | 4 +- src/engraving/editing/inserttime.h | 6 +- src/engraving/editing/textedit.cpp | 10 +- src/engraving/editing/textedit.h | 24 +-- .../editing/transaction/undoablecommand.cpp | 68 +++++++ .../editing/transaction/undoablecommand.h | 191 ++++++++++++++++++ .../{undo.cpp => transaction/undostack.cpp} | 89 +++----- .../{undo.h => transaction/undostack.h} | 181 ++--------------- src/engraving/editing/transpose.cpp | 4 +- src/engraving/playback/playbackmodel.cpp | 5 +- .../devtools/engravingundostackmodel.cpp | 13 +- .../devtools/engravingundostackmodel.h | 4 +- src/engraving/rendering/score/boxlayout.cpp | 1 - src/engraving/rendering/score/chordlayout.cpp | 1 - .../rendering/score/layoutcontext.cpp | 2 +- src/engraving/rendering/score/layoutcontext.h | 4 +- src/engraving/rw/read206/read206.cpp | 1 - src/engraving/rw/read400/readcontext.cpp | 2 +- src/engraving/rw/read410/readcontext.cpp | 2 - src/engraving/rw/read460/readcontext.cpp | 2 - src/engraving/rw/read500/readcontext.cpp | 2 - src/engraving/tests/box_tests.cpp | 2 +- src/engraving/tests/breath_tests.cpp | 2 +- src/engraving/tests/earlymusic_tests.cpp | 1 + src/engraving/tests/exchangevoices_tests.cpp | 2 +- src/engraving/tests/implodeexplode_tests.cpp | 2 +- src/engraving/tests/keysig_tests.cpp | 2 +- src/engraving/tests/measure_tests.cpp | 3 +- .../tests/readwriteundoreset_tests.cpp | 1 - src/engraving/tests/spanners_tests.cpp | 1 + src/engraving/tests/textbase_tests.cpp | 1 + src/engraving/tests/timesig_tests.cpp | 2 +- src/engraving/tests/tools_tests.cpp | 2 +- src/engraving/tests/transpose_tests.cpp | 2 +- .../internal/export/exportmusicxml.cpp | 1 - .../internal/systemobjectslayertreeitem.cpp | 2 +- src/notation/internal/masternotation.cpp | 3 - src/notation/internal/notationundostack.cpp | 2 +- .../elementpopups/partialtiepopupmodel.cpp | 1 - .../percussionpanel/percussionpanelmodel.cpp | 1 - src/notationscene/widgets/editstaff.cpp | 2 +- .../emptystavesvisiblitysettingsmodel.cpp | 1 - 92 files changed, 559 insertions(+), 528 deletions(-) create mode 100644 src/engraving/editing/transaction/undoablecommand.cpp create mode 100644 src/engraving/editing/transaction/undoablecommand.h rename src/engraving/editing/{undo.cpp => transaction/undostack.cpp} (87%) rename src/engraving/editing/{undo.h => transaction/undostack.h} (56%) diff --git a/src/engraving/CMakeLists.txt b/src/engraving/CMakeLists.txt index 08f3ac8015b47..eebca473eaa56 100644 --- a/src/engraving/CMakeLists.txt +++ b/src/engraving/CMakeLists.txt @@ -167,10 +167,12 @@ target_sources(engraving PRIVATE editing/splitjoinmeasure.h editing/textedit.cpp editing/textedit.h + editing/transaction/undoablecommand.cpp + editing/transaction/undoablecommand.h + editing/transaction/undostack.cpp + editing/transaction/undostack.h editing/transpose.cpp editing/transpose.h - editing/undo.cpp - editing/undo.h rw/ireader.h rw/iwriter.h diff --git a/src/engraving/api/v1/elements.h b/src/engraving/api/v1/elements.h index aa64e57780073..022c7d6b43f36 100644 --- a/src/engraving/api/v1/elements.h +++ b/src/engraving/api/v1/elements.h @@ -54,8 +54,6 @@ #include "engraving/dom/tie.h" #include "engraving/dom/accidental.h" -#include "engraving/editing/undo.h" - #include "playevent.h" // api diff --git a/src/engraving/api/v1/selection.cpp b/src/engraving/api/v1/selection.cpp index d0cbeacbcbd63..54b9597390f7d 100644 --- a/src/engraving/api/v1/selection.cpp +++ b/src/engraving/api/v1/selection.cpp @@ -22,7 +22,7 @@ #include "selection.h" -#include "engraving/editing/undo.h" +#include "engraving/editing/transaction/undostack.h" // api #include "score.h" diff --git a/src/engraving/compat/midi/compatmidirender.cpp b/src/engraving/compat/midi/compatmidirender.cpp index 84e6e552c338b..da1689c78eff8 100644 --- a/src/engraving/compat/midi/compatmidirender.cpp +++ b/src/engraving/compat/midi/compatmidirender.cpp @@ -35,7 +35,6 @@ #include "dom/trill.h" #include "dom/utils.h" #include "dom/volta.h" -#include "editing/undo.h" #include "types/constants.h" namespace mu::engraving { diff --git a/src/engraving/dom/arpeggio.cpp b/src/engraving/dom/arpeggio.cpp index 28c489d4c8925..1095f941bf1f8 100644 --- a/src/engraving/dom/arpeggio.cpp +++ b/src/engraving/dom/arpeggio.cpp @@ -24,7 +24,6 @@ #include "../editing/elementeditdata.h" #include "../editing/mscoreview.h" -#include "../editing/undo.h" #include "../types/typesconv.h" #include "accidental.h" diff --git a/src/engraving/dom/barline.cpp b/src/engraving/dom/barline.cpp index ea4b4ece91f47..aeac6d39031ce 100644 --- a/src/engraving/dom/barline.cpp +++ b/src/engraving/dom/barline.cpp @@ -24,7 +24,6 @@ #include "translation.h" -#include "../editing/undo.h" #include "../types/symnames.h" #include "articulation.h" diff --git a/src/engraving/dom/box.cpp b/src/engraving/dom/box.cpp index 4b02aa3a411b3..1cf79dd78e657 100644 --- a/src/engraving/dom/box.cpp +++ b/src/engraving/dom/box.cpp @@ -25,7 +25,6 @@ #include #include "../editing/elementeditdata.h" -#include "../editing/undo.h" #include "../editing/editfretboarddiagram.h" #include "actionicon.h" diff --git a/src/engraving/dom/clef.cpp b/src/engraving/dom/clef.cpp index 2e7bf497884ec..77801224857d8 100644 --- a/src/engraving/dom/clef.cpp +++ b/src/engraving/dom/clef.cpp @@ -29,7 +29,6 @@ #include "translation.h" -#include "../editing/undo.h" #include "../editing/editproperty.h" #include "../types/typesconv.h" diff --git a/src/engraving/dom/durationelement.cpp b/src/engraving/dom/durationelement.cpp index bf25f24408329..c7b9ca1a9f1f9 100644 --- a/src/engraving/dom/durationelement.cpp +++ b/src/engraving/dom/durationelement.cpp @@ -22,13 +22,13 @@ #include "durationelement.h" -#include "../editing/undo.h" - #include "property.h" #include "score.h" #include "staff.h" #include "tuplet.h" +#include "editing/transaction/undostack.h" + using namespace mu; using namespace mu::engraving; diff --git a/src/engraving/dom/dynamichairpingroup.cpp b/src/engraving/dom/dynamichairpingroup.cpp index 8c5fcf317dbd8..04b1a4dd91aa5 100644 --- a/src/engraving/dom/dynamichairpingroup.cpp +++ b/src/engraving/dom/dynamichairpingroup.cpp @@ -22,8 +22,6 @@ #include "dynamichairpingroup.h" -#include "../editing/undo.h" - #include "dynamic.h" #include "expression.h" #include "hairpin.h" diff --git a/src/engraving/dom/engravingobject.cpp b/src/engraving/dom/engravingobject.cpp index 4f0205bb8950a..c9989f272ef50 100644 --- a/src/engraving/dom/engravingobject.cpp +++ b/src/engraving/dom/engravingobject.cpp @@ -24,9 +24,9 @@ #include "global/containers.h" -#include "../editing/undo.h" #include "../editing/addremoveelement.h" #include "../editing/editproperty.h" +#include "../editing/transaction/undostack.h" #include "style/textstyle.h" #include "types/typesconv.h" diff --git a/src/engraving/dom/figuredbass.cpp b/src/engraving/dom/figuredbass.cpp index e9e6d6526c241..f3e9dde9f3fae 100644 --- a/src/engraving/dom/figuredbass.cpp +++ b/src/engraving/dom/figuredbass.cpp @@ -24,7 +24,6 @@ #include "io/file.h" -#include "../editing/undo.h" #include "rw/xmlreader.h" #include "style/textstyle.h" diff --git a/src/engraving/dom/harmony.cpp b/src/engraving/dom/harmony.cpp index bf75d0608ae33..e9ca5496b2472 100644 --- a/src/engraving/dom/harmony.cpp +++ b/src/engraving/dom/harmony.cpp @@ -26,11 +26,7 @@ #include "translation.h" #include "draw/fontmetrics.h" -#include "draw/types/brush.h" -#include "draw/types/pen.h" -#include "../editing/textedit.h" -#include "../editing/undo.h" #include "../editing/transpose.h" #include "chordlist.h" diff --git a/src/engraving/dom/lyrics.cpp b/src/engraving/dom/lyrics.cpp index 2dab93cfa642b..8c338d037a60e 100644 --- a/src/engraving/dom/lyrics.cpp +++ b/src/engraving/dom/lyrics.cpp @@ -25,7 +25,6 @@ #include "types/translatablestring.h" #include "../editing/textedit.h" -#include "../editing/undo.h" #include "measure.h" #include "navigate.h" diff --git a/src/engraving/dom/masterscore.cpp b/src/engraving/dom/masterscore.cpp index 1dc4f6a44851c..f1370997db1f2 100644 --- a/src/engraving/dom/masterscore.cpp +++ b/src/engraving/dom/masterscore.cpp @@ -25,6 +25,7 @@ #include "compat/writescorehook.h" #include "editing/editmeasures.h" +#include "editing/transaction/undostack.h" #include "rw/mscloader.h" #include "rw/xmlreader.h" #include "rw/rwregister.h" diff --git a/src/engraving/dom/playcounttext.cpp b/src/engraving/dom/playcounttext.cpp index 6211102eab876..72297d7d69115 100644 --- a/src/engraving/dom/playcounttext.cpp +++ b/src/engraving/dom/playcounttext.cpp @@ -23,13 +23,12 @@ #include "playcounttext.h" #include "../editing/textedit.h" -#include "../editing/undo.h" +#include "../editing/transaction/undostack.h" #include "../types/typesconv.h" #include "barline.h" #include "score.h" -using namespace mu; using namespace mu::engraving; static ElementStyle playCountStyle { diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index a95bfa9a59988..1d62dcc1d2b6e 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -37,6 +37,7 @@ #include "editing/editstavesharing.h" #include "editing/mscoreview.h" #include "editing/splitjoinmeasure.h" +#include "editing/transaction/undostack.h" #include "editing/transpose.h" #include "style/style.h" @@ -4340,7 +4341,7 @@ void Score::cmdSelectSection() // undo //--------------------------------------------------------- -void Score::undo(UndoCommand* cmd, EditData* ed) const +void Score::undo(UndoableCommand* cmd, EditData* ed) const { undoStack()->pushAndPerform(cmd, ed); } diff --git a/src/engraving/dom/score.h b/src/engraving/dom/score.h index 42f0c1f19eae5..a0f281f4249c0 100644 --- a/src/engraving/dom/score.h +++ b/src/engraving/dom/score.h @@ -148,7 +148,7 @@ class Text; class TimeSig; class TimeSigMap; class Tuplet; -class UndoCommand; +class UndoableCommand; class UndoStack; class ShadowNote; @@ -503,7 +503,7 @@ class Score : public EngravingObject, public muse::Contextable PropertyFlags propFlags = PropertyFlags::NOSTYLE); void undoPropertyChanged(EngravingObject*, Pid, const PropertyValue& v, PropertyFlags ps = PropertyFlags::NOSTYLE); virtual UndoStack* undoStack() const; - void undo(UndoCommand*, EditData* = nullptr) const; + void undo(UndoableCommand*, EditData* = nullptr) const; void undoRemoveMeasures(Measure*, Measure*, bool preserveTies = false, bool moveStaffTypeChanges = true); void undoChangeMeasureRepeatCount(Measure* m, int count, staff_idx_t staffIdx); void undoAddBracket(Staff* staff, size_t level, BracketType type, size_t span); diff --git a/src/engraving/dom/slur.cpp b/src/engraving/dom/slur.cpp index e6e76003a9836..5f3b49daff1f6 100644 --- a/src/engraving/dom/slur.cpp +++ b/src/engraving/dom/slur.cpp @@ -23,6 +23,7 @@ #include "../editing/editspanner.h" #include "../editing/mscoreview.h" +#include "../editing/transaction/undostack.h" #include "arpeggio.h" #include "beam.h" diff --git a/src/engraving/dom/system.cpp b/src/engraving/dom/system.cpp index 52653f81f8009..757724b8dbdf4 100644 --- a/src/engraving/dom/system.cpp +++ b/src/engraving/dom/system.cpp @@ -29,12 +29,10 @@ #include "style/style.h" -#include "actionicon.h" #include "beam.h" #include "box.h" #include "bracket.h" #include "bracketItem.h" -#include "chord.h" #include "chordrest.h" #include "factory.h" #include "measure.h" @@ -43,7 +41,6 @@ #include "part.h" #include "score.h" #include "segment.h" -#include "sig.h" #include "spacer.h" #include "spanner.h" #include "staff.h" @@ -51,8 +48,6 @@ #include "system.h" #include "systemdivider.h" -#include "tremolotwochord.h" - #ifndef ENGRAVING_NO_ACCESSIBILITY #include "accessibility/accessibleitem.h" #endif diff --git a/src/engraving/dom/textbase.cpp b/src/engraving/dom/textbase.cpp index a0a9d1fabc010..4e84961716fef 100644 --- a/src/engraving/dom/textbase.cpp +++ b/src/engraving/dom/textbase.cpp @@ -42,9 +42,7 @@ #endif #include "anchors.h" -#include "barline.h" #include "box.h" -#include "dynamic.h" #include "instrumentname.h" #include "measure.h" #include "mscore.h" @@ -52,7 +50,6 @@ #include "score.h" #include "../editing/textedit.h" -#include "../editing/undo.h" #include "log.h" diff --git a/src/engraving/dom/tie.cpp b/src/engraving/dom/tie.cpp index e9105cfad6699..6955da5be38d1 100644 --- a/src/engraving/dom/tie.cpp +++ b/src/engraving/dom/tie.cpp @@ -21,12 +21,7 @@ */ #include "tie.h" -#include - -#include "draw/types/transform.h" - #include "../editing/mscoreview.h" -#include "../editing/undo.h" #include "accidental.h" #include "barline.h" diff --git a/src/engraving/editing/addremoveelement.cpp b/src/engraving/editing/addremoveelement.cpp index e6993e9a2fcb4..57eeeaeec1016 100644 --- a/src/engraving/editing/addremoveelement.cpp +++ b/src/engraving/editing/addremoveelement.cpp @@ -144,13 +144,12 @@ const char* AddElement::name() const return buffer; } -bool AddElement::matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const +bool AddElement::matchesFilter(UndoableCommandFilter f, const EngravingItem* target) const { - using Filter = UndoCommand::Filter; switch (f) { - case Filter::AddElement: + case UndoableCommandFilter::AddElement: return target == element; - case Filter::AddElementLinked: + case UndoableCommandFilter::AddElementLinked: return muse::contains(target->linkList(), static_cast(element)); default: break; @@ -302,13 +301,12 @@ const char* RemoveElement::name() const return buffer; } -bool RemoveElement::matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const +bool RemoveElement::matchesFilter(UndoableCommandFilter f, const EngravingItem* target) const { - using Filter = UndoCommand::Filter; switch (f) { - case Filter::RemoveElement: + case UndoableCommandFilter::RemoveElement: return target == element; - case Filter::RemoveElementLinked: + case UndoableCommandFilter::RemoveElementLinked: return muse::contains(target->linkList(), static_cast(element)); default: break; @@ -456,10 +454,9 @@ Link::Link(EngravingObject* e1, EngravingObject* e2) e = e1; } -bool Link::matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const +bool Link::matchesFilter(UndoableCommandFilter f, const EngravingItem* target) const { - using Filter = UndoCommand::Filter; - if (f == Filter::Link) { + if (f == UndoableCommandFilter::Link) { return e == target || le->contains(const_cast(target)); } return false; diff --git a/src/engraving/editing/addremoveelement.h b/src/engraving/editing/addremoveelement.h index a04d6d98caa1d..63486fa38d092 100644 --- a/src/engraving/editing/addremoveelement.h +++ b/src/engraving/editing/addremoveelement.h @@ -22,11 +22,12 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" + #include "../dom/segment.h" namespace mu::engraving { -class AddElement : public UndoCommand +class AddElement : public UndoableCommand { OBJECT_ALLOCATOR(engraving, AddElement) @@ -42,14 +43,14 @@ class AddElement : public UndoCommand void cleanup(bool) override; const char* name() const override; - bool matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const override; + bool matchesFilter(UndoableCommandFilter f, const EngravingItem* target) const override; std::vector objectItems() const override; UNDO_TYPE(CommandType::AddElement) }; -class RemoveElement : public UndoCommand +class RemoveElement : public UndoableCommand { OBJECT_ALLOCATOR(engraving, RemoveElement) @@ -62,14 +63,14 @@ class RemoveElement : public UndoCommand void cleanup(bool) override; const char* name() const override; - bool matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const override; + bool matchesFilter(UndoableCommandFilter f, const EngravingItem* target) const override; std::vector objectItems() const override; UNDO_TYPE(CommandType::RemoveElement) }; -class ChangeElement : public UndoCommand +class ChangeElement : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeElement) @@ -86,7 +87,7 @@ class ChangeElement : public UndoCommand UNDO_CHANGED_OBJECTS({ oldElement, newElement }) }; -class ChangeParent : public UndoCommand +class ChangeParent : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeParent) @@ -105,7 +106,7 @@ class ChangeParent : public UndoCommand UNDO_CHANGED_OBJECTS({ element }) }; -class ChangeSegmentParent : public UndoCommand +class ChangeSegmentParent : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeSegmentParent) @@ -124,7 +125,7 @@ class ChangeSegmentParent : public UndoCommand UNDO_CHANGED_OBJECTS({ segment }) }; -class LinkUnlink : public UndoCommand +class LinkUnlink : public UndoableCommand { OBJECT_ALLOCATOR(engraving, LinkUnlink) @@ -166,6 +167,6 @@ class Link : public LinkUnlink UNDO_TYPE(CommandType::Link) UNDO_NAME("Link") - bool matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const override; + bool matchesFilter(UndoableCommandFilter f, const EngravingItem* target) const override; }; } diff --git a/src/engraving/editing/cmd.cpp b/src/engraving/editing/cmd.cpp index a32345c9ed2a2..610e272b046f0 100644 --- a/src/engraving/editing/cmd.cpp +++ b/src/engraving/editing/cmd.cpp @@ -91,12 +91,12 @@ #include "editspanner.h" #include "editstaff.h" #include "editsystemlocks.h" -#include "transpose.h" #include "mscoreview.h" +#include "transaction/undostack.h" +#include "transpose.h" #include "log.h" -using namespace mu; using namespace muse::io; using namespace mu::engraving; diff --git a/src/engraving/editing/edit.cpp b/src/engraving/editing/edit.cpp index 810b5df9d820b..776afb25b5edc 100644 --- a/src/engraving/editing/edit.cpp +++ b/src/engraving/editing/edit.cpp @@ -119,6 +119,7 @@ #include "inserttime.h" #include "mscoreview.h" #include "splitjoinmeasure.h" +#include "transaction/undostack.h" #include "transpose.h" #include "log.h" diff --git a/src/engraving/editing/editbrackets.h b/src/engraving/editing/editbrackets.h index 4116bda12ef0a..764e61e1e633b 100644 --- a/src/engraving/editing/editbrackets.h +++ b/src/engraving/editing/editbrackets.h @@ -22,12 +22,12 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/staff.h" namespace mu::engraving { -class RemoveBracket : public UndoCommand +class RemoveBracket : public UndoableCommand { OBJECT_ALLOCATOR(engraving, RemoveBracket) @@ -48,7 +48,7 @@ class RemoveBracket : public UndoCommand UNDO_CHANGED_OBJECTS({ staff }) }; -class AddBracket : public UndoCommand +class AddBracket : public UndoableCommand { OBJECT_ALLOCATOR(engraving, AddBracket) diff --git a/src/engraving/editing/editchord.h b/src/engraving/editing/editchord.h index 20f6995ca654b..c951534c2e75e 100644 --- a/src/engraving/editing/editchord.h +++ b/src/engraving/editing/editchord.h @@ -22,7 +22,9 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" + +#include "../dom/chord.h" #include "../dom/parenthesis.h" namespace mu::engraving { @@ -52,7 +54,7 @@ class EditChord static void doRemoveAllNoteParentheses(Chord* chord, Parenthesis* leftParen); }; -class ChangeChordStaffMove : public UndoCommand +class ChangeChordStaffMove : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeChordStaffMove) @@ -70,7 +72,7 @@ class ChangeChordStaffMove : public UndoCommand UNDO_CHANGED_OBJECTS({ chordRest }) }; -class SwapCR : public UndoCommand +class SwapCR : public UndoableCommand { OBJECT_ALLOCATOR(engraving, SwapCR) @@ -88,7 +90,7 @@ class SwapCR : public UndoCommand UNDO_CHANGED_OBJECTS({ cr1, cr2 }) }; -class ChangeSpanArpeggio : public UndoCommand +class ChangeSpanArpeggio : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeSpanArpeggio) @@ -104,7 +106,7 @@ class ChangeSpanArpeggio : public UndoCommand UNDO_CHANGED_OBJECTS({ m_chord }) }; -class AddNoteParenthesisInfo : public UndoCommand +class AddNoteParenthesisInfo : public UndoableCommand { OBJECT_ALLOCATOR(engraving, AddNoteParenthesisInfo) @@ -124,7 +126,7 @@ class AddNoteParenthesisInfo : public UndoCommand UNDO_TYPE(CommandType::AddNoteParenthesesInfo) }; -class RemoveNoteParenthesisInfo : public UndoCommand +class RemoveNoteParenthesisInfo : public UndoableCommand { OBJECT_ALLOCATOR(engraving, RemoveNoteParenthesisInfo) @@ -144,7 +146,7 @@ class RemoveNoteParenthesisInfo : public UndoCommand UNDO_TYPE(CommandType::RemoveNoteParenthesesInfo) }; -class RemoveSingleNoteParentheses : public UndoCommand +class RemoveSingleNoteParentheses : public UndoableCommand { OBJECT_ALLOCATOR(engraving, RemoveSingleNoteParentheses) diff --git a/src/engraving/editing/editclef.h b/src/engraving/editing/editclef.h index 9095dc1b28a56..6d5f917773877 100644 --- a/src/engraving/editing/editclef.h +++ b/src/engraving/editing/editclef.h @@ -22,12 +22,12 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/clef.h" namespace mu::engraving { -class ChangeClefType : public UndoCommand +class ChangeClefType : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeClefType) diff --git a/src/engraving/editing/editexcerpt.h b/src/engraving/editing/editexcerpt.h index b178bad8d72cb..82f1d61f12add 100644 --- a/src/engraving/editing/editexcerpt.h +++ b/src/engraving/editing/editexcerpt.h @@ -22,14 +22,14 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/excerpt.h" #include "../dom/masterscore.h" #include "../dom/part.h" namespace mu::engraving { -class AddExcerpt : public UndoCommand +class AddExcerpt : public UndoableCommand { OBJECT_ALLOCATOR(engraving, AddExcerpt) @@ -49,7 +49,7 @@ class AddExcerpt : public UndoCommand UNDO_NAME("AddExcerpt") }; -class RemoveExcerpt : public UndoCommand +class RemoveExcerpt : public UndoableCommand { OBJECT_ALLOCATOR(engraving, RemoveExcerpt) @@ -70,7 +70,7 @@ class RemoveExcerpt : public UndoCommand UNDO_NAME("RemoveExcerpt") }; -class SwapExcerpt : public UndoCommand +class SwapExcerpt : public UndoableCommand { OBJECT_ALLOCATOR(engraving, SwapExcerpt) @@ -89,7 +89,7 @@ class SwapExcerpt : public UndoCommand UNDO_CHANGED_OBJECTS({ score }) }; -class ChangeExcerptTitle : public UndoCommand +class ChangeExcerptTitle : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeExcerptTitle) @@ -106,7 +106,7 @@ class ChangeExcerptTitle : public UndoCommand UNDO_NAME("ChangeExcerptTitle") }; -class AddPartToExcerpt : public UndoCommand +class AddPartToExcerpt : public UndoableCommand { OBJECT_ALLOCATOR(engraving, AddPartToExcerpt) diff --git a/src/engraving/editing/editfretboarddiagram.h b/src/engraving/editing/editfretboarddiagram.h index a265abd5d83d5..1d5829c7b657a 100644 --- a/src/engraving/editing/editfretboarddiagram.h +++ b/src/engraving/editing/editfretboarddiagram.h @@ -22,7 +22,7 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/fret.h" @@ -51,7 +51,7 @@ class FretUndoData double m_userMag = 1.0; }; -class FretDataChange : public UndoCommand +class FretDataChange : public UndoableCommand { OBJECT_ALLOCATOR(engraving, FretDataChange) @@ -73,7 +73,7 @@ class FretDataChange : public UndoCommand UNDO_CHANGED_OBJECTS({ m_diagram }) }; -class FretDot : public UndoCommand +class FretDot : public UndoableCommand { OBJECT_ALLOCATOR(engraving, FretDot) @@ -96,7 +96,7 @@ class FretDot : public UndoCommand UNDO_CHANGED_OBJECTS({ diagram }) }; -class FretMarker : public UndoCommand +class FretMarker : public UndoableCommand { OBJECT_ALLOCATOR(engraving, FretMarker) @@ -117,7 +117,7 @@ class FretMarker : public UndoCommand UNDO_CHANGED_OBJECTS({ diagram }) }; -class FretBarre : public UndoCommand +class FretBarre : public UndoableCommand { OBJECT_ALLOCATOR(engraving, FretBarre) @@ -139,7 +139,7 @@ class FretBarre : public UndoCommand UNDO_CHANGED_OBJECTS({ diagram }) }; -class FretClear : public UndoCommand +class FretClear : public UndoableCommand { OBJECT_ALLOCATOR(engraving, FretClear) @@ -158,7 +158,7 @@ class FretClear : public UndoCommand UNDO_CHANGED_OBJECTS({ diagram }) }; -class AddFretDiagramToFretBox : public UndoCommand +class AddFretDiagramToFretBox : public UndoableCommand { OBJECT_ALLOCATOR(engraving, AddFretDiagramToFretBox) @@ -176,7 +176,7 @@ class AddFretDiagramToFretBox : public UndoCommand UNDO_CHANGED_OBJECTS({ m_fretDiagram }) }; -class RemoveFretDiagramFromFretBox : public UndoCommand +class RemoveFretDiagramFromFretBox : public UndoableCommand { OBJECT_ALLOCATOR(engraving, RemoveFretDiagramFromFretBox) diff --git a/src/engraving/editing/editharppedaldiagram.h b/src/engraving/editing/editharppedaldiagram.h index 8a11c20b06101..19ee1cfc437c1 100644 --- a/src/engraving/editing/editharppedaldiagram.h +++ b/src/engraving/editing/editharppedaldiagram.h @@ -22,12 +22,12 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/harppedaldiagram.h" namespace mu::engraving { -class ChangeHarpPedalState : public UndoCommand +class ChangeHarpPedalState : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeHarpPedalState) HarpPedalDiagram* diagram; @@ -44,7 +44,7 @@ class ChangeHarpPedalState : public UndoCommand std::vector objectItems() const override; }; -class ChangeSingleHarpPedal : public UndoCommand +class ChangeSingleHarpPedal : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeSingleHarpPedal) diff --git a/src/engraving/editing/editinstrumentchange.h b/src/engraving/editing/editinstrumentchange.h index f165f1ed37486..796529aa26cac 100644 --- a/src/engraving/editing/editinstrumentchange.h +++ b/src/engraving/editing/editinstrumentchange.h @@ -22,13 +22,13 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/instrchange.h" namespace mu::engraving { /// change instrument in an InstrumentChange element -class ChangeInstrument : public UndoCommand +class ChangeInstrument : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeInstrument) diff --git a/src/engraving/editing/editkeysig.h b/src/engraving/editing/editkeysig.h index 52232af8456ad..62eaa666a9c8d 100644 --- a/src/engraving/editing/editkeysig.h +++ b/src/engraving/editing/editkeysig.h @@ -22,13 +22,13 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/key.h" #include "../dom/keysig.h" namespace mu::engraving { -class ChangeKeySig : public UndoCommand +class ChangeKeySig : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeKeySig) diff --git a/src/engraving/editing/editmeasures.h b/src/engraving/editing/editmeasures.h index bc78ac274b303..4a3a8b02da6ce 100644 --- a/src/engraving/editing/editmeasures.h +++ b/src/engraving/editing/editmeasures.h @@ -21,12 +21,12 @@ */ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/measure.h" namespace mu::engraving { -class InsertRemoveMeasures : public UndoCommand +class InsertRemoveMeasures : public UndoableCommand { OBJECT_ALLOCATOR(engraving, InsertRemoveMeasures) @@ -75,7 +75,7 @@ class InsertMeasures : public InsertRemoveMeasures UNDO_NAME("InsertMeasures") }; -class ChangeMeasureLen : public UndoCommand +class ChangeMeasureLen : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeMeasureLen) @@ -92,7 +92,7 @@ class ChangeMeasureLen : public UndoCommand UNDO_CHANGED_OBJECTS({ measure }) }; -class ChangeMMRest : public UndoCommand +class ChangeMMRest : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeMMRest) @@ -110,7 +110,7 @@ class ChangeMMRest : public UndoCommand UNDO_CHANGED_OBJECTS({ m, mmrest }) }; -class ChangeMeasureRepeatCount : public UndoCommand +class ChangeMeasureRepeatCount : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeMeasureRepeatCount) diff --git a/src/engraving/editing/editnote.cpp b/src/engraving/editing/editnote.cpp index f0046e85487d0..a5c64c2a30c5a 100644 --- a/src/engraving/editing/editnote.cpp +++ b/src/engraving/editing/editnote.cpp @@ -38,16 +38,17 @@ #include "dom/score.h" #include "dom/staff.h" #include "dom/stringdata.h" -#include "dom/tapping.h" #include "dom/utils.h" +#include "editing/transaction/undostack.h" + using namespace mu::engraving; //--------------------------------------------------------- // ChangePitch //--------------------------------------------------------- -class ChangePitch : public UndoCommand +class ChangePitch : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangePitch) @@ -97,7 +98,7 @@ class ChangePitch : public UndoCommand // fret and string numbers for (potentially) all the notes of all the chords of a segment. //--------------------------------------------------------- -class ChangeFretting : public UndoCommand +class ChangeFretting : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeFretting) diff --git a/src/engraving/editing/editnote.h b/src/engraving/editing/editnote.h index 41425415ad507..fbe02353204f2 100644 --- a/src/engraving/editing/editnote.h +++ b/src/engraving/editing/editnote.h @@ -22,8 +22,9 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" +#include "../dom/chord.h" #include "../dom/note.h" namespace mu::engraving { @@ -51,7 +52,7 @@ class EditNote static void upDownChromatic(bool up, int pitch, Note* n, Key key, int tpc1, int tpc2, int& newPitch, int& newTpc1, int& newTpc2); }; -class ChangeVelocity : public UndoCommand +class ChangeVelocity : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeVelocity) @@ -68,7 +69,7 @@ class ChangeVelocity : public UndoCommand UNDO_CHANGED_OBJECTS({ note }) }; -class ChangeNoteEventList : public UndoCommand +class ChangeNoteEventList : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeNoteEventList) @@ -85,7 +86,7 @@ class ChangeNoteEventList : public UndoCommand UNDO_CHANGED_OBJECTS({ note }); }; -class ChangeNoteEvent : public UndoCommand +class ChangeNoteEvent : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeNoteEvent) @@ -103,7 +104,7 @@ class ChangeNoteEvent : public UndoCommand UNDO_CHANGED_OBJECTS({ note }) }; -class ChangeChordPlayEventType : public UndoCommand +class ChangeChordPlayEventType : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeChordPlayEventType) diff --git a/src/engraving/editing/editpart.h b/src/engraving/editing/editpart.h index dbfc591b111d0..b0710537e738a 100644 --- a/src/engraving/editing/editpart.h +++ b/src/engraving/editing/editpart.h @@ -22,7 +22,7 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../compat/midi/midipatch.h" #include "../dom/drumset.h" @@ -31,7 +31,7 @@ #include "../dom/score.h" namespace mu::engraving { -class InsertPart : public UndoCommand +class InsertPart : public UndoableCommand { OBJECT_ALLOCATOR(engraving, InsertPart) @@ -49,7 +49,7 @@ class InsertPart : public UndoCommand UNDO_CHANGED_OBJECTS({ m_part }) }; -class RemovePart : public UndoCommand +class RemovePart : public UndoableCommand { OBJECT_ALLOCATOR(engraving, RemovePart) @@ -67,7 +67,7 @@ class RemovePart : public UndoCommand UNDO_CHANGED_OBJECTS({ m_part }) }; -class SetSoloist : public UndoCommand +class SetSoloist : public UndoableCommand { OBJECT_ALLOCATOR(engraving, SetSoloist) @@ -84,7 +84,7 @@ class SetSoloist : public UndoCommand UNDO_CHANGED_OBJECTS({ part }) }; -class ChangePart : public UndoCommand +class ChangePart : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangePart) @@ -102,7 +102,7 @@ class ChangePart : public UndoCommand UNDO_CHANGED_OBJECTS({ part }) }; -class ChangeInstrumentLong : public UndoCommand +class ChangeInstrumentLong : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeInstrumentLong) @@ -120,7 +120,7 @@ class ChangeInstrumentLong : public UndoCommand UNDO_CHANGED_OBJECTS({ part }) }; -class ChangeInstrumentShort : public UndoCommand +class ChangeInstrumentShort : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeInstrumentShort) @@ -138,7 +138,7 @@ class ChangeInstrumentShort : public UndoCommand UNDO_CHANGED_OBJECTS({ part }) }; -class ChangeInstrumentGroupOptions : public UndoCommand +class ChangeInstrumentGroupOptions : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeInstrumentGroupOptions) @@ -158,7 +158,7 @@ class ChangeInstrumentGroupOptions : public UndoCommand UNDO_CHANGED_OBJECTS({ part }) }; -class ChangeInstrumentNumber : public UndoCommand +class ChangeInstrumentNumber : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeInstrumentNumber) @@ -176,7 +176,7 @@ class ChangeInstrumentNumber : public UndoCommand UNDO_CHANGED_OBJECTS({ part }) }; -class ChangeDrumset : public UndoCommand +class ChangeDrumset : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeDrumset) @@ -194,7 +194,7 @@ class ChangeDrumset : public UndoCommand UNDO_NAME("ChangeDrumset") }; -class ChangeStringData : public UndoCommand +class ChangeStringData : public UndoableCommand { Instrument* m_instrument = nullptr; StringTunings* m_stringTunings = nullptr; @@ -210,7 +210,7 @@ class ChangeStringData : public UndoCommand UNDO_NAME("ChangeStringData") }; -class ChangePatch : public UndoCommand +class ChangePatch : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangePatch) @@ -227,7 +227,7 @@ class ChangePatch : public UndoCommand UNDO_CHANGED_OBJECTS({ score }) }; -class SetUserBankController : public UndoCommand +class SetUserBankController : public UndoableCommand { OBJECT_ALLOCATOR(engraving, SetUserBankController) diff --git a/src/engraving/editing/editproperty.h b/src/engraving/editing/editproperty.h index 83a378b9437de..7cbff0d341653 100644 --- a/src/engraving/editing/editproperty.h +++ b/src/engraving/editing/editproperty.h @@ -22,12 +22,12 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/staff.h" namespace mu::engraving { -class ChangeProperty : public UndoCommand +class ChangeProperty : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeProperty) protected: @@ -51,9 +51,9 @@ class ChangeProperty : public UndoCommand std::vector objectItems() const override; - bool matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const override + bool matchesFilter(UndoableCommandFilter f, const EngravingItem* target) const override { - return f == UndoCommand::Filter::ChangePropertyLinked && muse::contains(target->linkList(), element); + return f == UndoableCommandFilter::ChangePropertyLinked && muse::contains(target->linkList(), element); } }; diff --git a/src/engraving/editing/editscoreproperties.h b/src/engraving/editing/editscoreproperties.h index d8625cfe059dd..3c883eb67e9b8 100644 --- a/src/engraving/editing/editscoreproperties.h +++ b/src/engraving/editing/editscoreproperties.h @@ -22,12 +22,12 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/score.h" namespace mu::engraving { -class ChangeMetaTags : public UndoCommand +class ChangeMetaTags : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeMetaTags) @@ -45,7 +45,7 @@ class ChangeMetaTags : public UndoCommand UNDO_CHANGED_OBJECTS({ score }) }; -class ChangeMetaText : public UndoCommand +class ChangeMetaText : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeMetaText) @@ -64,7 +64,7 @@ class ChangeMetaText : public UndoCommand UNDO_CHANGED_OBJECTS({ score }) }; -class ChangeScoreOrder : public UndoCommand +class ChangeScoreOrder : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeScoreOrder) @@ -81,7 +81,7 @@ class ChangeScoreOrder : public UndoCommand UNDO_CHANGED_OBJECTS({ score }) }; -class ChangePageNumberOffset : public UndoCommand +class ChangePageNumberOffset : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangePageNumberOffset) diff --git a/src/engraving/editing/editsoundflag.h b/src/engraving/editing/editsoundflag.h index f3c2342ed9bdd..397f61d79824e 100644 --- a/src/engraving/editing/editsoundflag.h +++ b/src/engraving/editing/editsoundflag.h @@ -22,12 +22,12 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/soundflag.h" namespace mu::engraving { -class ChangeSoundFlag : public UndoCommand +class ChangeSoundFlag : public UndoableCommand { SoundFlag* m_soundFlag = nullptr; SoundFlag::PresetCodes m_presets; diff --git a/src/engraving/editing/editspanner.h b/src/engraving/editing/editspanner.h index 8c954542a3a35..e6421010f0992 100644 --- a/src/engraving/editing/editspanner.h +++ b/src/engraving/editing/editspanner.h @@ -22,12 +22,12 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/spanner.h" namespace mu::engraving { -class ChangeSpannerElements : public UndoCommand +class ChangeSpannerElements : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeSpannerElements) @@ -46,7 +46,7 @@ class ChangeSpannerElements : public UndoCommand UNDO_CHANGED_OBJECTS({ spanner }) }; -class ChangeStartEndSpanner : public UndoCommand +class ChangeStartEndSpanner : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeStartEndSpanner) diff --git a/src/engraving/editing/editstaff.h b/src/engraving/editing/editstaff.h index f16b2db777db2..46a86dd2468a1 100644 --- a/src/engraving/editing/editstaff.h +++ b/src/engraving/editing/editstaff.h @@ -22,14 +22,14 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/measure.h" #include "../dom/score.h" #include "../dom/staff.h" namespace mu::engraving { -class InsertStaff : public UndoCommand +class InsertStaff : public UndoableCommand { OBJECT_ALLOCATOR(engraving, InsertStaff) @@ -47,7 +47,7 @@ class InsertStaff : public UndoCommand UNDO_CHANGED_OBJECTS({ staff }) }; -class RemoveStaff : public UndoCommand +class RemoveStaff : public UndoableCommand { OBJECT_ALLOCATOR(engraving, RemoveStaff) @@ -66,7 +66,7 @@ class RemoveStaff : public UndoCommand UNDO_CHANGED_OBJECTS({ staff }) }; -class AddSystemObjectStaff : public UndoCommand +class AddSystemObjectStaff : public UndoableCommand { OBJECT_ALLOCATOR(engraving, AddSystemObjectStaff) @@ -82,7 +82,7 @@ class AddSystemObjectStaff : public UndoCommand UNDO_CHANGED_OBJECTS({ staff }) }; -class RemoveSystemObjectStaff : public UndoCommand +class RemoveSystemObjectStaff : public UndoableCommand { OBJECT_ALLOCATOR(engraving, RemoveSystemObjectStaff) @@ -98,7 +98,7 @@ class RemoveSystemObjectStaff : public UndoCommand UNDO_CHANGED_OBJECTS({ staff }) }; -class InsertMStaff : public UndoCommand +class InsertMStaff : public UndoableCommand { OBJECT_ALLOCATOR(engraving, InsertMStaff) @@ -116,7 +116,7 @@ class InsertMStaff : public UndoCommand UNDO_CHANGED_OBJECTS({ measure }) }; -class RemoveMStaff : public UndoCommand +class RemoveMStaff : public UndoableCommand { OBJECT_ALLOCATOR(engraving, RemoveMStaff) @@ -134,7 +134,7 @@ class RemoveMStaff : public UndoCommand UNDO_CHANGED_OBJECTS({ measure }) }; -class InsertStaves : public UndoCommand +class InsertStaves : public UndoableCommand { OBJECT_ALLOCATOR(engraving, InsertStaves) @@ -152,7 +152,7 @@ class InsertStaves : public UndoCommand UNDO_CHANGED_OBJECTS({ measure }) }; -class RemoveStaves : public UndoCommand +class RemoveStaves : public UndoableCommand { OBJECT_ALLOCATOR(engraving, RemoveStaves) @@ -170,7 +170,7 @@ class RemoveStaves : public UndoCommand UNDO_CHANGED_OBJECTS({ measure }) }; -class SortStaves : public UndoCommand +class SortStaves : public UndoableCommand { OBJECT_ALLOCATOR(engraving, SortStaves) @@ -192,7 +192,7 @@ class SortStaves : public UndoCommand // ChangeStaff //--------------------------------------------------------- -class ChangeStaff : public UndoCommand +class ChangeStaff : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeStaff) @@ -223,7 +223,7 @@ class ChangeStaff : public UndoCommand // ChangeStaffType //--------------------------------------------------------- -class ChangeStaffType : public UndoCommand +class ChangeStaffType : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeStaffType) @@ -242,7 +242,7 @@ class ChangeStaffType : public UndoCommand UNDO_CHANGED_OBJECTS({ staff }) }; -class ChangeMStaffProperties : public UndoCommand +class ChangeMStaffProperties : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeMStaffProperties) @@ -261,7 +261,7 @@ class ChangeMStaffProperties : public UndoCommand UNDO_CHANGED_OBJECTS({ measure }) }; -class ChangeMStaffHideIfEmpty : public UndoCommand +class ChangeMStaffHideIfEmpty : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeMStaffHideIfEmpty) diff --git a/src/engraving/editing/editstavesharing.cpp b/src/engraving/editing/editstavesharing.cpp index e7e9db5af6db1..1d222a1509ba3 100644 --- a/src/engraving/editing/editstavesharing.cpp +++ b/src/engraving/editing/editstavesharing.cpp @@ -19,6 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + #include "editstavesharing.h" #include "dom/factory.h" @@ -28,7 +29,66 @@ #include "dom/sharedpart.h" #include "dom/staff.h" -namespace mu::engraving { +#include "transaction/undoablecommand.h" + +using namespace mu::engraving; + +namespace { +//------------------------------------------------------------------- +// Undo commands +//------------------------------------------------------------------- + +class ConnectSharedPart : public UndoableCommand +{ + OBJECT_ALLOCATOR(engraving, ConnectSharedPart) + + SharedPart* sharedPart = nullptr; + Part* originPart = nullptr; + +public: + ConnectSharedPart(SharedPart* s, Part* o) + : sharedPart(s), originPart(o) {} + + void undo(EditData*) override + { + sharedPart->removeOriginPart(originPart); + } + + void redo(EditData*) override + { + sharedPart->addOriginPart(originPart); + } + + UNDO_TYPE(CommandType::ConnectSharedPart) + UNDO_NAME("Connect shared part") +}; + +class DisconnectSharedPart : public UndoableCommand +{ + OBJECT_ALLOCATOR(engraving, DisconnectSharedPart) + + SharedPart* sharedPart = nullptr; + Part* originPart = nullptr; + +public: + DisconnectSharedPart(SharedPart* s, Part* o) + : sharedPart(s), originPart(o) {} + + void undo(EditData*) override + { + sharedPart->addOriginPart(originPart); + } + + void redo(EditData*) override + { + sharedPart->removeOriginPart(originPart); + } + + UNDO_TYPE(CommandType::DisconnectSharedPart) + UNDO_NAME("Disconnect shared part") +}; +} + //------------------------------------------------------------------- // EditStaveSharing //------------------------------------------------------------------- @@ -220,28 +280,3 @@ void EditStaveSharing::handleRemovePart(Part* part) } } } - -//------------------------------------------------------------------- -// Undo commands -//------------------------------------------------------------------- - -void ConnectSharedPart::undo(EditData*) -{ - sharedPart->removeOriginPart(originPart); -} - -void ConnectSharedPart::redo(EditData*) -{ - sharedPart->addOriginPart(originPart); -} - -void DisconnectSharedPart::undo(EditData*) -{ - sharedPart->addOriginPart(originPart); -} - -void DisconnectSharedPart::redo(EditData*) -{ - sharedPart->removeOriginPart(originPart); -} -} diff --git a/src/engraving/editing/editstavesharing.h b/src/engraving/editing/editstavesharing.h index 36e68e512ee54..5568837845ef9 100644 --- a/src/engraving/editing/editstavesharing.h +++ b/src/engraving/editing/editstavesharing.h @@ -21,12 +21,15 @@ */ #pragma once -#include "undo.h" +#include namespace mu::engraving { class Instrument; class KeyList; +class Part; +class Score; class SharedPart; +class StaffType; using StaveSharingGroup = std::vector; using StaveSharingGroups = std::vector; @@ -49,40 +52,4 @@ class EditStaveSharing static void connectSharedPart(SharedPart* sharedPart, Part* originPart); static void disconnectSharedPart(SharedPart* sharedPart, Part* originPart); }; - -class ConnectSharedPart : public UndoCommand -{ - OBJECT_ALLOCATOR(engraving, ConnectSharedPart) - - SharedPart* sharedPart = nullptr; - Part* originPart = nullptr; - -public: - ConnectSharedPart(SharedPart* s, Part* o) - : sharedPart(s), originPart(o) {} - - void undo(EditData*) override; - void redo(EditData*) override; - - UNDO_TYPE(CommandType::ConnectSharedPart) - UNDO_NAME("Connect shared part") -}; - -class DisconnectSharedPart : public UndoCommand -{ - OBJECT_ALLOCATOR(engraving, DisconnectSharedPart) - - SharedPart* sharedPart = nullptr; - Part* originPart = nullptr; - -public: - DisconnectSharedPart(SharedPart* s, Part* o) - : sharedPart(s), originPart(o) {} - - void undo(EditData*) override; - void redo(EditData*) override; - - UNDO_TYPE(CommandType::DisconnectSharedPart) - UNDO_NAME("Disconnect shared part") -}; } diff --git a/src/engraving/editing/editstyle.cpp b/src/engraving/editing/editstyle.cpp index 12bac0daeee18..ea606e57fe7d8 100644 --- a/src/engraving/editing/editstyle.cpp +++ b/src/engraving/editing/editstyle.cpp @@ -94,7 +94,7 @@ void ChangeStyle::flip(EditData*) void ChangeStyle::undo(EditData* ed) { overlap = false; - UndoCommand::undo(ed); + UndoableCommand::undo(ed); } //--------------------------------------------------------- diff --git a/src/engraving/editing/editstyle.h b/src/engraving/editing/editstyle.h index 21de827062d05..f3502f56815ef 100644 --- a/src/engraving/editing/editstyle.h +++ b/src/engraving/editing/editstyle.h @@ -22,12 +22,12 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/score.h" namespace mu::engraving { -class ChangeStyle : public UndoCommand +class ChangeStyle : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeStyle) @@ -48,7 +48,7 @@ class ChangeStyle : public UndoCommand UNDO_CHANGED_OBJECTS({ score }) }; -class ChangeStyleValues : public UndoCommand +class ChangeStyleValues : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeStyleValues) diff --git a/src/engraving/editing/editsystemlocks.cpp b/src/engraving/editing/editsystemlocks.cpp index a48a730c04101..d910d7de26c34 100644 --- a/src/engraving/editing/editsystemlocks.cpp +++ b/src/engraving/editing/editsystemlocks.cpp @@ -22,7 +22,7 @@ #include "editsystemlocks.h" -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/measurebase.h" #include "../dom/score.h" @@ -35,7 +35,7 @@ using namespace mu::engraving; // AddSystemLock //--------------------------------------------------------- -class AddSystemLock : public UndoCommand +class AddSystemLock : public UndoableCommand { OBJECT_ALLOCATOR(engraving, AddSystemLock) @@ -76,7 +76,7 @@ class AddSystemLock : public UndoCommand // RemoveSystemLock //--------------------------------------------------------- -class RemoveSystemLock : public UndoCommand +class RemoveSystemLock : public UndoableCommand { OBJECT_ALLOCATOR(engraving, RemoveSystemLock) diff --git a/src/engraving/editing/edittie.h b/src/engraving/editing/edittie.h index 4d63e834c6f29..041600a6c73d7 100644 --- a/src/engraving/editing/edittie.h +++ b/src/engraving/editing/edittie.h @@ -22,22 +22,26 @@ #pragma once -#include "undo.h" +#include "global/allocator.h" +#include "global/types/string.h" + +#include "transaction/undoablecommand.h" namespace mu::engraving { class TieJumpPointList; -class ChangeTieJumpPointActive : public UndoCommand + +class ChangeTieJumpPointActive : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeTieJumpPointActive) TieJumpPointList* m_jumpPointList = nullptr; - String m_id; + muse::String m_id; bool m_active = false; void flip(EditData*) override; public: - ChangeTieJumpPointActive(TieJumpPointList* jumpPointList, String& id, bool active) + ChangeTieJumpPointActive(TieJumpPointList* jumpPointList, muse::String id, bool active) : m_jumpPointList(jumpPointList), m_id(id), m_active(active) {} UNDO_TYPE(CommandType::ChangeTieEndPointActive) diff --git a/src/engraving/editing/edittremolo.h b/src/engraving/editing/edittremolo.h index 4a1a74fa49530..7bc7e0e83a68f 100644 --- a/src/engraving/editing/edittremolo.h +++ b/src/engraving/editing/edittremolo.h @@ -22,12 +22,12 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/tremolotwochord.h" namespace mu::engraving { -class MoveTremolo : public UndoCommand +class MoveTremolo : public UndoableCommand { OBJECT_ALLOCATOR(engraving, MoveTremolo) diff --git a/src/engraving/editing/exchangevoices.cpp b/src/engraving/editing/exchangevoices.cpp index 752308c867282..3b1986cdeab8c 100644 --- a/src/engraving/editing/exchangevoices.cpp +++ b/src/engraving/editing/exchangevoices.cpp @@ -31,12 +31,12 @@ #include "../types/types.h" #include "clonevoice.h" -#include "undo.h" +#include "transaction/undoablecommand.h" using namespace mu::engraving; namespace { -class ExchangeVoicesInMeasure : public UndoCommand +class ExchangeVoicesInMeasure : public UndoableCommand { OBJECT_ALLOCATOR(engraving, ExchangeVoicesInMeasure) diff --git a/src/engraving/editing/inserttime.h b/src/engraving/editing/inserttime.h index a0373a980b254..0c610154e9d13 100644 --- a/src/engraving/editing/inserttime.h +++ b/src/engraving/editing/inserttime.h @@ -22,12 +22,12 @@ #pragma once -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/score.h" namespace mu::engraving { -class InsertTime : public UndoCommand +class InsertTime : public UndoableCommand { OBJECT_ALLOCATOR(engraving, InsertTime) @@ -47,7 +47,7 @@ class InsertTime : public UndoCommand UNDO_CHANGED_OBJECTS({ score }) }; -class InsertTimeUnmanagedSpanner : public UndoCommand +class InsertTimeUnmanagedSpanner : public UndoableCommand { OBJECT_ALLOCATOR(engraving, InsertTimeUnmanagedSpanner) diff --git a/src/engraving/editing/textedit.cpp b/src/engraving/editing/textedit.cpp index 3fa85a5aadae3..2e1a10c722be8 100644 --- a/src/engraving/editing/textedit.cpp +++ b/src/engraving/editing/textedit.cpp @@ -23,7 +23,7 @@ #include "textedit.h" #include "mscoreview.h" -#include "undo.h" +#include "transaction/undostack.h" #include "iengravingfont.h" #include "types/symnames.h" @@ -156,14 +156,12 @@ void TextBase::endEdit(EditData& ed) // replace all undo/redo records collected during text editing with // one property change - using Filter = UndoCommand::Filter; - //! NOTE: Current index can be less than the start index if the text element is newly added and immediately removed through //! undo (the "add element" command will have been popped from the stack before the calling of this method)... const bool textWasEdited = undo->currentIndex() > ted->startUndoIdx; if (textWasEdited) { undo->mergeTransactions(ted->startUndoIdx); - undo->last()->removeCommandsMatchingFilter(Filter::TextEdit, this); + undo->last()->removeCommandsMatchingFilter(UndoableCommandFilter::TextEdit, this); } else { // No text changes in "undo" part of undo stack, // hence nothing to merge and filter. @@ -178,7 +176,7 @@ void TextBase::endEdit(EditData& ed) const bool newlyAdded = ted->oldXmlText.isEmpty(); if (newlyAdded) { const UndoableTransaction* transaction = textWasEdited ? undo->prev() : undo->last(); - if (transaction && transaction->hasCommandsMatchingFilter(Filter::AddElement, this)) { + if (transaction && transaction->hasCommandsMatchingFilter(UndoableCommandFilter::AddElement, this)) { // We have just added this element to a score. // Combine undo records of text creation with text editing. undo->mergeTransactions(ted->startUndoIdx - 1); @@ -1090,7 +1088,7 @@ void ChangeTextProperties::restoreSelection() } ChangeTextProperties::ChangeTextProperties(const TextCursor* tc, Pid propId, const PropertyValue& propVal, PropertyFlags flags_) - : TextEditUndoCommand(*tc) + : TextEditUndoableCommand(*tc) { m_propertyId = propId; m_propertyVal = propVal; diff --git a/src/engraving/editing/textedit.h b/src/engraving/editing/textedit.h index 0c15723d1b6ef..59822979c8ddc 100644 --- a/src/engraving/editing/textedit.h +++ b/src/engraving/editing/textedit.h @@ -23,7 +23,7 @@ #pragma once #include "elementeditdata.h" -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/textbase.h" @@ -62,24 +62,24 @@ struct TextEditData : public ElementEditData { }; //--------------------------------------------------------- -// TextEditUndoCommand +// TextEditUndoableCommand //--------------------------------------------------------- -class TextEditUndoCommand : public UndoCommand +class TextEditUndoableCommand : public UndoableCommand { - OBJECT_ALLOCATOR(engraving, TextEditUndoCommand) + OBJECT_ALLOCATOR(engraving, TextEditUndoableCommand) public: UNDO_TYPE(CommandType::TextEdit) UNDO_NAME("TextEdit") UNDO_CHANGED_OBJECTS({ m_cursor.text() }) - TextEditUndoCommand(const TextCursor& tc) + TextEditUndoableCommand(const TextCursor& tc) : m_cursor(tc) {} - bool matchesFilter(UndoCommand::Filter f, const EngravingItem* target) const override + bool matchesFilter(UndoableCommandFilter f, const EngravingItem* target) const override { - return f == UndoCommand::Filter::TextEdit && m_cursor.text() == target; + return f == UndoableCommandFilter::TextEdit && m_cursor.text() == target; } TextCursor& cursor() { return m_cursor; } @@ -93,7 +93,7 @@ class TextEditUndoCommand : public UndoCommand // ChangeText //--------------------------------------------------------- -class ChangeTextProperties : public TextEditUndoCommand +class ChangeTextProperties : public TextEditUndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeTextProperties) @@ -117,13 +117,13 @@ class ChangeTextProperties : public TextEditUndoCommand // ChangeText //--------------------------------------------------------- -class ChangeText : public TextEditUndoCommand +class ChangeText : public TextEditUndoableCommand { OBJECT_ALLOCATOR(engraving, ChangeText) public: ChangeText(const TextCursor* tc, const String& t) - : TextEditUndoCommand(*tc), m_s(t), m_format(*tc->format()) {} + : TextEditUndoableCommand(*tc), m_s(t), m_format(*tc->format()) {} virtual void undo(EditData*) override = 0; virtual void redo(EditData*) override = 0; const String& string() const { return m_s; } @@ -172,7 +172,7 @@ class RemoveText : public ChangeText // SplitJoinText //--------------------------------------------------------- -class SplitJoinText : public TextEditUndoCommand +class SplitJoinText : public TextEditUndoableCommand { OBJECT_ALLOCATOR(engraving, SplitJoinText) protected: @@ -181,7 +181,7 @@ class SplitJoinText : public TextEditUndoCommand public: SplitJoinText(const TextCursor* tc) - : TextEditUndoCommand(*tc) {} + : TextEditUndoableCommand(*tc) {} }; //--------------------------------------------------------- diff --git a/src/engraving/editing/transaction/undoablecommand.cpp b/src/engraving/editing/transaction/undoablecommand.cpp new file mode 100644 index 0000000000000..b8393bc81df06 --- /dev/null +++ b/src/engraving/editing/transaction/undoablecommand.cpp @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2026 MuseScore Limited and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "undoablecommand.h" + +#include "dom/chord.h" +#include "dom/fret.h" +#include "dom/harmony.h" +#include "dom/note.h" + +using namespace mu::engraving; + +std::vector mu::engraving::compoundObjects(EngravingObject* object) +{ + std::vector objects; + + if (object->isChord()) { + const Chord* chord = toChord(object); + for (const Note* note : chord->notes()) { + for (Note* compoundNote : note->compoundNotes()) { + objects.push_back(compoundNote); + } + } + } else if (object->isNote()) { + const Note* note = toNote(object); + for (Note* compoundNote : note->compoundNotes()) { + objects.push_back(compoundNote); + } + } else if (object->isFretDiagram()) { + const FretDiagram* fret = toFretDiagram(object); + if (Harmony* harmony = fret->harmony()) { + objects.push_back(harmony); + } + } + + objects.push_back(object); + + return objects; +} + +void UndoableCommand::undo(EditData* ed) +{ + flip(ed); +} + +void UndoableCommand::redo(EditData* ed) +{ + flip(ed); +} diff --git a/src/engraving/editing/transaction/undoablecommand.h b/src/engraving/editing/transaction/undoablecommand.h new file mode 100644 index 0000000000000..975cfc99dcd06 --- /dev/null +++ b/src/engraving/editing/transaction/undoablecommand.h @@ -0,0 +1,191 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2026 MuseScore Limited and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +namespace mu::engraving { +class EditData; +class EngravingItem; +class EngravingObject; + +enum class CommandType : signed char { + Unknown = -1, + + // Parts + InsertPart, + RemovePart, + AddPartToExcerpt, + SetSoloist, + ChangePart, + ConnectSharedPart, + DisconnectSharedPart, + + // Staves + InsertStaff, + RemoveStaff, + AddSystemObjectStaff, + RemoveSystemObjectStaff, + SortStaves, + ChangeStaff, + ChangeStaffType, + + // MStaves + InsertMStaff, + RemoveMStaff, + InsertStaves, + RemoveStaves, + ChangeMStaffProperties, + ChangeMStaffHideIfEmpty, + + // Instruments + ChangeInstrumentShort, + ChangeInstrumentLong, + ChangeInstrumentGroupOptions, + ChangeInstrumentNumber, + ChangeInstrument, + ChangeDrumset, + + // Measures + RemoveMeasures, + InsertMeasures, + ChangeMeasureLen, + ChangeMMRest, + ChangeMeasureRepeatCount, + + // Elements + AddElement, + RemoveElement, + Unlink, + Link, + ChangeElement, + ChangeParent, + + // Notes + ChangePitch, + ChangeFretting, + ChangeVelocity, + + // ChordRest + ChangeChordStaffMove, + SwapCR, + AddNoteParenthesesInfo, + RemoveNoteParenthesesInfo, + RemoveSingleNoteParentheses, + + // Brackets + RemoveBracket, + AddBracket, + + // Fret + FretDataChange, + FretDot, + FretMarker, + FretBarre, + FretClear, + AddFretDiagramToFretBox, + RemoveFretDiagramFromFretBox, + + // Harmony + TransposeHarmony, + + // KeySig + ChangeKeySig, + + // Clef + ChangeClefType, + + // Tremolo + MoveTremolo, + + // Spanners + ChangeSpannerElements, + InsertTimeUnmanagedSpanner, + ChangeStartEndSpanner, + + // Ties + ChangeTieEndPointActive, + + // Style + ChangeStyle, + ChangeStyleValues, + + // Property + ChangeProperty, + + // Voices + ExchangeVoice, + + // Excerpts + AddExcerpt, + RemoveExcerpt, + SwapExcerpt, + ChangeExcerptTitle, + + // Meta info + ChangeMetaInfo, + + // Text + TextEdit, + + // Other + InsertTime, + ChangeScoreOrder, +}; + +enum class UndoableCommandFilter : unsigned char { + TextEdit, + AddElement, + AddElementLinked, + Link, + RemoveElement, + RemoveElementLinked, + ChangePropertyLinked, +}; + +#define UNDO_TYPE(t) CommandType type() const override { return t; } +#define UNDO_NAME(a) const char* name() const override { return a; } +#define UNDO_CHANGED_OBJECTS(...) std::vector objectItems() const override { return __VA_ARGS__; } + +class UndoableCommand +{ +public: + virtual ~UndoableCommand() = default; + + virtual void undo(EditData*); + virtual void redo(EditData*); + + virtual void cleanup(bool /*undo*/) {} + + virtual std::vector objectItems() const { return {}; } + virtual const char* name() const { return "UndoableCommand"; } + virtual CommandType type() const { return CommandType::Unknown; } + + virtual bool matchesFilter(UndoableCommandFilter, const EngravingItem* /* target */) const { return false; } + +protected: + virtual void flip(EditData*) {} +}; + +std::vector compoundObjects(EngravingObject* object); +} diff --git a/src/engraving/editing/undo.cpp b/src/engraving/editing/transaction/undostack.cpp similarity index 87% rename from src/engraving/editing/undo.cpp rename to src/engraving/editing/transaction/undostack.cpp index ffa5e22663ec9..b6b5ea549cbc0 100644 --- a/src/engraving/editing/undo.cpp +++ b/src/engraving/editing/transaction/undostack.cpp @@ -32,17 +32,14 @@ between startUndo() and endUndo(). */ -#include "undo.h" +#include "undostack.h" #include "containers.h" -#include "editproperty.h" -#include "editstyle.h" -#include "textedit.h" - -#include "../dom/fret.h" -#include "../dom/harmony.h" -#include "../dom/note.h" +#include "editing/editproperty.h" +#include "editing/editstyle.h" +#include "editing/textedit.h" +#include "editing/transaction/undoablecommand.h" #include "log.h" @@ -85,44 +82,6 @@ static const std::unordered_map COMMAND_TYPE_INVERSION { CommandType::DisconnectSharedPart, CommandType::ConnectSharedPart }, }; -std::vector compoundObjects(EngravingObject* object) -{ - std::vector objects; - - if (object->isChord()) { - const Chord* chord = toChord(object); - for (const Note* note : chord->notes()) { - for (Note* compoundNote : note->compoundNotes()) { - objects.push_back(compoundNote); - } - } - } else if (object->isNote()) { - const Note* note = toNote(object); - for (Note* compoundNote : note->compoundNotes()) { - objects.push_back(compoundNote); - } - } else if (object->isFretDiagram()) { - const FretDiagram* fret = toFretDiagram(object); - if (fret->harmony()) { - objects.push_back(fret->harmony()); - } - } - - objects.push_back(object); - - return objects; -} - -void UndoCommand::undo(EditData* ed) -{ - flip(ed); -} - -void UndoCommand::redo(EditData* ed) -{ - flip(ed); -} - //--------------------------------------------------------- // UndoStack //--------------------------------------------------------- @@ -169,7 +128,7 @@ void UndoStack::beginTransaction(Score* score, const TranslatableString& actionN m_activeTransaction = new UndoableTransaction(score, actionName); } -void UndoStack::pushAndPerform(UndoCommand* cmd, EditData* ed) +void UndoStack::pushAndPerform(UndoableCommand* cmd, EditData* ed) { if (!m_activeTransaction) { // this can happen for layout() outside of a transaction (load) @@ -182,7 +141,7 @@ void UndoStack::pushAndPerform(UndoCommand* cmd, EditData* ed) return; } #ifndef QT_NO_DEBUG - if (!strcmp(cmd->name(), "ChangeProperty")) { + if (cmd->type() == CommandType::ChangeProperty) { ChangeProperty* cp = static_cast(cmd); LOG_UNDO() << cmd->name() << " id: " << int(cp->getId()) << ", property: " << propertyName(cp->getId()); } else { @@ -193,7 +152,7 @@ void UndoStack::pushAndPerform(UndoCommand* cmd, EditData* ed) cmd->redo(ed); } -void UndoStack::pushWithoutPerforming(UndoCommand* cmd) +void UndoStack::pushWithoutPerforming(UndoableCommand* cmd) { if (!m_activeTransaction) { if (!ScoreLoad::loading()) { @@ -212,7 +171,7 @@ void UndoStack::remove(size_t idx) while (m_transactions.size() > m_currentIndex) { UndoableTransaction* transaction = muse::takeLast(m_transactions); m_states.pop_back(); - transaction->cleanup(false); // delete elements for which UndoCommand() holds ownership + transaction->cleanup(false); // delete elements for which UndoableCommand() holds ownership delete transaction; // --curIdx; } @@ -259,7 +218,7 @@ void UndoStack::endTransaction(bool rollback) while (m_transactions.size() > m_currentIndex) { UndoableTransaction* transaction = muse::takeLast(m_transactions); m_states.pop_back(); - transaction->cleanup(false); // delete elements for which UndoCommand() holds ownership + transaction->cleanup(false); // delete elements for which UndoableCommand() holds ownership delete transaction; } m_transactions.push_back(m_activeTransaction); @@ -324,14 +283,14 @@ UndoableTransaction::UndoableTransaction(Score* s, const TranslatableString& act UndoableTransaction::~UndoableTransaction() { - for (UndoCommand* command : m_commands) { + for (UndoableCommand* command : m_commands) { delete command; } } void UndoableTransaction::cleanup(bool undo) { - for (UndoCommand* command : m_commands) { + for (UndoableCommand* command : m_commands) { command->cleanup(undo); } } @@ -339,7 +298,7 @@ void UndoableTransaction::cleanup(bool undo) void UndoableTransaction::unwind() { while (!m_commands.empty()) { - UndoCommand* command = muse::takeLast(m_commands); + UndoableCommand* command = muse::takeLast(m_commands); LOG_UNDO() << "unwind: " << command->name(); command->undo(nullptr); delete command; @@ -352,9 +311,9 @@ void UndoableTransaction::appendCommands(UndoableTransaction& other) other.m_commands.clear(); } -bool UndoableTransaction::hasCommandsMatchingFilter(UndoCommand::Filter f, const EngravingItem* target) const +bool UndoableTransaction::hasCommandsMatchingFilter(UndoableCommandFilter f, const EngravingItem* target) const { - for (UndoCommand* command : m_commands) { + for (UndoableCommand* command : m_commands) { if (command->matchesFilter(f, target)) { return true; } @@ -362,12 +321,12 @@ bool UndoableTransaction::hasCommandsMatchingFilter(UndoCommand::Filter f, const return false; } -bool UndoableTransaction::hasCommandsNotMatchingFilters(const std::vector& filters, +bool UndoableTransaction::hasCommandsNotMatchingFilters(const std::vector& filters, const EngravingItem* target) const { - for (UndoCommand* command : m_commands) { + for (UndoableCommand* command : m_commands) { bool filtered = false; - for (UndoCommand::Filter f : filters) { + for (UndoableCommandFilter f : filters) { if (command->matchesFilter(f, target)) { filtered = true; break; @@ -380,10 +339,10 @@ bool UndoableTransaction::hasCommandsNotMatchingFilters(const std::vector acceptedList; - for (UndoCommand* command : m_commands) { + std::vector acceptedList; + for (UndoableCommand* command : m_commands) { if (command->matchesFilter(f, target)) { delete command; } else { @@ -462,7 +421,7 @@ void UndoableTransaction::redo(EditData* ed) fillSelectionInfo(m_undoSelectionInfo, m_score->selection()); m_score->deselectAll(); - for (UndoCommand* command : m_commands) { + for (UndoableCommand* command : m_commands) { LOG_UNDO() << "<" << command->name() << ">"; command->redo(ed); } @@ -518,7 +477,7 @@ UndoableTransaction::ChangesInfo UndoableTransaction::changesInfo(bool undo) con { ChangesInfo result; - for (const UndoCommand* command : commands()) { + for (const UndoableCommand* command : commands()) { CommandType type = command->type(); if (type == CommandType::ChangeProperty) { @@ -534,7 +493,7 @@ UndoableTransaction::ChangesInfo UndoableTransaction::changesInfo(bool undo) con const StyleIdSet styleIds = changeStyle->changedIds(); result.changedStyleIdSet.insert(styleIds.cbegin(), styleIds.cend()); } else if (type == CommandType::TextEdit) { - result.isTextEditing |= static_cast(command)->cursor().editing(); + result.isTextEditing |= static_cast(command)->cursor().editing(); } if (undo) { diff --git a/src/engraving/editing/undo.h b/src/engraving/editing/transaction/undostack.h similarity index 56% rename from src/engraving/editing/undo.h rename to src/engraving/editing/transaction/undostack.h index ccb2be354e9da..a90978b098f2b 100644 --- a/src/engraving/editing/undo.h +++ b/src/engraving/editing/transaction/undostack.h @@ -27,171 +27,18 @@ #include "global/containers.h" #include "global/types/translatablestring.h" -#include "editdata.h" -#include "../dom/input.h" +#include "../../dom/input.h" namespace mu::engraving { class EngravingObject; class Score; class Segment; -enum class CommandType : signed char { - Unknown = -1, - - // Parts - InsertPart, - RemovePart, - AddPartToExcerpt, - SetSoloist, - ChangePart, - ConnectSharedPart, - DisconnectSharedPart, - - // Staves - InsertStaff, - RemoveStaff, - AddSystemObjectStaff, - RemoveSystemObjectStaff, - SortStaves, - ChangeStaff, - ChangeStaffType, - - // MStaves - InsertMStaff, - RemoveMStaff, - InsertStaves, - RemoveStaves, - ChangeMStaffProperties, - ChangeMStaffHideIfEmpty, - - // Instruments - ChangeInstrumentShort, - ChangeInstrumentLong, - ChangeInstrumentGroupOptions, - ChangeInstrumentNumber, - ChangeInstrument, - ChangeDrumset, - - // Measures - RemoveMeasures, - InsertMeasures, - ChangeMeasureLen, - ChangeMMRest, - ChangeMeasureRepeatCount, - - // Elements - AddElement, - RemoveElement, - Unlink, - Link, - ChangeElement, - ChangeParent, - - // Notes - ChangePitch, - ChangeFretting, - ChangeVelocity, - - // ChordRest - ChangeChordStaffMove, - SwapCR, - AddNoteParenthesesInfo, - RemoveNoteParenthesesInfo, - RemoveSingleNoteParentheses, - - // Brackets - RemoveBracket, - AddBracket, - - // Fret - FretDataChange, - FretDot, - FretMarker, - FretBarre, - FretClear, - AddFretDiagramToFretBox, - RemoveFretDiagramFromFretBox, - - // Harmony - TransposeHarmony, - - // KeySig - ChangeKeySig, - - // Clef - ChangeClefType, - - // Tremolo - MoveTremolo, - - // Spanners - ChangeSpannerElements, - InsertTimeUnmanagedSpanner, - ChangeStartEndSpanner, - - // Ties - ChangeTieEndPointActive, - - // Style - ChangeStyle, - ChangeStyleValues, - - // Property - ChangeProperty, - - // Voices - ExchangeVoice, - - // Excerpts - AddExcerpt, - RemoveExcerpt, - SwapExcerpt, - ChangeExcerptTitle, - - // Meta info - ChangeMetaInfo, - - // Text - TextEdit, - - // Other - InsertTime, - ChangeScoreOrder, -}; - -#define UNDO_TYPE(t) CommandType type() const override { return t; } -#define UNDO_NAME(a) const char* name() const override { return a; } -#define UNDO_CHANGED_OBJECTS(...) std::vector objectItems() const override { return __VA_ARGS__; } - -class UndoCommand -{ -public: - virtual ~UndoCommand() = default; - - virtual void undo(EditData*); - virtual void redo(EditData*); - - virtual void cleanup(bool /*undo*/) {} +class EditData; - virtual std::vector objectItems() const { return {}; } - virtual const char* name() const { return "UndoCommand"; } - virtual CommandType type() const { return CommandType::Unknown; } - - enum class Filter : unsigned char { - TextEdit, - AddElement, - AddElementLinked, - Link, - RemoveElement, - RemoveElementLinked, - ChangePropertyLinked, - }; - - virtual bool matchesFilter(Filter, const EngravingItem* /* target */) const { return false; } - -protected: - virtual void flip(EditData*) {} -}; +class UndoableCommand; +enum class UndoableCommandFilter : unsigned char; +enum class CommandType : signed char; class UndoableTransaction { @@ -202,18 +49,18 @@ class UndoableTransaction void undo(EditData*); void redo(EditData*); - void appendCommand(UndoCommand* cmd) { m_commands.push_back(cmd); } + void appendCommand(UndoableCommand* cmd) { m_commands.push_back(cmd); } void append(UndoableTransaction&& other); void unwind(); void cleanup(bool undo); - const std::vector& commands() const { return m_commands; } + const std::vector& commands() const { return m_commands; } bool empty() const { return m_commands.empty(); } - bool hasCommandsMatchingFilter(UndoCommand::Filter, const EngravingItem* target) const; - bool hasCommandsNotMatchingFilters(const std::vector& filters, const EngravingItem* target) const; - void removeCommandsMatchingFilter(UndoCommand::Filter f, EngravingItem* target); + bool hasCommandsMatchingFilter(UndoableCommandFilter, const EngravingItem* target) const; + bool hasCommandsNotMatchingFilters(const std::vector& filters, const EngravingItem* target) const; + void removeCommandsMatchingFilter(UndoableCommandFilter f, EngravingItem* target); const InputState& undoInputState() const; const InputState& redoInputState() const; @@ -248,7 +95,7 @@ class UndoableTransaction private: void appendCommands(UndoableTransaction& other); - std::vector m_commands; + std::vector m_commands; InputState m_undoInputState; InputState m_redoInputState; @@ -276,8 +123,8 @@ class UndoStack void beginTransaction(Score*, const muse::TranslatableString& actionName); void endTransaction(bool rollback); - void pushAndPerform(UndoCommand*, EditData*); - void pushWithoutPerforming(UndoCommand*); + void pushAndPerform(UndoableCommand*, EditData*); + void pushWithoutPerforming(UndoableCommand*); bool canUndo() const { return m_currentIndex > 0; } bool canRedo() const { return m_currentIndex < m_transactions.size(); } @@ -318,6 +165,4 @@ class UndoStack size_t m_currentIndex = 0; bool m_isLocked = false; }; - -std::vector compoundObjects(EngravingObject* object); } diff --git a/src/engraving/editing/transpose.cpp b/src/engraving/editing/transpose.cpp index c1b357d5e5bc5..ff914460acc33 100644 --- a/src/engraving/editing/transpose.cpp +++ b/src/engraving/editing/transpose.cpp @@ -734,7 +734,7 @@ int Transpose::transposeTpc(int tpc, Interval interval, bool useDoubleSharpsFlat //--------------------------------------------------------- namespace { -class TransposeHarmony : public UndoCommand +class TransposeHarmony : public UndoableCommand { OBJECT_ALLOCATOR(engraving, TransposeHarmony) @@ -784,7 +784,7 @@ void Transpose::undoTransposeHarmony(Score* score, Harmony* harmony, Interval in //--------------------------------------------------------- namespace { -class TransposeHarmonyDiatonic : public UndoCommand +class TransposeHarmonyDiatonic : public UndoableCommand { OBJECT_ALLOCATOR(engraving, TransposeHarmonyDiatonic) diff --git a/src/engraving/playback/playbackmodel.cpp b/src/engraving/playback/playbackmodel.cpp index 69b7a01417099..1dcb07b08ac26 100644 --- a/src/engraving/playback/playbackmodel.cpp +++ b/src/engraving/playback/playbackmodel.cpp @@ -22,8 +22,6 @@ #include "playbackmodel.h" -#include - #include "dom/fret.h" #include "dom/harmony.h" #include "dom/instrument.h" @@ -36,7 +34,8 @@ #include "dom/segment.h" #include "dom/tie.h" #include "dom/tremolotwochord.h" -#include "editing/undo.h" + +#include "editing/transaction/undoablecommand.h" #include "defer.h" #include "log.h" diff --git a/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.cpp b/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.cpp index 9b8cf991a2567..849318b5e1be8 100644 --- a/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.cpp +++ b/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.cpp @@ -23,7 +23,8 @@ #include "notation/inotation.h" -#include "engraving/editing/undo.h" +#include "engraving/editing/transaction/undoablecommand.h" +#include "engraving/editing/transaction/undostack.h" #include "log.h" @@ -79,7 +80,7 @@ void EngravingUndoStackModel::load(const UndoableTransaction* transaction, Item* { TRACEFUNC; - for (const UndoCommand* childCommand : transaction->commands()) { + for (const UndoableCommand* childCommand : transaction->commands()) { createItem(parent, childCommand); } } @@ -220,15 +221,15 @@ EngravingUndoStackModel::Item* EngravingUndoStackModel::createItem( } EngravingUndoStackModel::Item* EngravingUndoStackModel::createItem( - Item* parent, const UndoCommand* undoCommand, bool isCurrent) + Item* parent, const UndoableCommand* command, bool isCurrent) { Item* item = new Item(parent); m_allItems.insert(item->key(), item); - if (undoCommand) { + if (command) { QVariantMap data; - data["text"] = QString(undoCommand->name()); - data["color"] = colorForPointer(undoCommand); + data["text"] = QString(command->name()); + data["color"] = colorForPointer(command); data["isCurrent"] = isCurrent; item->setData(data); diff --git a/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.h b/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.h index 8a8f0a460b8e5..af628a50751e3 100644 --- a/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.h +++ b/src/engraving/qml/MuseScore/Engraving/devtools/engravingundostackmodel.h @@ -31,7 +31,7 @@ #include "modularity/ioc.h" namespace mu::engraving { -class UndoCommand; +class UndoableCommand; class UndoableTransaction; class EngravingUndoStackModel : public QAbstractItemModel, public muse::async::Asyncable, public muse::Contextable @@ -83,7 +83,7 @@ class EngravingUndoStackModel : public QAbstractItemModel, public muse::async::A void onNotationChanged(); Item* createItem(Item* parent, const engraving::UndoableTransaction* transaction, bool isCurrent = false); - Item* createItem(Item* parent, const engraving::UndoCommand* undoCommand, bool isCurrent = false); + Item* createItem(Item* parent, const engraving::UndoableCommand* command, bool isCurrent = false); Item* itemByModelIndex(const QModelIndex& index) const; QVariantMap makeData(const mu::engraving::EngravingObject* el) const; diff --git a/src/engraving/rendering/score/boxlayout.cpp b/src/engraving/rendering/score/boxlayout.cpp index 46b98ae33e295..0bdf03c501a95 100644 --- a/src/engraving/rendering/score/boxlayout.cpp +++ b/src/engraving/rendering/score/boxlayout.cpp @@ -27,7 +27,6 @@ #include "dom/text.h" #include "draw/fontmetrics.h" -#include "editing/editfretboarddiagram.h" #include "tlayout.h" using namespace mu::engraving; diff --git a/src/engraving/rendering/score/chordlayout.cpp b/src/engraving/rendering/score/chordlayout.cpp index 7a39e31a66da3..6eeed77f0b030 100644 --- a/src/engraving/rendering/score/chordlayout.cpp +++ b/src/engraving/rendering/score/chordlayout.cpp @@ -56,7 +56,6 @@ #include "dom/tremolosinglechord.h" #include "dom/tremolotwochord.h" #include "dom/utils.h" -#include "editing/undo.h" #include "editing/editchord.h" #include "accidentalslayout.h" diff --git a/src/engraving/rendering/score/layoutcontext.cpp b/src/engraving/rendering/score/layoutcontext.cpp index bbb458294fc92..25c2239e29f1f 100644 --- a/src/engraving/rendering/score/layoutcontext.cpp +++ b/src/engraving/rendering/score/layoutcontext.cpp @@ -455,7 +455,7 @@ void DomAccessor::undoRemoveElement(EngravingItem* item) score()->undoRemoveElement(item); } -void DomAccessor::undo(UndoCommand* cmd, EditData* ed) const +void DomAccessor::undo(UndoableCommand* cmd, EditData* ed) const { IF_ASSERT_FAILED(score()) { return; diff --git a/src/engraving/rendering/score/layoutcontext.h b/src/engraving/rendering/score/layoutcontext.h index 7d45e3cd27bf5..8fbeaa96a7eb3 100644 --- a/src/engraving/rendering/score/layoutcontext.h +++ b/src/engraving/rendering/score/layoutcontext.h @@ -70,7 +70,7 @@ class ChordRest; class Segment; struct PaddingTable; -class UndoCommand; +class UndoableCommand; class EditData; class Selection; @@ -202,7 +202,7 @@ class DomAccessor void undoAddElement(EngravingItem* item, bool addToLinkedStaves = true, bool ctrlModifier = false); void doUndoRemoveElement(EngravingItem* item); void undoRemoveElement(EngravingItem* item); - void undo(UndoCommand* cmd, EditData* ed = nullptr) const; + void undo(UndoableCommand* cmd, EditData* ed = nullptr) const; void addElement(EngravingItem* item); void removeElement(EngravingItem* item); void updateSystemLocksOnCreateMMRest(Measure* first, Measure* last); diff --git a/src/engraving/rw/read206/read206.cpp b/src/engraving/rw/read206/read206.cpp index fd22b50edd339..ba85823e6ba4a 100644 --- a/src/engraving/rw/read206/read206.cpp +++ b/src/engraving/rw/read206/read206.cpp @@ -101,7 +101,6 @@ #include "dom/tuplet.h" #include "dom/utils.h" #include "dom/volta.h" -#include "editing/undo.h" #include "editing/transpose.h" #include "../compat/readchordlisthook.h" diff --git a/src/engraving/rw/read400/readcontext.cpp b/src/engraving/rw/read400/readcontext.cpp index 41f183d1d3916..04f08b89b86eb 100644 --- a/src/engraving/rw/read400/readcontext.cpp +++ b/src/engraving/rw/read400/readcontext.cpp @@ -29,7 +29,7 @@ #include "engraving/dom/score.h" #include "engraving/dom/staff.h" #include "engraving/dom/tuplet.h" -#include "engraving/editing/undo.h" +#include "engraving/editing/transaction/undostack.h" #include "engraving/types/types.h" #include "connectorinforeader.h" diff --git a/src/engraving/rw/read410/readcontext.cpp b/src/engraving/rw/read410/readcontext.cpp index e769f4625b70d..d6f9c1b459527 100644 --- a/src/engraving/rw/read410/readcontext.cpp +++ b/src/engraving/rw/read410/readcontext.cpp @@ -26,13 +26,11 @@ #include "dom/score.h" #include "dom/staff.h" #include "dom/trill.h" -#include "editing/undo.h" #include "connectorinforeader.h" #include "log.h" -using namespace mu; using namespace mu::engraving; using namespace mu::engraving::read410; diff --git a/src/engraving/rw/read460/readcontext.cpp b/src/engraving/rw/read460/readcontext.cpp index 43533395001fe..951ee0e102040 100644 --- a/src/engraving/rw/read460/readcontext.cpp +++ b/src/engraving/rw/read460/readcontext.cpp @@ -26,13 +26,11 @@ #include "dom/masterscore.h" #include "dom/score.h" #include "dom/trill.h" -#include "editing/undo.h" #include "connectorinforeader.h" #include "log.h" -using namespace mu; using namespace mu::engraving; using namespace mu::engraving::read460; diff --git a/src/engraving/rw/read500/readcontext.cpp b/src/engraving/rw/read500/readcontext.cpp index 8a90a42cd02f0..1d5849b1ffa92 100644 --- a/src/engraving/rw/read500/readcontext.cpp +++ b/src/engraving/rw/read500/readcontext.cpp @@ -26,13 +26,11 @@ #include "dom/masterscore.h" #include "dom/score.h" #include "dom/trill.h" -#include "editing/undo.h" #include "connectorinforeader.h" #include "log.h" -using namespace mu; using namespace mu::engraving; using namespace mu::engraving::read500; diff --git a/src/engraving/tests/box_tests.cpp b/src/engraving/tests/box_tests.cpp index 67fe7c9a248ed..230fa777c3300 100644 --- a/src/engraving/tests/box_tests.cpp +++ b/src/engraving/tests/box_tests.cpp @@ -25,7 +25,7 @@ #include "engraving/dom/box.h" #include "engraving/dom/masterscore.h" #include "engraving/dom/system.h" -#include "engraving/editing/undo.h" +#include "engraving/editing/transaction/undostack.h" #include "utils/scorerw.h" #include "utils/scorecomp.h" diff --git a/src/engraving/tests/breath_tests.cpp b/src/engraving/tests/breath_tests.cpp index 52619bc9d9b5c..f40f636b71bcd 100644 --- a/src/engraving/tests/breath_tests.cpp +++ b/src/engraving/tests/breath_tests.cpp @@ -25,7 +25,7 @@ #include "engraving/dom/breath.h" #include "engraving/dom/factory.h" #include "engraving/dom/masterscore.h" -#include "engraving/editing/undo.h" +#include "engraving/editing/transaction/undostack.h" #include "utils/scorerw.h" #include "utils/scorecomp.h" diff --git a/src/engraving/tests/earlymusic_tests.cpp b/src/engraving/tests/earlymusic_tests.cpp index 5548070d34b9b..0914d94fefa97 100644 --- a/src/engraving/tests/earlymusic_tests.cpp +++ b/src/engraving/tests/earlymusic_tests.cpp @@ -28,6 +28,7 @@ #include "engraving/dom/masterscore.h" #include "engraving/dom/measure.h" #include "engraving/editing/editstyle.h" +#include "engraving/editing/transaction/undostack.h" #include "utils/scorerw.h" #include "utils/scorecomp.h" diff --git a/src/engraving/tests/exchangevoices_tests.cpp b/src/engraving/tests/exchangevoices_tests.cpp index 4a6e4c7530d73..30e580739e744 100644 --- a/src/engraving/tests/exchangevoices_tests.cpp +++ b/src/engraving/tests/exchangevoices_tests.cpp @@ -29,7 +29,7 @@ #include "engraving/dom/segment.h" #include "engraving/dom/score.h" #include "engraving/editing/exchangevoices.h" -#include "engraving/editing/undo.h" +#include "engraving/editing/transaction/undostack.h" #include "utils/scorerw.h" #include "utils/scorecomp.h" diff --git a/src/engraving/tests/implodeexplode_tests.cpp b/src/engraving/tests/implodeexplode_tests.cpp index 4fb66ae784ff5..ae3c2da59af1b 100644 --- a/src/engraving/tests/implodeexplode_tests.cpp +++ b/src/engraving/tests/implodeexplode_tests.cpp @@ -24,7 +24,7 @@ #include "engraving/dom/masterscore.h" #include "engraving/editing/implodeexplode.h" -#include "engraving/editing/undo.h" +#include "engraving/editing/transaction/undostack.h" #include "utils/scorerw.h" #include "utils/scorecomp.h" diff --git a/src/engraving/tests/keysig_tests.cpp b/src/engraving/tests/keysig_tests.cpp index 71912fc33e3d6..a357ff79035cb 100644 --- a/src/engraving/tests/keysig_tests.cpp +++ b/src/engraving/tests/keysig_tests.cpp @@ -26,7 +26,7 @@ #include "engraving/dom/masterscore.h" #include "engraving/dom/measure.h" #include "engraving/dom/part.h" -#include "engraving/editing/undo.h" +#include "engraving/editing/transaction/undostack.h" #include "engraving/editing/transpose.h" #include "utils/scorerw.h" diff --git a/src/engraving/tests/measure_tests.cpp b/src/engraving/tests/measure_tests.cpp index 789f2bfc1da6a..83ef84cd99cca 100644 --- a/src/engraving/tests/measure_tests.cpp +++ b/src/engraving/tests/measure_tests.cpp @@ -26,11 +26,10 @@ #include "engraving/dom/excerpt.h" #include "engraving/dom/masterscore.h" #include "engraving/dom/measure.h" -#include "engraving/dom/measurenumber.h" #include "engraving/dom/rest.h" #include "engraving/dom/segment.h" #include "engraving/editing/splitjoinmeasure.h" -#include "engraving/editing/undo.h" +#include "engraving/editing/transaction/undostack.h" #include "utils/scorerw.h" #include "utils/scorecomp.h" diff --git a/src/engraving/tests/readwriteundoreset_tests.cpp b/src/engraving/tests/readwriteundoreset_tests.cpp index a486354184e3b..f32866e4db71e 100644 --- a/src/engraving/tests/readwriteundoreset_tests.cpp +++ b/src/engraving/tests/readwriteundoreset_tests.cpp @@ -22,7 +22,6 @@ #include #include "engraving/dom/masterscore.h" -#include "engraving/editing/undo.h" #include "utils/scorerw.h" #include "utils/scorecomp.h" diff --git a/src/engraving/tests/spanners_tests.cpp b/src/engraving/tests/spanners_tests.cpp index f40a3c011aa2f..ad6e6e5fb2ddf 100644 --- a/src/engraving/tests/spanners_tests.cpp +++ b/src/engraving/tests/spanners_tests.cpp @@ -35,6 +35,7 @@ #include "engraving/dom/staff.h" #include "engraving/dom/system.h" #include "engraving/editing/editexcerpt.h" +#include "engraving/editing/transaction/undostack.h" #include "engraving/api/v1/score.h" #include "engraving/api/v1/elements.h" diff --git a/src/engraving/tests/textbase_tests.cpp b/src/engraving/tests/textbase_tests.cpp index d61c61db8e12a..b956e4ff9a07f 100644 --- a/src/engraving/tests/textbase_tests.cpp +++ b/src/engraving/tests/textbase_tests.cpp @@ -31,6 +31,7 @@ #include "engraving/dom/segment.h" #include "engraving/dom/stafftext.h" #include "engraving/editing/textedit.h" +#include "engraving/editing/transaction/undostack.h" #include "utils/scorerw.h" #include "utils/scorecomp.h" diff --git a/src/engraving/tests/timesig_tests.cpp b/src/engraving/tests/timesig_tests.cpp index d1e12d2e1b806..7d97fd9ca9b55 100644 --- a/src/engraving/tests/timesig_tests.cpp +++ b/src/engraving/tests/timesig_tests.cpp @@ -29,7 +29,7 @@ #include "engraving/dom/measure.h" #include "engraving/dom/note.h" #include "engraving/dom/timesig.h" -#include "engraving/editing/undo.h" +#include "engraving/editing/transaction/undostack.h" #include "utils/scorerw.h" #include "utils/scorecomp.h" diff --git a/src/engraving/tests/tools_tests.cpp b/src/engraving/tests/tools_tests.cpp index 8dd41eac98dce..7b85158059eda 100644 --- a/src/engraving/tests/tools_tests.cpp +++ b/src/engraving/tests/tools_tests.cpp @@ -24,7 +24,7 @@ #include "engraving/dom/masterscore.h" #include "engraving/dom/measure.h" -#include "engraving/editing/undo.h" +#include "engraving/editing/transaction/undostack.h" #include "utils/scorerw.h" #include "utils/scorecomp.h" diff --git a/src/engraving/tests/transpose_tests.cpp b/src/engraving/tests/transpose_tests.cpp index 23146f27c5990..865b51a0520c7 100644 --- a/src/engraving/tests/transpose_tests.cpp +++ b/src/engraving/tests/transpose_tests.cpp @@ -23,7 +23,7 @@ #include #include "engraving/dom/masterscore.h" -#include "engraving/editing/undo.h" +#include "engraving/editing/transaction/undostack.h" #include "engraving/editing/transpose.h" #include "utils/scorerw.h" diff --git a/src/importexport/musicxml/internal/export/exportmusicxml.cpp b/src/importexport/musicxml/internal/export/exportmusicxml.cpp index 73441da9972f1..0bd96b4672783 100644 --- a/src/importexport/musicxml/internal/export/exportmusicxml.cpp +++ b/src/importexport/musicxml/internal/export/exportmusicxml.cpp @@ -125,7 +125,6 @@ #include "engraving/dom/utils.h" #include "engraving/dom/volta.h" #include "engraving/dom/whammybar.h" -#include "engraving/editing/undo.h" #include "../shared/musicxmlfonthandler.h" #include "../shared/musicxmlsupport.h" diff --git a/src/instrumentsscene/qml/MuseScore/InstrumentsScene/internal/systemobjectslayertreeitem.cpp b/src/instrumentsscene/qml/MuseScore/InstrumentsScene/internal/systemobjectslayertreeitem.cpp index 5f4e0daabc070..88f6639dd0e5e 100644 --- a/src/instrumentsscene/qml/MuseScore/InstrumentsScene/internal/systemobjectslayertreeitem.cpp +++ b/src/instrumentsscene/qml/MuseScore/InstrumentsScene/internal/systemobjectslayertreeitem.cpp @@ -23,7 +23,7 @@ #include "systemobjectslayertreeitem.h" #include "engraving/dom/timesig.h" -#include "engraving/editing/undo.h" +#include "engraving/editing/transaction/undoablecommand.h" #include "layoutpanelutils.h" #include "translation.h" diff --git a/src/notation/internal/masternotation.cpp b/src/notation/internal/masternotation.cpp index b24bbec05b95b..bc3cfddf4c829 100644 --- a/src/notation/internal/masternotation.cpp +++ b/src/notation/internal/masternotation.cpp @@ -21,7 +21,6 @@ */ #include "masternotation.h" -#include #include #include "log.h" @@ -39,10 +38,8 @@ #include "engraving/dom/measure.h" #include "engraving/dom/box.h" #include "engraving/dom/keysig.h" -#include "engraving/dom/rest.h" #include "engraving/dom/sig.h" #include "engraving/dom/tempotext.h" -#include "engraving/editing/undo.h" #include "excerptnotation.h" #include "masternotationparts.h" diff --git a/src/notation/internal/notationundostack.cpp b/src/notation/internal/notationundostack.cpp index 9becf06a976ab..290bc9eab1271 100644 --- a/src/notation/internal/notationundostack.cpp +++ b/src/notation/internal/notationundostack.cpp @@ -25,7 +25,7 @@ #include "log.h" #include "engraving/dom/masterscore.h" -#include "engraving/editing/undo.h" +#include "engraving/editing/transaction/undostack.h" using namespace mu::notation; using namespace muse::async; diff --git a/src/notationscene/qml/MuseScore/NotationScene/elementpopups/partialtiepopupmodel.cpp b/src/notationscene/qml/MuseScore/NotationScene/elementpopups/partialtiepopupmodel.cpp index 8d6c6cd4c34c3..605c6473aa578 100644 --- a/src/notationscene/qml/MuseScore/NotationScene/elementpopups/partialtiepopupmodel.cpp +++ b/src/notationscene/qml/MuseScore/NotationScene/elementpopups/partialtiepopupmodel.cpp @@ -24,7 +24,6 @@ #include "engraving/dom/partialtie.h" #include "engraving/dom/tie.h" -#include "engraving/editing/undo.h" using namespace mu::notation; using namespace mu::engraving; diff --git a/src/notationscene/qml/MuseScore/NotationScene/percussionpanel/percussionpanelmodel.cpp b/src/notationscene/qml/MuseScore/NotationScene/percussionpanel/percussionpanelmodel.cpp index 97eef8565d070..6ebd7ab61a776 100644 --- a/src/notationscene/qml/MuseScore/NotationScene/percussionpanel/percussionpanelmodel.cpp +++ b/src/notationscene/qml/MuseScore/NotationScene/percussionpanel/percussionpanelmodel.cpp @@ -33,7 +33,6 @@ #include "engraving/dom/factory.h" #include "engraving/dom/utils.h" -#include "engraving/editing/undo.h" static const QString PAD_NAMES_CODE("percussion-pad-names"); static const QString NOTATION_PREVIEW_CODE("percussion-notation-preview"); diff --git a/src/notationscene/widgets/editstaff.cpp b/src/notationscene/widgets/editstaff.cpp index 31a09ac743ade..1040238f98ae1 100644 --- a/src/notationscene/widgets/editstaff.cpp +++ b/src/notationscene/widgets/editstaff.cpp @@ -43,7 +43,7 @@ #include "engraving/dom/system.h" #include "engraving/dom/text.h" #include "engraving/dom/utils.h" -#include "engraving/editing/undo.h" +#include "engraving/editing/transaction/undostack.h" #include "log.h" diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/emptystaves/emptystavesvisiblitysettingsmodel.cpp b/src/propertiespanel/qml/MuseScore/PropertiesPanel/emptystaves/emptystavesvisiblitysettingsmodel.cpp index 42a617998103d..1afcb6e5c4e29 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/emptystaves/emptystavesvisiblitysettingsmodel.cpp +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/emptystaves/emptystavesvisiblitysettingsmodel.cpp @@ -24,7 +24,6 @@ #include "engraving/dom/measure.h" #include "engraving/dom/score.h" #include "engraving/dom/system.h" -#include "engraving/editing/undo.h" #include "engraving/rendering/score/systemlayout.h" using namespace mu::propertiespanel; From 17570e0b022f70711c3b55afa310723f515b5d83 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Sun, 24 May 2026 14:45:09 +0200 Subject: [PATCH 04/18] Make the `deleteLater` concept MasterScore-wide It was already kind of, but not completely. --- src/engraving/dom/engravingitem.cpp | 2 +- src/engraving/dom/score.cpp | 4 +- src/engraving/dom/score.h | 2 - src/engraving/editing/cmd.cpp | 55 +++++++++++++++----------- src/engraving/editing/cmd.h | 5 ++- src/engraving/editing/editmeasures.cpp | 4 +- src/engraving/rw/read206/read206.cpp | 4 +- src/engraving/rw/read400/tread.cpp | 4 +- src/engraving/rw/read410/tread.cpp | 4 +- 9 files changed, 48 insertions(+), 36 deletions(-) diff --git a/src/engraving/dom/engravingitem.cpp b/src/engraving/dom/engravingitem.cpp index 12f2ae1aa0329..b8a639ff82713 100644 --- a/src/engraving/dom/engravingitem.cpp +++ b/src/engraving/dom/engravingitem.cpp @@ -362,7 +362,7 @@ void EngravingItem::deleteLater() if (selected()) { score()->deselect(this); } - masterScore()->deleteLater(this); + masterScore()->cmdState().deleteLater(this); } //--------------------------------------------------------- diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index 1d62dcc1d2b6e..a9079a109f10f 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -1560,7 +1560,7 @@ void Score::removeElement(EngravingItem* element) } muse::remove(m_systems, system); - deleteLater(system); + system->deleteLater(); if (page && page->systems().empty()) { // Remove this page, since it is now empty. @@ -1568,7 +1568,7 @@ void Score::removeElement(EngravingItem* element) PointF pos = page->pos(); auto ii = std::find(pages().begin(), pages().end(), page); pages().erase(ii); - deleteLater(page); + page->deleteLater(); while (ii != pages().end()) { page = *ii; diff --git a/src/engraving/dom/score.h b/src/engraving/dom/score.h index a0f281f4249c0..4b3c62f8755f7 100644 --- a/src/engraving/dom/score.h +++ b/src/engraving/dom/score.h @@ -612,8 +612,6 @@ class Score : public EngravingObject, public muse::Contextable bool selectionEmpty() const { return m_selection.staffStart() == m_selection.staffEnd(); } bool selectionChanged() const { return m_updateState.selectionChanged; } void setSelectionChanged(bool val) { m_updateState.selectionChanged = val; } - void deleteLater(EngravingObject* e) { m_updateState.deleteList.push_back(e); } - void deletePostponed(); void changeSelectedElementsVoice(voice_idx_t); void changeSelectedElementsVoiceAssignment(VoiceAssignment); diff --git a/src/engraving/editing/cmd.cpp b/src/engraving/editing/cmd.cpp index 610e272b046f0..80f4e448124b5 100644 --- a/src/engraving/editing/cmd.cpp +++ b/src/engraving/editing/cmd.cpp @@ -335,6 +335,38 @@ void CmdState::setUpdateMode(UpdateMode m) } } +//--------------------------------------------------------- +// deleteLater +//--------------------------------------------------------- + +void CmdState::deleteLater(EngravingObject* e) +{ + m_postponedDeletions.push_back(e); +} + +std::vector CmdState::takePostponedDeletions() +{ + std::vector result; + result.swap(m_postponedDeletions); + return result; +} + +static void deletePostponed(CmdState& cmdState) +{ + for (EngravingObject* e : cmdState.takePostponedDeletions()) { + if (e->isSystem()) { + System* s = toSystem(e); + std::list spanners = s->spannerSegments(); + for (SpannerSegment* ss : spanners) { + if (ss->system() == s) { + ss->setSystem(0); + } + } + } + delete e; + } +} + //--------------------------------------------------------- // startCmd /// Start a GUI command by clearing the redraw area @@ -476,7 +508,7 @@ void Score::update(bool resetCmdState, bool layoutAllParts) { MasterScore* ms = masterScore(); CmdState& cs = ms->cmdState(); - ms->deletePostponed(); + deletePostponed(cs); if (cs.layoutRange()) { for (Score* s : ms->scoreList()) { @@ -531,27 +563,6 @@ void Score::lockUpdates(bool locked) m_updatesLocked = locked; } -//--------------------------------------------------------- -// deletePostponed -//--------------------------------------------------------- - -void Score::deletePostponed() -{ - for (EngravingObject* e : m_updateState.deleteList) { - if (e->isSystem()) { - System* s = toSystem(e); - std::list spanners = s->spannerSegments(); - for (SpannerSegment* ss : spanners) { - if (ss->system() == s) { - ss->setSystem(0); - } - } - } - } - muse::DeleteAll(m_updateState.deleteList); - m_updateState.deleteList.clear(); -} - //--------------------------------------------------------- // cmdAddSpanner // drop VOLTA, OTTAVA, TRILL, PEDAL, DYNAMIC diff --git a/src/engraving/editing/cmd.h b/src/engraving/editing/cmd.h index 1e4e722afd751..51d0cafaafdc8 100644 --- a/src/engraving/editing/cmd.h +++ b/src/engraving/editing/cmd.h @@ -37,7 +37,6 @@ struct UpdateState bool playNote = false; ///< play selected note after command bool playChord = false; ///< play whole chord for the selected note bool selectionChanged = false; - std::vector deleteList; }; //--------------------------------------------------------- @@ -73,6 +72,9 @@ class CmdState staff_idx_t endStaff() const { return m_endStaff; } const EngravingItem* element() const; + void deleteLater(EngravingObject* e); + std::vector takePostponedDeletions(); + void lock() { m_locked = true; } void unlock() { m_locked = false; } #ifndef NDEBUG @@ -91,6 +93,7 @@ class CmdState const MeasureBase* m_mb = nullptr; bool m_oneElement = true; bool m_oneMeasureBase = true; + std::vector m_postponedDeletions; bool m_locked = false; }; diff --git a/src/engraving/editing/editmeasures.cpp b/src/engraving/editing/editmeasures.cpp index 2c11232fe8756..320db88e327d3 100644 --- a/src/engraving/editing/editmeasures.cpp +++ b/src/engraving/editing/editmeasures.cpp @@ -343,12 +343,12 @@ void InsertRemoveMeasures::removeMeasures() // erase system from score muse::remove(score->systems(), s); // finally delete system - score->deleteLater(s); + s->deleteLater(); if (page && page->systems().empty()) { // if page is empty, delete it as well muse::remove(score->pages(), page); - score->deleteLater(page); + page->deleteLater(); } } } diff --git a/src/engraving/rw/read206/read206.cpp b/src/engraving/rw/read206/read206.cpp index ba85823e6ba4a..f09092f4b47f8 100644 --- a/src/engraving/rw/read206/read206.cpp +++ b/src/engraving/rw/read206/read206.cpp @@ -1177,10 +1177,10 @@ bool Read206::readNoteProperties206(Note* note, XmlReader& e, ReadContext& ctx) read400::TRead::read(s, e, ctx); if (s->sym() == SymId::noteheadParenthesisLeft) { note->setParenthesesMode(note->rightParen() ? ParenthesesMode::BOTH : ParenthesesMode::LEFT); - ctx.score()->deleteLater(s); + s->deleteLater(); } else if (s->sym() == SymId::noteheadParenthesisRight) { note->setParenthesesMode(note->leftParen() ? ParenthesesMode::BOTH : ParenthesesMode::RIGHT); - ctx.score()->deleteLater(s); + s->deleteLater(); } else { note->add(s); } diff --git a/src/engraving/rw/read400/tread.cpp b/src/engraving/rw/read400/tread.cpp index 258008bd941b9..3f42afea37bd0 100644 --- a/src/engraving/rw/read400/tread.cpp +++ b/src/engraving/rw/read400/tread.cpp @@ -3147,10 +3147,10 @@ bool TRead::readProperties(Note* n, XmlReader& e, ReadContext& ctx) TRead::read(s, e, ctx); if (s->sym() == SymId::noteheadParenthesisLeft) { n->setParenthesesMode(ParenthesesMode::BOTH); - ctx.score()->deleteLater(s); + s->deleteLater(); } else if (s->sym() == SymId::noteheadParenthesisRight) { n->setParenthesesMode(ParenthesesMode::BOTH); - ctx.score()->deleteLater(s); + s->deleteLater(); } else { n->add(s); } diff --git a/src/engraving/rw/read410/tread.cpp b/src/engraving/rw/read410/tread.cpp index 15b176a82b4dd..a7d4362765eae 100644 --- a/src/engraving/rw/read410/tread.cpp +++ b/src/engraving/rw/read410/tread.cpp @@ -3307,10 +3307,10 @@ bool TRead::readProperties(Note* n, XmlReader& e, ReadContext& ctx) TRead::read(s, e, ctx); if (s->sym() == SymId::noteheadParenthesisLeft) { n->setParenthesesMode(ParenthesesMode::BOTH); - ctx.score()->deleteLater(s); + s->deleteLater(); } else if (s->sym() == SymId::noteheadParenthesisRight) { n->setParenthesesMode(ParenthesesMode::BOTH); - ctx.score()->deleteLater(s); + s->deleteLater(); } else { n->add(s); } From 66ddaf66ed45dd84983eef40646d8e3e98781706 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Sun, 24 May 2026 20:22:32 +0200 Subject: [PATCH 05/18] Move start/endCmd and `Score::update` into MasterScore This might involve small behaviour changes, but those should be fine. --- src/engraving/dom/masterscore.h | 9 ++++ src/engraving/dom/score.h | 12 ++--- src/engraving/editing/cmd.cpp | 92 ++++++++++++++++++++------------- 3 files changed, 69 insertions(+), 44 deletions(-) diff --git a/src/engraving/dom/masterscore.h b/src/engraving/dom/masterscore.h index 670548f7ba91f..86aaf9ea65689 100644 --- a/src/engraving/dom/masterscore.h +++ b/src/engraving/dom/masterscore.h @@ -138,6 +138,13 @@ class MasterScore : public Score bool excerptsChanged() const { return m_cmdState.excerptsChanged; } bool instrumentsChanged() const { return m_cmdState.instrumentsChanged; } + void startCmd(const TranslatableString& actionName); + void endCmd(bool rollback = false, bool layoutAllParts = false); + void undoRedo(bool undo, EditData* ed); + + void update() { update(true); } + void lockUpdates(bool locked); + void setTempomap(TempoMap* tm); int midiPortCount() const { return m_midiPortCount; } @@ -193,6 +200,7 @@ class MasterScore : public Score double widthOfSegmentCell() const { return m_widthOfSegmentCell; } private: + void update(bool resetCmdState, bool layoutAllParts = false); void reorderMidiMapping(); void rebuildExcerptsMidiMapping(); @@ -230,6 +238,7 @@ class MasterScore : public Score bool m_readOnly = false; CmdState m_cmdState; // modified during cmd processing + bool m_updatesLocked = false; std::array m_loopBoundaries; ///< 0 - LoopIn, 1 - LoopOut diff --git a/src/engraving/dom/score.h b/src/engraving/dom/score.h index 4b3c62f8755f7..feebfa7c1d852 100644 --- a/src/engraving/dom/score.h +++ b/src/engraving/dom/score.h @@ -584,8 +584,7 @@ class Score : public EngravingObject, public muse::Contextable void startCmd(const TranslatableString& actionName); // start undoable command void endCmd(bool rollback = false, bool layoutAllParts = false); // end undoable command - void update() { update(true); } - void lockUpdates(bool locked); + void update(); void undoRedo(bool undo, EditData*); virtual muse::async::Channel changesChannel() const; @@ -601,7 +600,6 @@ class Score : public EngravingObject, public muse::Contextable virtual const CmdState& cmdState() const; virtual void addLayoutFlags(LayoutFlags); virtual void setInstrumentsChanged(bool); - void addRefresh(const RectF&); void cmdToggleAutoplace(bool all); @@ -612,6 +610,9 @@ class Score : public EngravingObject, public muse::Contextable bool selectionEmpty() const { return m_selection.staffStart() == m_selection.staffEnd(); } bool selectionChanged() const { return m_updateState.selectionChanged; } void setSelectionChanged(bool val) { m_updateState.selectionChanged = val; } + const RectF& refreshRect() const { return m_updateState.refresh; } + void addRefresh(const RectF&); + void clearRefreshRect() { m_updateState.refresh = RectF(); } void changeSelectedElementsVoice(voice_idx_t); void changeSelectedElementsVoiceAssignment(VoiceAssignment); @@ -686,6 +687,7 @@ class Score : public EngravingObject, public muse::Contextable void setUpTempoMapLater(); void setUpTempoMap(); + bool needSetUpTempoMap() const { return m_needSetUpTempoMap; } EngravingItem* nextElement(); EngravingItem* prevElement(); @@ -1133,8 +1135,6 @@ class Score : public EngravingObject, public muse::Contextable void deleteRangeAtTrack(std::vector& crsToSelect, const track_idx_t track, Segment* startSeg, const Fraction& endTick, Tuplet* currentTuplet, const SelectionFilter& filter, bool selectionContainsMultiNoteChords); - void update(bool resetCmdState, bool layoutAllParts = false); - muse::ID newStaffId() const; muse::ID newPartId() const; @@ -1226,8 +1226,6 @@ class Score : public EngravingObject, public muse::Contextable PaddingTable m_paddingTable; double m_minimumPaddingUnit = 0.0; - - bool m_updatesLocked = false; }; static inline Score* toScore(EngravingObject* e) diff --git a/src/engraving/editing/cmd.cpp b/src/engraving/editing/cmd.cpp index 80f4e448124b5..e341dd1c2ceb7 100644 --- a/src/engraving/editing/cmd.cpp +++ b/src/engraving/editing/cmd.cpp @@ -374,17 +374,18 @@ static void deletePostponed(CmdState& cmdState) //--------------------------------------------------------- void Score::startCmd(const TranslatableString& actionName) +{ + masterScore()->startCmd(actionName); +} + +void MasterScore::startCmd(const TranslatableString& actionName) { if (undoStack()->isLocked()) { return; } - if (MScore::debugMode) { - LOGD("===startCmd()"); - } - if (undoStack()->hasActiveTransaction()) { - LOGD("Score::startCmd(): cmd already active"); + LOGD() << "cmd already active"; return; } @@ -401,6 +402,11 @@ void Score::startCmd(const TranslatableString& actionName) //--------------------------------------------------------- void Score::undoRedo(bool undo, EditData* ed) +{ + masterScore()->undoRedo(undo, ed); +} + +void MasterScore::undoRedo(bool undo, EditData* ed) { if (readOnly()) { return; @@ -421,7 +427,7 @@ void Score::undoRedo(bool undo, EditData* ed) } update(false); - masterScore()->setPlaylistDirty(); // TODO: flag all individual operations + setPlaylistDirty(); // TODO: flag all individual operations updateSelection(); ScoreChanges result = buildScoreChanges(cmdState(), changes); @@ -435,6 +441,11 @@ void Score::undoRedo(bool undo, EditData* ed) //--------------------------------------------------------- void Score::endCmd(bool rollback, bool layoutAllParts) +{ + masterScore()->endCmd(rollback, layoutAllParts); +} + +void MasterScore::endCmd(bool rollback, bool layoutAllParts) { if (undoStack()->isLocked()) { return; @@ -467,7 +478,7 @@ void Score::endCmd(bool rollback, bool layoutAllParts) undoStack()->endTransaction(isCurrentTransactionEmpty); if (dirty()) { - masterScore()->setPlaylistDirty(); // TODO: flag individual operations + setPlaylistDirty(); // TODO: flag individual operations } cmdState().reset(); @@ -496,7 +507,12 @@ void CmdState::dump() // layout & update //--------------------------------------------------------- -void Score::update(bool resetCmdState, bool layoutAllParts) +void Score::update() +{ + masterScore()->update(); +} + +void MasterScore::update(bool resetCmdState, bool layoutAllParts) { if (m_updatesLocked) { return; @@ -504,53 +520,55 @@ void Score::update(bool resetCmdState, bool layoutAllParts) TRACEFUNC; + deletePostponed(m_cmdState); + bool updateAll = false; - { - MasterScore* ms = masterScore(); - CmdState& cs = ms->cmdState(); - deletePostponed(cs); - - if (cs.layoutRange()) { - for (Score* s : ms->scoreList()) { - if (s != this && !s->isOpen() && ms->scoreList().size() > 1 && !layoutAllParts) { - continue; - } - s->doLayoutRange(cs.startTick(), cs.endTick()); + if (m_cmdState.layoutRange()) { + for (Score* s : scoreList()) { + if (s != this && !s->isOpen() && scoreList().size() > 1 && !layoutAllParts) { + continue; } - updateAll = true; + s->doLayoutRange(m_cmdState.startTick(), m_cmdState.endTick()); } + updateAll = true; } - if (m_needSetUpTempoMap) { + if (needSetUpTempoMap()) { setUpTempoMap(); - m_needSetUpTempoMap = false; } - MasterScore* ms = masterScore(); - CmdState& cs = ms->cmdState(); - if (updateAll || cs.updateAll()) { + if (updateAll || m_cmdState.updateAll()) { for (Score* s : scoreList()) { - for (MuseScoreView* v : s->m_viewer) { + for (MuseScoreView* v : s->getViewer()) { v->updateAll(); } } - } else if (cs.updateRange()) { - // updateRange updates only current score - double d = style().spatium() * .5; - m_updateState.refresh.adjust(-d, -d, 2 * d, 2 * d); - for (MuseScoreView* v : m_viewer) { - v->dataChanged(m_updateState.refresh); + } else if (m_cmdState.updateRange()) { + // Any score that accumulated a refresh rect via addRefresh() calls dataChanged() on its viewers. + for (Score* s : scoreList()) { + if (s->refreshRect().isNull()) { + continue; + } + const std::list& viewers = s->getViewer(); + if (!viewers.empty()) { + double d = s->style().spatium() * .5; + RectF rect = s->refreshRect().adjusted(-d, -d, 2 * d, 2 * d); + for (MuseScoreView* v : viewers) { + v->dataChanged(rect); + } + } + s->clearRefreshRect(); } - m_updateState.refresh = RectF(); } + if (playlistDirty()) { - masterScore()->setPlaylistClean(); + setPlaylistClean(); } if (resetCmdState) { - cs.reset(); + m_cmdState.reset(); } - for (Score* score : ms->scoreList()) { + for (Score* score : scoreList()) { Selection& sel = score->selection(); if (sel.isRange() && !sel.isLocked()) { sel.updateSelectedElements(); @@ -558,7 +576,7 @@ void Score::update(bool resetCmdState, bool layoutAllParts) } } -void Score::lockUpdates(bool locked) +void MasterScore::lockUpdates(bool locked) { m_updatesLocked = locked; } From 10872529cee70363dd800c05d3edb72846d7a3d2 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Fri, 12 Jun 2026 02:52:14 +0200 Subject: [PATCH 06/18] Introduce sanity checks in MasterScore::undoRedo --- src/engraving/editing/cmd.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/engraving/editing/cmd.cpp b/src/engraving/editing/cmd.cpp index e341dd1c2ceb7..fb3b1eba51052 100644 --- a/src/engraving/editing/cmd.cpp +++ b/src/engraving/editing/cmd.cpp @@ -412,12 +412,30 @@ void MasterScore::undoRedo(bool undo, EditData* ed) return; } + IF_ASSERT_FAILED(!undoStack()->hasActiveTransaction()) { + LOGW() << "cannot undo/redo while transaction is active"; + return; + } + + if (undo) { + IF_ASSERT_FAILED(undoStack()->canUndo()) { + LOGW() << "cannot undo"; + return; + } + } else { + IF_ASSERT_FAILED(undoStack()->canRedo()) { + LOGW() << "cannot redo"; + return; + } + } + + cmdState().reset(); + //! NOTE: the order of operations is very important here //! 1. for the undo operation, the list of changed elements is available before undo() //! 2. for the redo operation, the list of changed elements will be available after redo() UndoableTransaction::ChangesInfo changes; - cmdState().reset(); if (undo) { changes = changesInfo(undoStack(), undo); undoStack()->undo(ed); From c68b69dcb0f2c29799511205ae521da2fb556093 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Thu, 4 Jun 2026 12:34:09 +0200 Subject: [PATCH 07/18] Remove unexpected start/endCmd in harpdiagram_tests.cpp --- src/engraving/tests/harpdiagram_tests.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/engraving/tests/harpdiagram_tests.cpp b/src/engraving/tests/harpdiagram_tests.cpp index 81f72d8a6b0cf..b5427f7744269 100644 --- a/src/engraving/tests/harpdiagram_tests.cpp +++ b/src/engraving/tests/harpdiagram_tests.cpp @@ -213,9 +213,7 @@ TEST_F(Engraving_HarpDiagramTests, textdiagrams2) EXPECT_EQ(diagram1->xmlText(), expText); // Test undo - score->startCmd(TranslatableString::untranslatable("Harp diagram tests")); score->undoRedo(true, &dd); - score->endCmd(); EXPECT_TRUE(ScoreComp::saveCompareScore(score, writeFile, initFile)); } From 5c1be46edbf8968aa5a54dda45b091e66d32819b Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:35:29 +0200 Subject: [PATCH 08/18] Fix double start/endCmd calls in partialtie_tests.cpp --- src/engraving/tests/partialtie_tests.cpp | 30 ++++++------------------ 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/src/engraving/tests/partialtie_tests.cpp b/src/engraving/tests/partialtie_tests.cpp index 5eb08e3839b1c..769acd669c86e 100644 --- a/src/engraving/tests/partialtie_tests.cpp +++ b/src/engraving/tests/partialtie_tests.cpp @@ -92,11 +92,9 @@ class Engraving_PartialTieTests : public ::testing::Test { // Add tie to start note // Expect tie to be added successfully and all jump points to have an incoming tie - m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); m_masterScore->select(m_startNote); - Tie* t = m_masterScore->cmdToggleTie(); + Tie* t = m_masterScore->cmdToggleTie(); // calls startCmd/endCmd internally EXPECT_TRUE(t); - m_masterScore->endCmd(); for (const Note* note : m_jumpPoints) { EXPECT_TRUE(note->tieBack()); @@ -112,9 +110,7 @@ class Engraving_PartialTieTests : public ::testing::Test TieJumpPointList* jumpPointList = m_startNote->tieJumpPoints(); EXPECT_TRUE(jumpPointList->size() > 1); - m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); jumpPointList->toggleJumpPoint(u"jumpPoint1"); - m_masterScore->endCmd(); for (TieJumpPoint* jumpPoint : *jumpPointList) { if (jumpPoint->id() == u"jumpPoint1") { @@ -199,9 +195,7 @@ class Engraving_PartialTieTests : public ::testing::Test Tie* startTie = jumpPointList->startTie(); - m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); - jumpPointList->toggleJumpPoint(u"jumpPoint0"); - m_masterScore->endCmd(); + jumpPointList->toggleJumpPoint(u"jumpPoint0"); // calls startCmd/endCmd internally for (TieJumpPoint* jumpPoint : *jumpPointList) { if (jumpPoint->id() == u"jumpPoint0") { @@ -261,10 +255,8 @@ class Engraving_PartialTieTests : public ::testing::Test EXPECT_TRUE(initialTie && initialTie->isPartialTie()); Note* noteBeforeSegno = getNoteAtTick(tickBeforeSegno); - m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); m_masterScore->select(noteBeforeSegno); - Tie* tieBeforeSegno = m_masterScore->cmdToggleTie(); - m_masterScore->endCmd(); + Tie* tieBeforeSegno = m_masterScore->cmdToggleTie(); // calls startCmd/endCmd internally bool newTieFound = false; for (TieJumpPoint* jumpPoint : *jumpPointList) { @@ -297,11 +289,9 @@ class Engraving_PartialTieTests : public ::testing::Test { // Add a full tie to the note preceding a segno, then add a tie to the D.S which should add the previous tie to the list of jump points Note* noteBeforeSegno = getNoteAtTick(tickBeforeSegno); - m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); m_masterScore->select(noteBeforeSegno); - Tie* tieBeforeSegno = m_masterScore->cmdToggleTie(); + Tie* tieBeforeSegno = m_masterScore->cmdToggleTie(); // calls startCmd/endCmd internally EXPECT_TRUE(tieBeforeSegno); - m_masterScore->endCmd(); Tie* startTie = addTie(); @@ -358,12 +348,10 @@ class Engraving_PartialTieTests : public ::testing::Test // Add tie to start note // Expect tie to be added successfully and all jump points to have an incoming tie - m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); m_masterScore->select(m_startNote); m_masterScore->select(secondTieNote, SelectType::ADD); - Tie* t = m_masterScore->cmdToggleTie(); + Tie* t = m_masterScore->cmdToggleTie(); // calls startCmd/endCmd internally EXPECT_TRUE(t); - m_masterScore->endCmd(); for (const Note* note : m_jumpPoints) { EXPECT_TRUE(note->tieBack()); @@ -560,9 +548,7 @@ TEST_F(Engraving_PartialTieTests, toggleTiePartialThenRestore) // Toggle tie at 4/4 score->select(tieFromNote); - score->startCmd(TranslatableString::untranslatable("Partial tie tests")); - score->cmdToggleTie(); - score->endCmd(); + score->cmdToggleTie(); // calls startCmd/endCmd internally // Clear the second measure @@ -600,9 +586,7 @@ TEST_F(Engraving_PartialTieTests, toggleTiePartialThenRestore) // Toggle tie again at 4/4 score->select(tieFromNote); - score->startCmd(TranslatableString::untranslatable("Partial tie tests")); - score->cmdToggleTie(); - score->endCmd(); + score->cmdToggleTie(); // calls startCmd/endCmd internally // Verify the tie is now full Tie* newTie = tieFromNote->tieFor(); From 141dbfd6533423bc7c6b6b1cd200c5bbfee5d3cd Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Fri, 5 Jun 2026 10:41:34 +0200 Subject: [PATCH 09/18] Disable incorrect undo calls from braille module They were supposed to undo some transactions that are commented out --- src/braille/internal/braille.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/braille/internal/braille.cpp b/src/braille/internal/braille.cpp index 1779f7fd06704..5beb86e9b5fd6 100644 --- a/src/braille/internal/braille.cpp +++ b/src/braille/internal/braille.cpp @@ -2402,8 +2402,10 @@ QString Braille::brailleMeasure(Measure* measure, int staffCount) resetOctave(staffCount); // Undo filling the missing beats with rests, so we don't have an altered score. +/* see FIXME above m_score->undoRedo(true, nullptr); m_score->undoRedo(true, nullptr); +*/ m_score->deselectAll(); } } From 4ad5f61eccc5e27695737812138b2e99f006b2ae Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Fri, 12 Jun 2026 02:42:36 +0200 Subject: [PATCH 10/18] Inline static `changesInfo` method --- src/engraving/editing/cmd.cpp | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/src/engraving/editing/cmd.cpp b/src/engraving/editing/cmd.cpp index fb3b1eba51052..d8ed160ee943d 100644 --- a/src/engraving/editing/cmd.cpp +++ b/src/engraving/editing/cmd.cpp @@ -101,27 +101,6 @@ using namespace muse::io; using namespace mu::engraving; namespace mu::engraving { -static UndoableTransaction::ChangesInfo changesInfo(const UndoStack* stack, bool undo = false) -{ - IF_ASSERT_FAILED(stack) { - static UndoableTransaction::ChangesInfo empty; - return empty; - } - - const UndoableTransaction* transaction = stack->activeTransaction(); - - if (!transaction) { - transaction = stack->last(); - } - - if (!transaction) { - static UndoableTransaction::ChangesInfo empty; - return empty; - } - - return transaction->changesInfo(undo); -} - static ScoreChanges buildScoreChanges(const CmdState& cmdState, const UndoableTransaction::ChangesInfo& changes) { int startTick = cmdState.startTick().ticks(); @@ -437,11 +416,11 @@ void MasterScore::undoRedo(bool undo, EditData* ed) UndoableTransaction::ChangesInfo changes; if (undo) { - changes = changesInfo(undoStack(), undo); + changes = undoStack()->last()->changesInfo(true); undoStack()->undo(ed); } else { undoStack()->redo(ed); - changes = changesInfo(undoStack()); + changes = undoStack()->last()->changesInfo(false); } update(false); @@ -487,7 +466,7 @@ void MasterScore::endCmd(bool rollback, bool layoutAllParts) ScoreChanges changes; if (!rollback) { - changes = buildScoreChanges(cmdState(), changesInfo(undoStack())); + changes = buildScoreChanges(cmdState(), undoStack()->activeTransaction()->changesInfo()); } LOGD() << "Undo stack current transaction commands count: " << undoStack()->activeTransaction()->commands().size(); From a35569c95ccac39fec11738b782a4981930fa8e7 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Fri, 12 Jun 2026 02:23:39 +0200 Subject: [PATCH 11/18] Rename confusing parameter of UndoableCommand::cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This `undo` parameter probably meant “whether this UndoableCommand currently lives in the undo stack or in the redo stack, but that’s difficult to understand. Rename it to `wasDone`, as in, “was not undone”. --- src/engraving/editing/addremoveelement.cpp | 8 ++++---- src/engraving/editing/editchord.cpp | 8 ++++---- src/engraving/editing/editchord.h | 4 ++-- src/engraving/editing/editexcerpt.cpp | 4 ++-- src/engraving/editing/editexcerpt.h | 2 +- src/engraving/editing/editpart.cpp | 8 ++++---- src/engraving/editing/editstaff.cpp | 8 ++++---- src/engraving/editing/editsystemlocks.cpp | 8 ++++---- src/engraving/editing/transaction/undoablecommand.h | 2 +- src/engraving/editing/transaction/undostack.cpp | 4 ++-- src/engraving/editing/transaction/undostack.h | 2 +- 11 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/engraving/editing/addremoveelement.cpp b/src/engraving/editing/addremoveelement.cpp index 57eeeaeec1016..67d2ed43f75ba 100644 --- a/src/engraving/editing/addremoveelement.cpp +++ b/src/engraving/editing/addremoveelement.cpp @@ -83,9 +83,9 @@ AddElement::AddElement(EngravingItem* e) element = e; } -void AddElement::cleanup(bool undo) +void AddElement::cleanup(bool wasDone) { - if (!undo) { + if (!wasDone) { delete element; element = nullptr; } @@ -233,9 +233,9 @@ RemoveElement::RemoveElement(EngravingItem* e) } } -void RemoveElement::cleanup(bool undo) +void RemoveElement::cleanup(bool wasDone) { - if (undo) { + if (wasDone) { delete element; element = nullptr; } diff --git a/src/engraving/editing/editchord.cpp b/src/engraving/editing/editchord.cpp index 073bb754b62b6..684348bd95236 100644 --- a/src/engraving/editing/editchord.cpp +++ b/src/engraving/editing/editchord.cpp @@ -418,9 +418,9 @@ void mu::engraving::AddNoteParenthesisInfo::undo(EditData*) m_chord->triggerLayout(); } -void AddNoteParenthesisInfo::cleanup(bool undo) +void AddNoteParenthesisInfo::cleanup(bool wasDone) { - if (!undo) { + if (!wasDone) { delete m_noteParenInfo; m_noteParenInfo = nullptr; } @@ -440,9 +440,9 @@ void RemoveNoteParenthesisInfo::undo(EditData*) m_chord->triggerLayout(); } -void RemoveNoteParenthesisInfo::cleanup(bool undo) +void RemoveNoteParenthesisInfo::cleanup(bool wasDone) { - if (undo) { + if (wasDone) { delete m_noteParenInfo; m_noteParenInfo = nullptr; } diff --git a/src/engraving/editing/editchord.h b/src/engraving/editing/editchord.h index c951534c2e75e..3b1227ef87631 100644 --- a/src/engraving/editing/editchord.h +++ b/src/engraving/editing/editchord.h @@ -113,7 +113,7 @@ class AddNoteParenthesisInfo : public UndoableCommand void redo(EditData*) override; void undo(EditData*) override; - void cleanup(bool undo) override; + void cleanup(bool wasDone) override; Chord* m_chord = nullptr; NoteParenthesisInfo* m_noteParenInfo = nullptr; @@ -133,7 +133,7 @@ class RemoveNoteParenthesisInfo : public UndoableCommand void redo(EditData*) override; void undo(EditData*) override; - void cleanup(bool undo) override; + void cleanup(bool wasDone) override; Chord* m_chord = nullptr; NoteParenthesisInfo* m_noteParenInfo = nullptr; diff --git a/src/engraving/editing/editexcerpt.cpp b/src/engraving/editing/editexcerpt.cpp index 0232fc27e09e4..20debfa3c61e7 100644 --- a/src/engraving/editing/editexcerpt.cpp +++ b/src/engraving/editing/editexcerpt.cpp @@ -162,9 +162,9 @@ void AddPartToExcerpt::redo(EditData*) } } -void AddPartToExcerpt::cleanup(bool undo) +void AddPartToExcerpt::cleanup(bool wasDone) { - if (!undo) { + if (!wasDone) { delete m_part; m_part = nullptr; } diff --git a/src/engraving/editing/editexcerpt.h b/src/engraving/editing/editexcerpt.h index 82f1d61f12add..49be078ed1be7 100644 --- a/src/engraving/editing/editexcerpt.h +++ b/src/engraving/editing/editexcerpt.h @@ -118,7 +118,7 @@ class AddPartToExcerpt : public UndoableCommand AddPartToExcerpt(Excerpt* e, Part* p, size_t targetPartIdx); void undo(EditData*) override; void redo(EditData*) override; - void cleanup(bool undo) override; + void cleanup(bool wasDone) override; UNDO_TYPE(CommandType::AddPartToExcerpt) UNDO_NAME("AddPartToExcerpt") diff --git a/src/engraving/editing/editpart.cpp b/src/engraving/editing/editpart.cpp index 3b26cb7dc81a4..90854f3761c94 100644 --- a/src/engraving/editing/editpart.cpp +++ b/src/engraving/editing/editpart.cpp @@ -62,9 +62,9 @@ void InsertPart::redo(EditData*) m_part->score()->insertPart(m_part, m_targetPartIdx); } -void InsertPart::cleanup(bool undo) +void InsertPart::cleanup(bool wasDone) { - if (!undo) { + if (!wasDone) { delete m_part; m_part = nullptr; } @@ -94,9 +94,9 @@ void RemovePart::redo(EditData*) m_part->score()->removePart(m_part); } -void RemovePart::cleanup(bool undo) +void RemovePart::cleanup(bool wasDone) { - if (undo) { + if (wasDone) { delete m_part; m_part = nullptr; } diff --git a/src/engraving/editing/editstaff.cpp b/src/engraving/editing/editstaff.cpp index c1015eed815dc..5a759bd0170ac 100644 --- a/src/engraving/editing/editstaff.cpp +++ b/src/engraving/editing/editstaff.cpp @@ -52,9 +52,9 @@ void InsertStaff::redo(EditData*) staff->score()->insertStaff(staff, ridx); } -void InsertStaff::cleanup(bool undo) +void InsertStaff::cleanup(bool wasDone) { - if (!undo) { + if (!wasDone) { delete staff; staff = nullptr; } @@ -87,9 +87,9 @@ void RemoveStaff::redo(EditData*) } } -void RemoveStaff::cleanup(bool undo) +void RemoveStaff::cleanup(bool wasDone) { - if (undo) { + if (wasDone) { delete staff; staff = nullptr; } diff --git a/src/engraving/editing/editsystemlocks.cpp b/src/engraving/editing/editsystemlocks.cpp index d910d7de26c34..266926fbf6f1a 100644 --- a/src/engraving/editing/editsystemlocks.cpp +++ b/src/engraving/editing/editsystemlocks.cpp @@ -56,9 +56,9 @@ class AddSystemLock : public UndoableCommand score->addSystemLock(m_systemLock); } - void cleanup(bool undo) override + void cleanup(bool wasDone) override { - if (!undo) { + if (!wasDone) { delete m_systemLock; m_systemLock = nullptr; } @@ -97,9 +97,9 @@ class RemoveSystemLock : public UndoableCommand score->removeSystemLock(m_systemLock); } - void cleanup(bool undo) override + void cleanup(bool wasDone) override { - if (undo) { + if (wasDone) { delete m_systemLock; m_systemLock = nullptr; } diff --git a/src/engraving/editing/transaction/undoablecommand.h b/src/engraving/editing/transaction/undoablecommand.h index 975cfc99dcd06..81a43af804733 100644 --- a/src/engraving/editing/transaction/undoablecommand.h +++ b/src/engraving/editing/transaction/undoablecommand.h @@ -175,7 +175,7 @@ class UndoableCommand virtual void undo(EditData*); virtual void redo(EditData*); - virtual void cleanup(bool /*undo*/) {} + virtual void cleanup(bool /*wasDone*/) {} virtual std::vector objectItems() const { return {}; } virtual const char* name() const { return "UndoableCommand"; } diff --git a/src/engraving/editing/transaction/undostack.cpp b/src/engraving/editing/transaction/undostack.cpp index b6b5ea549cbc0..ff0081e300a7b 100644 --- a/src/engraving/editing/transaction/undostack.cpp +++ b/src/engraving/editing/transaction/undostack.cpp @@ -288,10 +288,10 @@ UndoableTransaction::~UndoableTransaction() } } -void UndoableTransaction::cleanup(bool undo) +void UndoableTransaction::cleanup(bool wasDone) { for (UndoableCommand* command : m_commands) { - command->cleanup(undo); + command->cleanup(wasDone); } } diff --git a/src/engraving/editing/transaction/undostack.h b/src/engraving/editing/transaction/undostack.h index a90978b098f2b..5878388799522 100644 --- a/src/engraving/editing/transaction/undostack.h +++ b/src/engraving/editing/transaction/undostack.h @@ -53,7 +53,7 @@ class UndoableTransaction void append(UndoableTransaction&& other); void unwind(); - void cleanup(bool undo); + void cleanup(bool wasDone); const std::vector& commands() const { return m_commands; } bool empty() const { return m_commands.empty(); } From 506bbf96fd57d0591c152d10068cdd7a099c9e1c Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Fri, 12 Jun 2026 01:22:01 +0200 Subject: [PATCH 12/18] Don't leak UndoableCommand in early exit path of `pushWithoutPerforming` --- src/engraving/editing/transaction/undostack.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engraving/editing/transaction/undostack.cpp b/src/engraving/editing/transaction/undostack.cpp index ff0081e300a7b..d344140a8e613 100644 --- a/src/engraving/editing/transaction/undostack.cpp +++ b/src/engraving/editing/transaction/undostack.cpp @@ -158,6 +158,7 @@ void UndoStack::pushWithoutPerforming(UndoableCommand* cmd) if (!ScoreLoad::loading()) { LOGW("no active transaction, UndoStack %p", this); } + delete cmd; return; } m_activeTransaction->appendCommand(cmd); From 67b5fa12a479e179931c2ab3143a27ba9e13a681 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Fri, 12 Jun 2026 03:50:36 +0200 Subject: [PATCH 13/18] Don't leak items owned by UndoableCommand deleted in `unwind` --- src/engraving/editing/transaction/undostack.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engraving/editing/transaction/undostack.cpp b/src/engraving/editing/transaction/undostack.cpp index d344140a8e613..fd1339e59bbdf 100644 --- a/src/engraving/editing/transaction/undostack.cpp +++ b/src/engraving/editing/transaction/undostack.cpp @@ -302,6 +302,7 @@ void UndoableTransaction::unwind() UndoableCommand* command = muse::takeLast(m_commands); LOG_UNDO() << "unwind: " << command->name(); command->undo(nullptr); + command->cleanup(false); delete command; } } From e2dd0ca31f86117f8487422d453a6063303249bb Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Fri, 12 Jun 2026 03:50:22 +0200 Subject: [PATCH 14/18] Fix off-by-one error in UndoStack::reopen The UndoStack constructor pushes one state entry onto `m_states`, so the state entry corresponding to the reopened UndoableCommand is at one index higher than the UndoableCommand. Reported at https://github.com/musescore/MuseScore/pull/33658#discussion_r3399611552 and https://github.com/musescore/MuseScore/pull/33658#pullrequestreview-4481667194. --- src/engraving/editing/transaction/undostack.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engraving/editing/transaction/undostack.cpp b/src/engraving/editing/transaction/undostack.cpp index fd1339e59bbdf..bde73ae57c058 100644 --- a/src/engraving/editing/transaction/undostack.cpp +++ b/src/engraving/editing/transaction/undostack.cpp @@ -240,7 +240,7 @@ void UndoStack::reopen() assert(m_currentIndex > 0); --m_currentIndex; m_activeTransaction = muse::takeAt(m_transactions, m_currentIndex); - m_states.erase(m_states.begin() + m_currentIndex); + m_states.erase(m_states.begin() + m_currentIndex + 1); for (auto i : m_activeTransaction->commands()) { LOG_UNDO() << " " << i->name(); } From a6a9d380b4d491b61b9eb9f0e1448d5bb5c1eff0 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Fri, 12 Jun 2026 16:02:10 +0200 Subject: [PATCH 15/18] Fix theoretical concern about `isHairpinDragCreatedFromDynamic` flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit was [concerned](https://github.com/musescore/MuseScore/pull/33658#discussion_r3402708581) about the fact that we never reset this flag. That’s half true: it is reset indirectly in `m_editData.clear()` in `doEndEditElement()`, which is called as soon as the drag-created hairpin is deselected; and that’s pretty soon, because just after dragging, the hairpin is deselected and a new (empty) dynamic is created. So in practice there is no reproducible bug. However, it’s a small effort to reset the flag just where a reset would be expected. Just slightly more robust. --- src/notation/internal/notationinteraction.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index 014ea89977f19..4084e5ccdfbc9 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -1388,7 +1388,11 @@ void NotationInteraction::endDrag() if (m_editData.isHairpinDragCreatedFromDynamic) { // Merge the two actions of hairpin creation + hairpin drag - m_undoStack->mergeTransactions(m_undoStack->currentStateIndex() - 2); + assert(m_undoStack->currentStateIndex() >= 2); + if (m_undoStack->currentStateIndex() >= 2) { + m_undoStack->mergeTransactions(m_undoStack->currentStateIndex() - 2); + } + m_editData.isHairpinDragCreatedFromDynamic = false; } notifyAboutDragChanged(); From 2345eccd22e86b376338a8c1463ec8d31024f5b6 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Sun, 24 May 2026 23:37:25 +0200 Subject: [PATCH 16/18] Remove dead `playlistDirty` methods from Score/MasterScore --- src/engraving/api/v1/elements.cpp | 1 - src/engraving/dom/chord.cpp | 4 +-- src/engraving/dom/excerpt.cpp | 2 +- src/engraving/dom/jump.cpp | 2 +- src/engraving/dom/masterscore.cpp | 7 ++--- src/engraving/dom/masterscore.h | 12 +++---- src/engraving/dom/measure.cpp | 8 ++--- src/engraving/dom/note.cpp | 7 ----- src/engraving/dom/score.cpp | 31 ++----------------- src/engraving/dom/score.h | 8 ++--- src/engraving/dom/segment.cpp | 2 -- src/engraving/dom/staff.cpp | 1 - src/engraving/dom/tempotext.cpp | 1 - src/engraving/dom/tremolobar.cpp | 1 - src/engraving/editing/cmd.cpp | 7 ++--- src/engraving/editing/editmeasures.cpp | 4 +-- src/engraving/editing/editnote.cpp | 3 -- src/engraving/editing/editpart.cpp | 1 - src/engraving/editing/editstaff.cpp | 1 - src/engraving/engravingproject.cpp | 2 +- src/engraving/tests/utils/scorerw.cpp | 2 +- src/importexport/capella/internal/capella.cpp | 2 +- src/notation/internal/masternotation.cpp | 2 +- src/notationscene/widgets/editstafftype.cpp | 1 - .../widgets/stafftextpropertiesdialog.cpp | 1 - 25 files changed, 28 insertions(+), 85 deletions(-) diff --git a/src/engraving/api/v1/elements.cpp b/src/engraving/api/v1/elements.cpp index 857a715f62bb1..a2628bad78ea6 100644 --- a/src/engraving/api/v1/elements.cpp +++ b/src/engraving/api/v1/elements.cpp @@ -335,7 +335,6 @@ void Chord::setPlayEventType(mu::engraving::PlayEventType v) { // Only create undo operation if the value has changed. if (v != chord()->playEventType()) { - chord()->score()->setPlaylistDirty(); chord()->score()->undo(new ChangeChordPlayEventType(chord(), v)); } } diff --git a/src/engraving/dom/chord.cpp b/src/engraving/dom/chord.cpp index 12fd9b026adca..92b8eeff62a7a 100644 --- a/src/engraving/dom/chord.cpp +++ b/src/engraving/dom/chord.cpp @@ -658,8 +658,7 @@ void Chord::add(EngravingItem* e) measure()->setHasVoices(staffIdx(), true); } } - score()->setPlaylistDirty(); - break; + break; case ElementType::ARPEGGIO: m_arpeggio = toArpeggio(e); break; @@ -761,7 +760,6 @@ void Chord::remove(EngravingItem* e) if (voice() && measure() && note->visible()) { measure()->checkMultiVoices(staffIdx()); } - score()->setPlaylistDirty(); } break; diff --git a/src/engraving/dom/excerpt.cpp b/src/engraving/dom/excerpt.cpp index c5862b6bb345d..d5f32bb1ae0c6 100644 --- a/src/engraving/dom/excerpt.cpp +++ b/src/engraving/dom/excerpt.cpp @@ -481,7 +481,7 @@ void Excerpt::createExcerpt(Excerpt* excerpt) } // second layout of score - score->setPlaylistDirty(); + score->invalidateRepeatList(); masterScore->rebuildMidiMapping(); masterScore->updateChannel(); score->remapBracketsAndBarlines(); diff --git a/src/engraving/dom/jump.cpp b/src/engraving/dom/jump.cpp index 2e3a046dea6df..976ec699fa964 100644 --- a/src/engraving/dom/jump.cpp +++ b/src/engraving/dom/jump.cpp @@ -157,7 +157,7 @@ bool Jump::setProperty(Pid propertyId, const PropertyValue& v) break; } triggerLayout(); - score()->setPlaylistDirty(); + score()->invalidateRepeatList(); return true; } diff --git a/src/engraving/dom/masterscore.cpp b/src/engraving/dom/masterscore.cpp index f1370997db1f2..06aefd14f5e5a 100644 --- a/src/engraving/dom/masterscore.cpp +++ b/src/engraving/dom/masterscore.cpp @@ -152,12 +152,11 @@ IAutomation* MasterScore::automation() const } //--------------------------------------------------------- -// setPlaylistDirty +// invalidateRepeatLists //--------------------------------------------------------- -void MasterScore::setPlaylistDirty() +void MasterScore::invalidateRepeatList() { - m_playlistDirty = true; m_expandedRepeatList->setScoreChanged(); m_nonExpandedRepeatList->setScoreChanged(); } @@ -172,7 +171,7 @@ void MasterScore::setExpandRepeats(bool expand) return; } m_expandRepeats = expand; - setPlaylistDirty(); + invalidateRepeatList(); } //--------------------------------------------------------- diff --git a/src/engraving/dom/masterscore.h b/src/engraving/dom/masterscore.h index 86aaf9ea65689..5bbb7b3060772 100644 --- a/src/engraving/dom/masterscore.h +++ b/src/engraving/dom/masterscore.h @@ -103,10 +103,6 @@ class MasterScore : public Score muse::async::Channel changesChannel() const override { return m_changesChannel; } IAutomation* automation() const override; - bool playlistDirty() const override { return m_playlistDirty; } - void setPlaylistDirty() override; - void setPlaylistClean() { m_playlistDirty = false; } - /// Always call this before calling `repeatList()` /// No need to set it back after use, because everyone always calls it before using `repeatList()` void setExpandRepeats(bool expandRepeats); @@ -115,8 +111,10 @@ class MasterScore : public Score void updateRepeatListTempo(); void updateRepeatList(); - const RepeatList& repeatList() const override; - const RepeatList& repeatList(bool expandRepeats, bool updateTies = true) const override; + const RepeatList& repeatList() const; + const RepeatList& repeatList(bool expandRepeats, bool updateTies = true) const; + + void invalidateRepeatList(); std::vector& excerpts() { return m_excerpts; } const std::vector& excerpts() const { return m_excerpts; } @@ -229,7 +227,7 @@ class MasterScore : public Score RepeatList* m_nonExpandedRepeatList = nullptr; AutomationController* m_automationController = nullptr; bool m_expandRepeats = true; - bool m_playlistDirty = true; + std::vector m_excerpts; std::vector m_playbackSettingsLinks; Score* m_playbackScore = nullptr; diff --git a/src/engraving/dom/measure.cpp b/src/engraving/dom/measure.cpp index 767422ebc44c7..018b81b6fff62 100644 --- a/src/engraving/dom/measure.cpp +++ b/src/engraving/dom/measure.cpp @@ -2887,12 +2887,10 @@ bool Measure::setProperty(Pid propertyId, const PropertyValue& value) case Pid::EXCLUDE_FROM_NUMBERING: setExcludeFromNumbering(value.toBool()); triggerLayoutAll(); - score()->setPlaylistDirty(); return true; case Pid::MEASURE_NUMBER_OFFSET: setMeasureNumberOffset(value.toInt()); triggerLayoutAll(); - score()->setPlaylistDirty(); return true; case Pid::MEASURE_NUMBER_MODE: setMeasureNumberMode(MeasureNumberMode(value.toInt())); @@ -2909,15 +2907,15 @@ bool Measure::setProperty(Pid propertyId, const PropertyValue& value) break; case Pid::REPEAT_END: setRepeatEnd(value.toBool()); - score()->setPlaylistDirty(); + score()->invalidateRepeatList(); break; case Pid::REPEAT_START: setRepeatStart(value.toBool()); - score()->setPlaylistDirty(); + score()->invalidateRepeatList(); break; case Pid::REPEAT_JUMP: setRepeatJump(value.toBool()); - score()->setPlaylistDirty(); + score()->invalidateRepeatList(); break; default: return MeasureBase::setProperty(propertyId, value); diff --git a/src/engraving/dom/note.cpp b/src/engraving/dom/note.cpp index 469b3bc5627bc..c573ebf7fc963 100644 --- a/src/engraving/dom/note.cpp +++ b/src/engraving/dom/note.cpp @@ -713,8 +713,6 @@ void Note::setPitch(int val, bool notifyAboutChanged) m_pitch = val; if (notifyAboutChanged) { - score()->setPlaylistDirty(); - #ifndef ENGRAVING_NO_ACCESSIBILITY notifyAboutNameChanged(); #endif @@ -3036,7 +3034,6 @@ bool Note::setProperty(Pid propertyId, const PropertyValue& v) switch (propertyId) { case Pid::PITCH: setPitch(v.toInt()); - score()->setPlaylistDirty(); break; case Pid::CENT_OFFSET: setCentOffset(v.toDouble()); @@ -3077,11 +3074,9 @@ bool Note::setProperty(Pid propertyId, const PropertyValue& v) break; case Pid::USER_VELOCITY: setUserVelocity(v.toInt()); - score()->setPlaylistDirty(); break; case Pid::TUNING: setTuning(v.toDouble()); - score()->setPlaylistDirty(); break; case Pid::FRET: setFret(v.toInt()); @@ -3105,7 +3100,6 @@ bool Note::setProperty(Pid propertyId, const PropertyValue& v) break; case Pid::VELO_TYPE: m_veloType = v.value(); - score()->setPlaylistDirty(); break; case Pid::VISIBLE: { setVisible(v.toBool()); @@ -3116,7 +3110,6 @@ bool Note::setProperty(Pid propertyId, const PropertyValue& v) } case Pid::PLAY: setPlay(v.toBool()); - score()->setPlaylistDirty(); break; case Pid::FIXED: setFixed(v.toBool()); diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index a9079a109f10f..99effd01ff0dd 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -826,22 +826,9 @@ bool Score::dirty() const return !undoStack()->isClean(); } -//--------------------------------------------------------- -// playlistDirty -//--------------------------------------------------------- - -bool Score::playlistDirty() const +void Score::invalidateRepeatList() { - return masterScore()->playlistDirty(); -} - -//--------------------------------------------------------- -// setPlaylistDirty -//--------------------------------------------------------- - -void Score::setPlaylistDirty() -{ - masterScore()->setPlaylistDirty(); + masterScore()->invalidateRepeatList(); } bool Score::isOpen() const @@ -1435,14 +1422,9 @@ void Score::addElement(EngravingItem* element) } } o->staff()->updateOttava(); - setPlaylistDirty(); } break; - case ElementType::DYNAMIC: - setPlaylistDirty(); - break; - case ElementType::INSTRUMENT_CHANGE: { InstrumentChange* ic = toInstrumentChange(element); ic->part()->setInstrument(ic->instrument(), ic->segment()->tick()); @@ -1453,7 +1435,6 @@ void Score::addElement(EngravingItem* element) case ElementType::CHORD: { - setPlaylistDirty(); // May need to reconnect slur when inserting new chord SpannerMap& smap = spannerMap(); Fraction tick = element->tick(); @@ -1653,14 +1634,9 @@ void Score::removeElement(EngravingItem* element) o->triggerLayout(); removeSpanner(o); o->staff()->updateOttava(); - setPlaylistDirty(); } break; - case ElementType::DYNAMIC: - setPlaylistDirty(); - break; - case ElementType::CHORD: case ElementType::REST: case ElementType::MMREST: @@ -4205,7 +4181,6 @@ void Score::setTempo(Segment* segment, BeatsPerSecond tempo) void Score::setTempo(const Fraction& tick, BeatsPerSecond tempo) { tempomap()->setTempo(tick.ticks(), roundTempo(tempo)); - setPlaylistDirty(); } //--------------------------------------------------------- @@ -4215,7 +4190,6 @@ void Score::setTempo(const Fraction& tick, BeatsPerSecond tempo) void Score::removeTempo(const Fraction& tick) { tempomap()->delTempo(tick.ticks()); - setPlaylistDirty(); } //--------------------------------------------------------- @@ -4256,7 +4230,6 @@ void Score::resetTempoRange(const Fraction& tick1, const Fraction& tick2) void Score::setPause(const Fraction& tick, double seconds) { tempomap()->setPause(tick.ticks(), seconds); - setPlaylistDirty(); } //--------------------------------------------------------- diff --git a/src/engraving/dom/score.h b/src/engraving/dom/score.h index feebfa7c1d852..5756ca141d059 100644 --- a/src/engraving/dom/score.h +++ b/src/engraving/dom/score.h @@ -705,8 +705,6 @@ class Score : public EngravingObject, public muse::Contextable void setSavedCapture(bool v) { m_savedCapture = v; } bool printing() const { return m_printing; } void setPrinting(bool val) { m_printing = val; } - virtual bool playlistDirty() const; - virtual void setPlaylistDirty(); bool hasCorruptedMeasures() const { return m_corrupted; } void setHasCorruptedMeasures(bool val) { m_corrupted = val; } @@ -821,9 +819,11 @@ class Score : public EngravingObject, public muse::Contextable /// To be used together with setExpandRepeats. /// For bigger operations, where suboperations might also use it, /// where those need to have the same value for expandRepeats. - virtual const RepeatList& repeatList() const; + const RepeatList& repeatList() const; /// For small, one-step operations, where you need to get the relevant repeatList just once - virtual const RepeatList& repeatList(bool expandRepeats, bool updateTies = true) const; + const RepeatList& repeatList(bool expandRepeats, bool updateTies = true) const; + + void invalidateRepeatList(); double utick2utime(int tick) const; int utime2utick(double utime) const; diff --git a/src/engraving/dom/segment.cpp b/src/engraving/dom/segment.cpp index 16c97bf5c3833..f119214561f28 100644 --- a/src/engraving/dom/segment.cpp +++ b/src/engraving/dom/segment.cpp @@ -818,7 +818,6 @@ void Segment::add(EngravingItem* el) measure()->setHasVoices(track / VOICES, true); } } - score()->setPlaylistDirty(); } // fall through @@ -886,7 +885,6 @@ void Segment::remove(EngravingItem* el) score()->undo(new ChangeStartEndSpanner(s, start, end)); } } - score()->setPlaylistDirty(); } break; diff --git a/src/engraving/dom/staff.cpp b/src/engraving/dom/staff.cpp index 8cac93b1f761f..178fefaa91e4f 100644 --- a/src/engraving/dom/staff.cpp +++ b/src/engraving/dom/staff.cpp @@ -1795,7 +1795,6 @@ bool Staff::setProperty(Pid id, const PropertyValue& v) case Pid::VISIBLE: setVisible(v.toBool()); masterScore()->rebuildMidiMapping(); - score()->setPlaylistDirty(); break; case Pid::STAFF_CUTAWAY: setCutaway(v.toBool()); diff --git a/src/engraving/dom/tempotext.cpp b/src/engraving/dom/tempotext.cpp index 1ab6178c1a7be..6fe9051e2a664 100644 --- a/src/engraving/dom/tempotext.cpp +++ b/src/engraving/dom/tempotext.cpp @@ -224,7 +224,6 @@ String TempoText::duration2tempoTextString(const TDuration dur) void TempoText::updateScore() { score()->setUpTempoMapLater(); - score()->setPlaylistDirty(); } //--------------------------------------------------------- diff --git a/src/engraving/dom/tremolobar.cpp b/src/engraving/dom/tremolobar.cpp index 6126f84d3c2a3..d622d32ace2ba 100644 --- a/src/engraving/dom/tremolobar.cpp +++ b/src/engraving/dom/tremolobar.cpp @@ -107,7 +107,6 @@ bool TremoloBar::setProperty(Pid propertyId, const PropertyValue& v) break; case Pid::PLAY: setPlay(v.toBool()); - score()->setPlaylistDirty(); break; case Pid::TREMOLOBAR_TYPE: updatePointsByTremoloBarType(static_cast(v.toInt())); diff --git a/src/engraving/editing/cmd.cpp b/src/engraving/editing/cmd.cpp index d8ed160ee943d..e7aea8698029b 100644 --- a/src/engraving/editing/cmd.cpp +++ b/src/engraving/editing/cmd.cpp @@ -424,7 +424,7 @@ void MasterScore::undoRedo(bool undo, EditData* ed) } update(false); - setPlaylistDirty(); // TODO: flag all individual operations + invalidateRepeatList(); // TODO: flag individual operations updateSelection(); ScoreChanges result = buildScoreChanges(cmdState(), changes); @@ -475,7 +475,7 @@ void MasterScore::endCmd(bool rollback, bool layoutAllParts) undoStack()->endTransaction(isCurrentTransactionEmpty); if (dirty()) { - setPlaylistDirty(); // TODO: flag individual operations + invalidateRepeatList(); // TODO: flag individual operations } cmdState().reset(); @@ -558,9 +558,6 @@ void MasterScore::update(bool resetCmdState, bool layoutAllParts) } } - if (playlistDirty()) { - setPlaylistClean(); - } if (resetCmdState) { m_cmdState.reset(); } diff --git a/src/engraving/editing/editmeasures.cpp b/src/engraving/editing/editmeasures.cpp index 320db88e327d3..24c9f09224f17 100644 --- a/src/engraving/editing/editmeasures.cpp +++ b/src/engraving/editing/editmeasures.cpp @@ -85,7 +85,7 @@ void InsertRemoveMeasures::insertMeasures() Segment* fs = nullptr; Segment* ls = nullptr; if (fm->isMeasure()) { - score->setPlaylistDirty(); + score->invalidateRepeatList(); fs = toMeasure(fm)->first(); ls = toMeasure(lm)->last(); for (Segment* s = fs; s && s != ls; s = s->next1()) { @@ -294,7 +294,7 @@ void InsertRemoveMeasures::removeMeasures() if (fm->isMeasure()) { score->setUpTempoMap(); - score->setPlaylistDirty(); + score->invalidateRepeatList(); // check if there is a clef at the end of last measure // remove clef from staff cleflist diff --git a/src/engraving/editing/editnote.cpp b/src/engraving/editing/editnote.cpp index a5c64c2a30c5a..c566b8bb2dd57 100644 --- a/src/engraving/editing/editnote.cpp +++ b/src/engraving/editing/editnote.cpp @@ -697,7 +697,6 @@ void ChangeVelocity::flip(EditData*) void ChangeNoteEventList::flip(EditData*) { - note->score()->setPlaylistDirty(); // Get copy of current list. NoteEventList nel = note->playEvents(); // Replace current copy with new list. @@ -718,7 +717,6 @@ void ChangeNoteEventList::flip(EditData*) void ChangeNoteEvent::flip(EditData*) { - note->score()->setPlaylistDirty(); NoteEvent e = *oldEvent; *oldEvent = newEvent; newEvent = e; @@ -736,7 +734,6 @@ void ChangeNoteEvent::flip(EditData*) void ChangeChordPlayEventType::flip(EditData*) { - chord->score()->setPlaylistDirty(); // Flips data between NoteEventList's. size_t n = chord->notes().size(); for (size_t i = 0; i < n; ++i) { diff --git a/src/engraving/editing/editpart.cpp b/src/engraving/editing/editpart.cpp index 90854f3761c94..74a4a2978ab94 100644 --- a/src/engraving/editing/editpart.cpp +++ b/src/engraving/editing/editpart.cpp @@ -142,7 +142,6 @@ void ChangePart::flip(EditData*) Score* score = part->score(); score->masterScore()->rebuildMidiMapping(); score->setInstrumentsChanged(true); - score->setPlaylistDirty(); // check if notes need to be updated // true if changing into or away from TAB or from one TAB type to another diff --git a/src/engraving/editing/editstaff.cpp b/src/engraving/editing/editstaff.cpp index 5a759bd0170ac..c1cfdfdf5f64e 100644 --- a/src/engraving/editing/editstaff.cpp +++ b/src/engraving/editing/editstaff.cpp @@ -302,7 +302,6 @@ void ChangeStaff::flip(EditData*) staff->triggerLayout(); staff->masterScore()->rebuildMidiMapping(); - staff->score()->setPlaylistDirty(); } //--------------------------------------------------------- diff --git a/src/engraving/engravingproject.cpp b/src/engraving/engravingproject.cpp index bb7a7d6952645..d1e9680569fe0 100644 --- a/src/engraving/engravingproject.cpp +++ b/src/engraving/engravingproject.cpp @@ -123,7 +123,7 @@ Ret EngravingProject::setupMasterScore(bool forceMode) m_masterScore->rebuildMidiMapping(); for (Score* s : m_masterScore->scoreList()) { - s->setPlaylistDirty(); + s->invalidateRepeatList(); s->setLayoutAll(); s->createPaddingTable(); } diff --git a/src/engraving/tests/utils/scorerw.cpp b/src/engraving/tests/utils/scorerw.cpp index 5db16a50bc03b..f9ae51a618986 100644 --- a/src/engraving/tests/utils/scorerw.cpp +++ b/src/engraving/tests/utils/scorerw.cpp @@ -92,7 +92,7 @@ MasterScore* ScoreRW::readScore(const String& name, bool isAbsolutePath, ImportF // up-to-date from that point. But we weren't finished reading the score, so the score will still // change. We need to tell the repeat list about that, so that it will be updated next time // someone uses it. - score->setPlaylistDirty(); + score->invalidateRepeatList(); return score; } diff --git a/src/importexport/capella/internal/capella.cpp b/src/importexport/capella/internal/capella.cpp index e955e0efce29e..87fdae64044c2 100644 --- a/src/importexport/capella/internal/capella.cpp +++ b/src/importexport/capella/internal/capella.cpp @@ -1421,7 +1421,7 @@ void convertCapella(Score* score, Capella* cap, bool capxMode) // score->connectSlurs(); score->connectTies(); score->setUpTempoMap(); - score->setPlaylistDirty(); + score->invalidateRepeatList(); score->setLayoutAll(); } diff --git a/src/notation/internal/masternotation.cpp b/src/notation/internal/masternotation.cpp index bc3cfddf4c829..e4c2432e3b601 100644 --- a/src/notation/internal/masternotation.cpp +++ b/src/notation/internal/masternotation.cpp @@ -193,7 +193,7 @@ static void clearMeasures(mu::engraving::MasterScore* masterScore) measures->clear(); } - masterScore->setPlaylistDirty(); + masterScore->invalidateRepeatList(); masterScore->updateRepeatList(); } diff --git a/src/notationscene/widgets/editstafftype.cpp b/src/notationscene/widgets/editstafftype.cpp index f6e4bc4dfd8c2..fd2de46b47eac 100644 --- a/src/notationscene/widgets/editstafftype.cpp +++ b/src/notationscene/widgets/editstafftype.cpp @@ -268,7 +268,6 @@ Ret EditStaffType::loadScore(mu::engraving::MasterScore* score, const muse::io:: } score->rebuildMidiMapping(); for (mu::engraving::Score* s : score->scoreList()) { - s->setPlaylistDirty(); s->setLayoutAll(); } score->updateChannel(); diff --git a/src/notationscene/widgets/stafftextpropertiesdialog.cpp b/src/notationscene/widgets/stafftextpropertiesdialog.cpp index 0396fd7c54fe7..65779ab8f36bb 100644 --- a/src/notationscene/widgets/stafftextpropertiesdialog.cpp +++ b/src/notationscene/widgets/stafftextpropertiesdialog.cpp @@ -152,7 +152,6 @@ void StaffTextPropertiesDialog::saveValues() score->undoChangeElement(m_originStaffText, nt); score->masterScore()->updateChannel(); score->updateSwing(); - score->setPlaylistDirty(); stack->commitChanges(); } From f8ca911048da30f51cd0dbde25a89147e4d596e1 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:43:31 +0200 Subject: [PATCH 17/18] Fix setting up tie jump points MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During score load, some operations *invalidate* the repeat list, and some operations *request* it, causing it to be updated and marked as valid, even while the score has not been fully loaded yet and thus will change. Because apparently not all operations that should invalidate the repeat list during score load *do* invalidate the repeat list, it is a bit hit-or-miss whether the repeat list is up to date (either invalidated, or rightfully updated) at the time of the last `connectTies` call, depending on whether the repeat list was by chance last invalidated or validated. Apparently the previous commit changed something about that, leading to a failure in partialtie_tests.cpp. Solution: make sure `connectTies` is called after the repeat list has been certainly invalidated after score load. Do this inside score read, so that it is always done, regardless of whether it’s via ScoreRW or EngravingProject. Disclaimer: despite somewhat thorough investigation, I’m still not *completely* confident about this diagnosis of the problem, given how complex the jump points resolution is during score load (repeatedly resolving things based on incomplete apparently states). It feels like this is more complicated than it should be, and therefore so bug-prone. --- src/braille/tests/braille_tests.cpp | 11 ----------- src/engraving/engravingproject.cpp | 13 +------------ src/engraving/rw/read114/read114.cpp | 16 ++++++++++++---- src/engraving/rw/read206/read206.cpp | 14 ++++++++++++-- src/engraving/rw/read302/read302.cpp | 16 +++++++++++++--- src/engraving/rw/read400/read400.cpp | 16 +++++++++++++--- src/engraving/rw/read410/read410.cpp | 16 +++++++++++++--- src/engraving/rw/read460/read460.cpp | 16 +++++++++++++--- src/engraving/rw/read500/read500.cpp | 16 +++++++++++++--- src/engraving/tests/utils/scorerw.cpp | 9 --------- .../musicxml/tests/musicxml_tests.cpp | 3 --- 11 files changed, 90 insertions(+), 56 deletions(-) diff --git a/src/braille/tests/braille_tests.cpp b/src/braille/tests/braille_tests.cpp index 067598747b1eb..d66844c8207ff 100644 --- a/src/braille/tests/braille_tests.cpp +++ b/src/braille/tests/braille_tests.cpp @@ -40,16 +40,6 @@ class Braille_Tests : public ::testing::Test void brailleSaveTest(const char* file); }; -//--------------------------------------------------------- -// fixupScore -- do required fixups after reading/importing score -//--------------------------------------------------------- - -static void fixupScore(MasterScore* score) -{ - score->connectTies(); - score->masterScore()->rebuildMidiMapping(); -} - static bool saveBraille(MasterScore* score, const String& saveName) { QFile file(saveName); @@ -73,7 +63,6 @@ void Braille_Tests::brailleSaveTest(const char* file) String fileName = String::fromUtf8(file); MasterScore* score = ScoreRW::readScore(BRAILLE_DIR + fileName + u".mscx", false); EXPECT_TRUE(score); - fixupScore(score); score->doLayout(); EXPECT_TRUE(saveCompareBrailleScore(score, fileName + ".brf", BRAILLE_DIR + fileName + "_ref.brf")); delete score; diff --git a/src/engraving/engravingproject.cpp b/src/engraving/engravingproject.cpp index d1e9680569fe0..73202439b297c 100644 --- a/src/engraving/engravingproject.cpp +++ b/src/engraving/engravingproject.cpp @@ -113,22 +113,11 @@ Ret EngravingProject::setupMasterScore(bool forceMode) TRACEFUNC; m_masterScore->createPaddingTable(); - m_masterScore->connectTies(); - m_masterScore->undoRemoveStaleTieJumpPoints(false); - - for (Part* p : m_masterScore->parts()) { - p->updateHarmonyChannels(false); - } - - m_masterScore->rebuildMidiMapping(); - for (Score* s : m_masterScore->scoreList()) { - s->invalidateRepeatList(); - s->setLayoutAll(); s->createPaddingTable(); } - m_masterScore->updateChannel(); + m_masterScore->setLayoutAll(); m_masterScore->update(); Ret ret = checkCorrupted(); diff --git a/src/engraving/rw/read114/read114.cpp b/src/engraving/rw/read114/read114.cpp index b943821c1d934..cf691fed368e1 100644 --- a/src/engraving/rw/read114/read114.cpp +++ b/src/engraving/rw/read114/read114.cpp @@ -3038,8 +3038,6 @@ muse::Ret Read114::readScoreFile(Score* score, XmlReader& e, ReadInOutData* out) } } - masterScore->connectTies(); - // // remove "middle beam" flags from first ChordRest in // measure @@ -3131,6 +3129,18 @@ muse::Ret Read114::readScoreFile(Score* score, XmlReader& e, ReadInOutData* out) } } + masterScore->setUpTempoMap(); + // While reading the score, some elements might use `score->repeatList()` (which is incorrect + // anyway, because the repeatList will be incomplete because the score is incomplete, but some + // elements still do it). + // `score->repeatList()` calls `_repeatList->update()`; the repeat list then thinks that it is + // up-to-date from that point. But we weren't finished reading the score, so the score will still + // change. We need to tell the repeat list about that, so that it will be updated next time + // someone uses it. + masterScore->invalidateRepeatList(); + masterScore->connectTies(); + masterScore->undoRemoveStaleTieJumpPoints(false); + // create excerpts { std::vector readExcerpts; @@ -3156,8 +3166,6 @@ muse::Ret Read114::readScoreFile(Score* score, XmlReader& e, ReadInOutData* out) masterScore->style().set(Sid::voltaPosAbove, PointF(0.0, -2.0f)); } - masterScore->setUpTempoMap(); - for (Part* p : masterScore->parts()) { p->updateHarmonyChannels(false); } diff --git a/src/engraving/rw/read206/read206.cpp b/src/engraving/rw/read206/read206.cpp index f09092f4b47f8..dfcf0607f3430 100644 --- a/src/engraving/rw/read206/read206.cpp +++ b/src/engraving/rw/read206/read206.cpp @@ -3582,11 +3582,21 @@ bool Read206::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx) return false; } - score->connectTies(); - score->setFileDivision(Constants::DIVISION); score->setUpTempoMap(); + if (score->isMaster()) { + // While reading the score, some elements might use `score->repeatList()` (which is incorrect + // anyway, because the repeatList will be incomplete because the score is incomplete, but some + // elements still do it). + // `score->repeatList()` calls `_repeatList->update()`; the repeat list then thinks that it is + // up-to-date from that point. But we weren't finished reading the score, so the score will still + // change. We need to tell the repeat list about that, so that it will be updated next time + // someone uses it. + static_cast(score)->invalidateRepeatList(); + } + score->connectTies(); + score->undoRemoveStaleTieJumpPoints(false); for (Part* p : score->parts()) { p->updateHarmonyChannels(false); diff --git a/src/engraving/rw/read302/read302.cpp b/src/engraving/rw/read302/read302.cpp index 91874391d215d..31676b0d1217f 100644 --- a/src/engraving/rw/read302/read302.cpp +++ b/src/engraving/rw/read302/read302.cpp @@ -197,8 +197,6 @@ bool Read302::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx) return false; } - score->connectTies(); - score->m_fileDivision = Constants::DIVISION; if (score->mscVersion() == 302) { @@ -218,8 +216,20 @@ bool Read302::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx) } score->setUpTempoMap(); + if (score->isMaster()) { + // While reading the score, some elements might use `score->repeatList()` (which is incorrect + // anyway, because the repeatList will be incomplete because the score is incomplete, but some + // elements still do it). + // `score->repeatList()` calls `_repeatList->update()`; the repeat list then thinks that it is + // up-to-date from that point. But we weren't finished reading the score, so the score will still + // change. We need to tell the repeat list about that, so that it will be updated next time + // someone uses it. + static_cast(score)->invalidateRepeatList(); + } + score->connectTies(); + score->undoRemoveStaleTieJumpPoints(false); - for (Part* p : score->m_parts) { + for (Part* p : score->parts()) { p->updateHarmonyChannels(false); } diff --git a/src/engraving/rw/read400/read400.cpp b/src/engraving/rw/read400/read400.cpp index 3aea1d272b61c..9427389fc5212 100644 --- a/src/engraving/rw/read400/read400.cpp +++ b/src/engraving/rw/read400/read400.cpp @@ -293,8 +293,6 @@ bool Read400::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx) return false; } - score->connectTies(); - score->m_fileDivision = Constants::DIVISION; // Make sure every instrument has an instrumentId set. @@ -305,8 +303,20 @@ bool Read400::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx) } score->setUpTempoMap(); + if (score->isMaster()) { + // While reading the score, some elements might use `score->repeatList()` (which is incorrect + // anyway, because the repeatList will be incomplete because the score is incomplete, but some + // elements still do it). + // `score->repeatList()` calls `_repeatList->update()`; the repeat list then thinks that it is + // up-to-date from that point. But we weren't finished reading the score, so the score will still + // change. We need to tell the repeat list about that, so that it will be updated next time + // someone uses it. + static_cast(score)->invalidateRepeatList(); + } + score->connectTies(); + score->undoRemoveStaleTieJumpPoints(false); - for (Part* p : score->m_parts) { + for (Part* p : score->parts()) { p->updateHarmonyChannels(false); } diff --git a/src/engraving/rw/read410/read410.cpp b/src/engraving/rw/read410/read410.cpp index 8e42fda561c85..5d8e7be62e51e 100644 --- a/src/engraving/rw/read410/read410.cpp +++ b/src/engraving/rw/read410/read410.cpp @@ -288,8 +288,6 @@ bool Read410::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx) return false; } - score->connectTies(); - score->m_fileDivision = Constants::DIVISION; // Make sure every instrument has an instrumentId set. @@ -300,8 +298,20 @@ bool Read410::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx) } score->setUpTempoMap(); + if (score->isMaster()) { + // While reading the score, some elements might use `score->repeatList()` (which is incorrect + // anyway, because the repeatList will be incomplete because the score is incomplete, but some + // elements still do it). + // `score->repeatList()` calls `_repeatList->update()`; the repeat list then thinks that it is + // up-to-date from that point. But we weren't finished reading the score, so the score will still + // change. We need to tell the repeat list about that, so that it will be updated next time + // someone uses it. + static_cast(score)->invalidateRepeatList(); + } + score->connectTies(); + score->undoRemoveStaleTieJumpPoints(false); - for (Part* p : score->m_parts) { + for (Part* p : score->parts()) { p->updateHarmonyChannels(false); } diff --git a/src/engraving/rw/read460/read460.cpp b/src/engraving/rw/read460/read460.cpp index 855907b467765..3f1ece836d8ef 100644 --- a/src/engraving/rw/read460/read460.cpp +++ b/src/engraving/rw/read460/read460.cpp @@ -276,8 +276,6 @@ bool Read460::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx) return false; } - score->connectTies(); - for (Spanner* sp : score->unmanagedSpanners()) { if (sp->isLyricsLine() && toLyricsLine(sp)->isDash()) { LyricsLine* line = toLyricsLine(sp); @@ -300,8 +298,20 @@ bool Read460::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx) } score->setUpTempoMap(); + if (score->isMaster()) { + // While reading the score, some elements might use `score->repeatList()` (which is incorrect + // anyway, because the repeatList will be incomplete because the score is incomplete, but some + // elements still do it). + // `score->repeatList()` calls `_repeatList->update()`; the repeat list then thinks that it is + // up-to-date from that point. But we weren't finished reading the score, so the score will still + // change. We need to tell the repeat list about that, so that it will be updated next time + // someone uses it. + static_cast(score)->invalidateRepeatList(); + } + score->connectTies(); + score->undoRemoveStaleTieJumpPoints(false); - for (Part* p : score->m_parts) { + for (Part* p : score->parts()) { p->updateHarmonyChannels(false); } diff --git a/src/engraving/rw/read500/read500.cpp b/src/engraving/rw/read500/read500.cpp index a6987f66c3d0f..5696ceea1c873 100644 --- a/src/engraving/rw/read500/read500.cpp +++ b/src/engraving/rw/read500/read500.cpp @@ -281,8 +281,6 @@ bool Read500::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx) return false; } - score->connectTies(); - for (Spanner* sp : score->unmanagedSpanners()) { if (sp->isLyricsLine() && toLyricsLine(sp)->isDash()) { LyricsLine* line = toLyricsLine(sp); @@ -305,8 +303,20 @@ bool Read500::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx) } score->setUpTempoMap(); + if (score->isMaster()) { + // While reading the score, some elements might use `score->repeatList()` (which is incorrect + // anyway, because the repeatList will be incomplete because the score is incomplete, but some + // elements still do it). + // `score->repeatList()` calls `_repeatList->update()`; the repeat list then thinks that it is + // up-to-date from that point. But we weren't finished reading the score, so the score will still + // change. We need to tell the repeat list about that, so that it will be updated next time + // someone uses it. + static_cast(score)->invalidateRepeatList(); + } + score->connectTies(); + score->undoRemoveStaleTieJumpPoints(false); - for (Part* p : score->m_parts) { + for (Part* p : score->parts()) { p->updateHarmonyChannels(false); } diff --git a/src/engraving/tests/utils/scorerw.cpp b/src/engraving/tests/utils/scorerw.cpp index f9ae51a618986..b38b61f362ae9 100644 --- a/src/engraving/tests/utils/scorerw.cpp +++ b/src/engraving/tests/utils/scorerw.cpp @@ -85,15 +85,6 @@ MasterScore* ScoreRW::readScore(const String& name, bool isAbsolutePath, ImportF s->doLayout(); } - // While reading the score, some elements might use `score->repeatList()` (which is incorrect - // anyway, because the repeatList will be incomplete because the score is incomplete, but some - // elements still do it). - // `score->repeatList()` calls `_repeatList->update()`; the repeat list then thinks that it is - // up-to-date from that point. But we weren't finished reading the score, so the score will still - // change. We need to tell the repeat list about that, so that it will be updated next time - // someone uses it. - score->invalidateRepeatList(); - return score; } diff --git a/src/importexport/musicxml/tests/musicxml_tests.cpp b/src/importexport/musicxml/tests/musicxml_tests.cpp index cc47ad7cde8db..ca304c438b7da 100644 --- a/src/importexport/musicxml/tests/musicxml_tests.cpp +++ b/src/importexport/musicxml/tests/musicxml_tests.cpp @@ -220,7 +220,6 @@ void MusicXml_Tests::musicXmlMscxExportTestRef(const char* file, bool exportLayo String fileName = String::fromUtf8(file); MasterScore* score = readScore(XML_IO_DATA_DIR + fileName + u".mscx"); ASSERT_TRUE(score); - fixupScore(score); score->doLayout(); EXPECT_TRUE(saveCompareMusicXmlScore(score, fileName + u".xml", XML_IO_DATA_DIR + fileName + u"_ref.xml")); @@ -245,7 +244,6 @@ void MusicXml_Tests::musicXmlMscxExportTestRefBreaks(const char* file) String fileName = String::fromUtf8(file); MasterScore* score = readScore(XML_IO_DATA_DIR + fileName + u".mscx"); ASSERT_TRUE(score); - fixupScore(score); score->doLayout(); setValue(PREF_EXPORT_MUSICXML_EXPORTBREAKS, Val(IMusicXmlConfiguration::MusicXmlExportBreaksType::No)); @@ -273,7 +271,6 @@ void MusicXml_Tests::musicXmlMscxExportTestRefInvisibleElements(const char* file String fileName = String::fromUtf8(file); MasterScore* score = readScore(XML_IO_DATA_DIR + fileName + u".mscx"); ASSERT_TRUE(score); - fixupScore(score); score->doLayout(); setValue(PREF_EXPORT_MUSICXML_EXPORTINVISIBLE, Val(true)); From 14efa0285dd840a1a2768020643dea833939bf41 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Sat, 6 Jun 2026 14:06:36 +0200 Subject: [PATCH 18/18] Fix repeat list being wrong before layout for cross-staff voltas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generated by Claude Opus 4.8: Measure-anchored spanners (voltas, ...) in the location-based file format are split across two connectors: a start connector that carries the spanner and a anchor, and an end connector with a anchor. In ConnectorInfoReader::readAddConnector, the start branch called addSpanner(), which immediately computes the cached start/end elements. But at that point tick2/track2 are not set yet, so the end element was cached one measure too short (tick2 == tick). The end branch then set the real tick2/track2 via the direct setters, which bypass property-system invalidation, and never recomputed the end element. Nothing else recomputed it until layout. For voltas confined to a single staff this was masked, because StaffRead::readStaff calls Score::checkSpanner() per measure, which recomputes every spanner's start/end. But voltas duplicated onto a non-top staff use a cross-staff connector resolved by reconnectBrokenConnectors() only after all staves are read, so checkSpanner never runs for them again and the stale (too-short) end measure survived. As a result, RepeatList::collectRepeatListElements() read a wrong Volta::endMeasure() and produced an incorrect playback order until a layout pass recomputed the spanner end element. Compute each anchor's element exactly when that anchor is finalized: defer the end computation in the start branch (addSpanner with computeStartEnd = false, then computeStartElement), and call computeEndElement in the end branch once tick2/track2 are set. Both branches run back-to-back within a single addToScore pass, so each element is computed once, with correct values, and the previous wasted (wrong) end computation is removed. Applied identically to read400, read410, read460 and read500. (end of AI-generated message) (I wonder if “cross staff voltas” are a valid concept, but apparently this file contains one, so…) --- src/engraving/rw/read400/connectorinforeader.cpp | 8 +++++++- src/engraving/rw/read410/connectorinforeader.cpp | 8 +++++++- src/engraving/rw/read460/connectorinforeader.cpp | 8 +++++++- src/engraving/rw/read500/connectorinforeader.cpp | 8 +++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/engraving/rw/read400/connectorinforeader.cpp b/src/engraving/rw/read400/connectorinforeader.cpp index b9088b4102dff..d798bea36e179 100644 --- a/src/engraving/rw/read400/connectorinforeader.cpp +++ b/src/engraving/rw/read400/connectorinforeader.cpp @@ -341,10 +341,16 @@ void ConnectorInfoReader::readAddConnector(Measure* item, ConnectorInfoReader* i sp->setTrack(l.track()); sp->setTrack2(sp->track()); sp->setTick(spTick); - item->score()->addSpanner(sp); + // Defer computing the end element: the end anchor (tick2/track2) is only known once the + // matching end connector is processed (below). Computing it here would cache a wrong value + // (tick2 == tick) that nothing recomputes until layout. See readAddConnector end branch. + item->score()->addSpanner(sp, /*computeStartEnd=*/ false); + sp->computeStartElement(); } else if (info->isEnd()) { sp->setTrack2(l.track()); sp->setTick2(spTick); + // Now that the end anchor is set, compute the end element once with the final value. + sp->computeEndElement(); } } break; diff --git a/src/engraving/rw/read410/connectorinforeader.cpp b/src/engraving/rw/read410/connectorinforeader.cpp index 26aa17975c7ad..f28c72f9d6d87 100644 --- a/src/engraving/rw/read410/connectorinforeader.cpp +++ b/src/engraving/rw/read410/connectorinforeader.cpp @@ -349,10 +349,16 @@ void ConnectorInfoReader::readAddConnector(Measure* item, ConnectorInfoReader* i sp->setTrack(l.track()); sp->setTrack2(sp->track()); sp->setTick(spTick); - item->score()->addSpanner(sp); + // Defer computing the end element: the end anchor (tick2/track2) is only known once the + // matching end connector is processed (below). Computing it here would cache a wrong value + // (tick2 == tick) that nothing recomputes until layout. See readAddConnector end branch. + item->score()->addSpanner(sp, /*computeStartEnd=*/ false); + sp->computeStartElement(); } else if (info->isEnd()) { sp->setTrack2(l.track()); sp->setTick2(spTick); + // Now that the end anchor is set, compute the end element once with the final value. + sp->computeEndElement(); } } break; diff --git a/src/engraving/rw/read460/connectorinforeader.cpp b/src/engraving/rw/read460/connectorinforeader.cpp index a1239e28a20b7..68601f8bb0510 100644 --- a/src/engraving/rw/read460/connectorinforeader.cpp +++ b/src/engraving/rw/read460/connectorinforeader.cpp @@ -350,10 +350,16 @@ void ConnectorInfoReader::readAddConnector(Measure* item, ConnectorInfoReader* i sp->setTrack(l.track()); sp->setTrack2(sp->track()); sp->setTick(spTick); - item->score()->addSpanner(sp); + // Defer computing the end element: the end anchor (tick2/track2) is only known once the + // matching end connector is processed (below). Computing it here would cache a wrong value + // (tick2 == tick) that nothing recomputes until layout. See readAddConnector end branch. + item->score()->addSpanner(sp, /*computeStartEnd=*/ false); + sp->computeStartElement(); } else if (info->isEnd()) { sp->setTrack2(l.track()); sp->setTick2(spTick); + // Now that the end anchor is set, compute the end element once with the final value. + sp->computeEndElement(); } } break; diff --git a/src/engraving/rw/read500/connectorinforeader.cpp b/src/engraving/rw/read500/connectorinforeader.cpp index bf267f024b213..3b49125534fe6 100644 --- a/src/engraving/rw/read500/connectorinforeader.cpp +++ b/src/engraving/rw/read500/connectorinforeader.cpp @@ -350,10 +350,16 @@ void ConnectorInfoReader::readAddConnector(Measure* item, ConnectorInfoReader* i sp->setTrack(l.track()); sp->setTrack2(sp->track()); sp->setTick(spTick); - item->score()->addSpanner(sp); + // Defer computing the end element: the end anchor (tick2/track2) is only known once the + // matching end connector is processed (below). Computing it here would cache a wrong value + // (tick2 == tick) that nothing recomputes until layout. See readAddConnector end branch. + item->score()->addSpanner(sp, /*computeStartEnd=*/ false); + sp->computeStartElement(); } else if (info->isEnd()) { sp->setTrack2(l.track()); sp->setTick2(spTick); + // Now that the end anchor is set, compute the end element once with the final value. + sp->computeEndElement(); } } break;