From e2d15a55e3c8d0f70bccd26e34a4f705b1d12fd3 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Wed, 29 Apr 2026 16:26:26 +0100 Subject: [PATCH 01/10] Rename SystemLock to RangeLock --- src/engraving/api/v1/elements.cpp | 4 +- src/engraving/dom/dom.cmake | 4 +- src/engraving/dom/factory.cpp | 4 +- src/engraving/dom/factory.h | 4 +- src/engraving/dom/measurebase.cpp | 6 +- src/engraving/dom/measurebase.h | 4 +- .../dom/{systemlock.cpp => rangelock.cpp} | 66 ++++++------- .../dom/{systemlock.h => rangelock.h} | 30 +++--- src/engraving/dom/score.cpp | 4 +- src/engraving/dom/score.h | 10 +- src/engraving/dom/system.cpp | 2 +- src/engraving/dom/system.h | 4 +- src/engraving/editing/edit.cpp | 2 +- src/engraving/editing/editsystemlocks.cpp | 94 +++++++++---------- src/engraving/editing/editsystemlocks.h | 8 +- .../rendering/score/layoutcontext.cpp | 2 +- src/engraving/rendering/score/layoutcontext.h | 4 +- .../score/scorehorizontalviewlayout.cpp | 4 +- .../rendering/score/systemlayout.cpp | 6 +- src/engraving/rendering/score/tdraw.cpp | 2 +- src/engraving/rendering/score/tlayout.cpp | 2 +- src/engraving/rw/read410/tread.cpp | 2 +- src/engraving/rw/read460/tread.cpp | 2 +- src/engraving/rw/read500/tread.cpp | 2 +- src/engraving/rw/write/twrite.cpp | 6 +- src/engraving/rw/write/twrite.h | 2 +- src/engraving/tests/system_locks_tests.cpp | 10 +- 27 files changed, 145 insertions(+), 145 deletions(-) rename src/engraving/dom/{systemlock.cpp => rangelock.cpp} (65%) rename src/engraving/dom/{systemlock.h => rangelock.h} (70%) diff --git a/src/engraving/api/v1/elements.cpp b/src/engraving/api/v1/elements.cpp index a2628bad78ea6..71634a5129cfb 100644 --- a/src/engraving/api/v1/elements.cpp +++ b/src/engraving/api/v1/elements.cpp @@ -503,11 +503,11 @@ void System::setIsLocked(bool locked) if (locked == isLocked()) { return; } - const mu::engraving::SystemLock* currentLock = system()->systemLock(); + const mu::engraving::RangeLock* currentLock = system()->systemLock(); if (currentLock && !locked) { EditSystemLocks::undoRemoveSystemLock(system()->score(), currentLock); } else if (!currentLock && locked) { - EditSystemLocks::undoAddSystemLock(system()->score(), new mu::engraving::SystemLock(system()->first(), system()->last())); + EditSystemLocks::undoAddSystemLock(system()->score(), new mu::engraving::RangeLock(system()->first(), system()->last())); } } diff --git a/src/engraving/dom/dom.cmake b/src/engraving/dom/dom.cmake index 42438dff83dc3..86ca59c29741c 100644 --- a/src/engraving/dom/dom.cmake +++ b/src/engraving/dom/dom.cmake @@ -243,6 +243,8 @@ set(DOM_SRC ${CMAKE_CURRENT_LIST_DIR}/property.h ${CMAKE_CURRENT_LIST_DIR}/range.cpp ${CMAKE_CURRENT_LIST_DIR}/range.h + ${CMAKE_CURRENT_LIST_DIR}/rangelock.cpp + ${CMAKE_CURRENT_LIST_DIR}/rangelock.h ${CMAKE_CURRENT_LIST_DIR}/rasgueado.cpp ${CMAKE_CURRENT_LIST_DIR}/rasgueado.h ${CMAKE_CURRENT_LIST_DIR}/realizedharmony.cpp @@ -321,8 +323,6 @@ set(DOM_SRC ${CMAKE_CURRENT_LIST_DIR}/symbol.h ${CMAKE_CURRENT_LIST_DIR}/synthesizerstate.cpp ${CMAKE_CURRENT_LIST_DIR}/synthesizerstate.h - ${CMAKE_CURRENT_LIST_DIR}/systemlock.cpp - ${CMAKE_CURRENT_LIST_DIR}/systemlock.h ${CMAKE_CURRENT_LIST_DIR}/system.cpp ${CMAKE_CURRENT_LIST_DIR}/system.h ${CMAKE_CURRENT_LIST_DIR}/systemdivider.cpp diff --git a/src/engraving/dom/factory.cpp b/src/engraving/dom/factory.cpp index bca4c75dd0327..6e02e2c03a0b7 100644 --- a/src/engraving/dom/factory.cpp +++ b/src/engraving/dom/factory.cpp @@ -101,7 +101,7 @@ #include "stringtunings.h" #include "system.h" #include "systemdivider.h" -#include "systemlock.h" +#include "rangelock.h" #include "systemtext.h" #include "soundflag.h" #include "tapping.h" @@ -738,7 +738,7 @@ CREATE_ITEM_IMPL(TimeTickAnchor, Segment, isAccessibleEnabled) CREATE_ITEM_IMPL(StaffVisibilityIndicator, System, isAccessibleEnabled) -SystemLockIndicator* Factory::createSystemLockIndicator(System * parent, const SystemLock * lock, bool isAccessibleEnabled) +SystemLockIndicator* Factory::createSystemLockIndicator(System * parent, const RangeLock * lock, bool isAccessibleEnabled) { SystemLockIndicator* sli = new SystemLockIndicator(parent, lock); sli->setAccessibleEnabled(isAccessibleEnabled); diff --git a/src/engraving/dom/factory.h b/src/engraving/dom/factory.h index 3485e3cf32424..568808a49106a 100644 --- a/src/engraving/dom/factory.h +++ b/src/engraving/dom/factory.h @@ -28,7 +28,7 @@ namespace mu::engraving { class Instrument; class RootItem; -class SystemLock; +class RangeLock; class TremoloSingleChord; class TremoloTwoChord; @@ -120,7 +120,7 @@ class Factory static StaffVisibilityIndicator* createStaffVisibilityIndicator(System* parent, bool isAccessibleEnabled = true); - static SystemLockIndicator* createSystemLockIndicator(System* parent, const SystemLock* lock, bool isAccessibleEnabled = true); + static SystemLockIndicator* createSystemLockIndicator(System* parent, const RangeLock* lock, bool isAccessibleEnabled = true); static SystemLockIndicator* copySystemLockIndicator(const SystemLockIndicator& src); static Lyrics* createLyrics(ChordRest* parent, bool isAccessibleEnabled = true); diff --git a/src/engraving/dom/measurebase.cpp b/src/engraving/dom/measurebase.cpp index 3452311c315ff..2271e6fa22e98 100644 --- a/src/engraving/dom/measurebase.cpp +++ b/src/engraving/dom/measurebase.cpp @@ -638,20 +638,20 @@ bool MeasureBase::isBefore(const MeasureBase* other) const return false; } -const SystemLock* MeasureBase::systemLock() const +const RangeLock* MeasureBase::systemLock() const { return score()->systemLocks()->lockContaining(this); } bool MeasureBase::isStartOfSystemLock() const { - const SystemLock* lock = score()->systemLocks()->lockStartingAt(this); + const RangeLock* lock = score()->systemLocks()->lockStartingAt(this); return lock != nullptr; } bool MeasureBase::isEndOfSystemLock() const { - const SystemLock* lock = systemLock(); + const RangeLock* lock = systemLock(); return lock && lock->endMB() == this; } diff --git a/src/engraving/dom/measurebase.h b/src/engraving/dom/measurebase.h index 5c5dc32981e75..b67f15b1d6c39 100644 --- a/src/engraving/dom/measurebase.h +++ b/src/engraving/dom/measurebase.h @@ -35,7 +35,7 @@ class LayoutBreak; class Measure; class Score; class System; -class SystemLock; +class RangeLock; //--------------------------------------------------------- // Repeat @@ -160,7 +160,7 @@ class MeasureBase : public EngravingItem bool isAfter(const MeasureBase* other) const { return !isBeforeOrEqual(other); } bool isAfterOrEqual(const MeasureBase* other) const { return !isBefore(other); } - const SystemLock* systemLock() const; + const RangeLock* systemLock() const; bool isStartOfSystemLock() const; bool isEndOfSystemLock() const; diff --git a/src/engraving/dom/systemlock.cpp b/src/engraving/dom/rangelock.cpp similarity index 65% rename from src/engraving/dom/systemlock.cpp rename to src/engraving/dom/rangelock.cpp index 53b5fff272d06..978c135ab32d5 100644 --- a/src/engraving/dom/systemlock.cpp +++ b/src/engraving/dom/rangelock.cpp @@ -25,65 +25,65 @@ #include "page.h" #include "score.h" #include "system.h" -#include "systemlock.h" +#include "rangelock.h" #include "log.h" using namespace muse::draw; namespace mu::engraving { -bool SystemLock::contains(const MeasureBase* mb) const +bool RangeLock::contains(const MeasureBase* mb) const { return m_startMB->isBeforeOrEqual(mb) && mb->isBeforeOrEqual(m_endMB); } -void SystemLocks::add(const SystemLock* lock) +void RangeLocks::add(const RangeLock* lock) { - m_systemLocks.emplace(lock->startMB(), lock); + m_rangeLocks.emplace(lock->startMB(), lock); #ifndef NDEBUG sanityCheck(); #endif } -void SystemLocks::remove(const SystemLock* lock) +void RangeLocks::remove(const RangeLock* lock) { - m_systemLocks.erase(lock->startMB()); + m_rangeLocks.erase(lock->startMB()); #ifndef NDEBUG sanityCheck(); #endif } -const SystemLock* SystemLocks::lockStartingAt(const MeasureBase* mb) const +const RangeLock* RangeLocks::lockStartingAt(const MeasureBase* mb) const { - auto iter = m_systemLocks.find(mb); - return iter != m_systemLocks.end() ? iter->second : nullptr; + auto iter = m_rangeLocks.find(mb); + return iter != m_rangeLocks.end() ? iter->second : nullptr; } -const SystemLock* SystemLocks::lockContaining(const MeasureBase* mb) const +const RangeLock* RangeLocks::lockContaining(const MeasureBase* mb) const { - if (m_systemLocks.empty()) { + if (m_rangeLocks.empty()) { return nullptr; } - auto iter = m_systemLocks.lower_bound(mb); - if (iter != m_systemLocks.begin() - && (iter == m_systemLocks.end() || mb->isBefore(iter->second->startMB()))) { + auto iter = m_rangeLocks.lower_bound(mb); + if (iter != m_rangeLocks.begin() + && (iter == m_rangeLocks.end() || mb->isBefore(iter->second->startMB()))) { --iter; } - const SystemLock* lock = iter->second; + const RangeLock* lock = iter->second; return lock->contains(mb) ? lock : nullptr; } -std::vector SystemLocks::locksContainedInRange(const MeasureBase* start, const MeasureBase* end) const +std::vector RangeLocks::locksContainedInRange(const MeasureBase* start, const MeasureBase* end) const { - std::vector result; + std::vector result; - for (auto& pair : m_systemLocks) { - const SystemLock* lock = pair.second; + for (auto& pair : m_rangeLocks) { + const RangeLock* lock = pair.second; if (start->isBeforeOrEqual(lock->startMB()) && lock->endMB()->isBeforeOrEqual(end)) { result.push_back(lock); } @@ -95,33 +95,33 @@ std::vector SystemLocks::locksContainedInRange(const MeasureB return result; } -std::vector SystemLocks::allLocks() const +std::vector RangeLocks::allLocks() const { - std::vector locks; - locks.reserve(m_systemLocks.size()); - for (auto& pair : m_systemLocks) { + std::vector locks; + locks.reserve(m_rangeLocks.size()); + for (auto& pair : m_rangeLocks) { locks.push_back(pair.second); } return locks; } #ifndef NDEBUG -void SystemLocks::sanityCheck() +void RangeLocks::sanityCheck() { - for (auto iter = m_systemLocks.begin(); iter != m_systemLocks.end();) { + for (auto iter = m_rangeLocks.begin(); iter != m_rangeLocks.end();) { auto curIter = iter; auto nextIter = ++iter; - if (nextIter == m_systemLocks.end()) { + if (nextIter == m_rangeLocks.end()) { break; } const MeasureBase* curMB = curIter->first; - const SystemLock* curSysLock = curIter->second; + const RangeLock* curSysLock = curIter->second; DO_ASSERT(curSysLock->startMB() == curMB); const MeasureBase* nextMB = nextIter->first; - const SystemLock* nextSysLock = nextIter->second; + const RangeLock* nextSysLock = nextIter->second; DO_ASSERT(nextSysLock->startMB() == nextMB); DO_ASSERT(curMB->isBefore(nextMB)); @@ -129,20 +129,20 @@ void SystemLocks::sanityCheck() } } -void SystemLocks::dump() +void RangeLocks::dump() { - for (auto& pair : m_systemLocks) { - const SystemLock* sl = pair.second; + for (auto& pair : m_rangeLocks) { + const RangeLock* sl = pair.second; const Measure* startMeasure = sl->startMB()->isMeasure() ? toMeasure(sl->startMB()) : sl->startMB()->prevMeasure(); const Measure* endMeasure = sl->endMB()->isMeasure() ? toMeasure(sl->endMB()) : sl->endMB()->prevMeasure(); - LOGD() << "SystemLock --- Start measure: " << (startMeasure ? startMeasure->measureNumber() : -1) + LOGD() << "RangeLock --- Start measure: " << (startMeasure ? startMeasure->measureNumber() : -1) << ", End Measure: " << (endMeasure ? endMeasure->measureNumber() : -1); } } #endif -SystemLockIndicator::SystemLockIndicator(System* parent, const SystemLock* lock) +SystemLockIndicator::SystemLockIndicator(System* parent, const RangeLock* lock) : IndicatorIcon(ElementType::SYSTEM_LOCK_INDICATOR, parent, ElementFlag::SYSTEM | ElementFlag::GENERATED), m_systemLock(lock) {} void SystemLockIndicator::setSelected(bool v) diff --git a/src/engraving/dom/systemlock.h b/src/engraving/dom/rangelock.h similarity index 70% rename from src/engraving/dom/systemlock.h rename to src/engraving/dom/rangelock.h index 0d46f500a025e..1aed7c8e9a35a 100644 --- a/src/engraving/dom/systemlock.h +++ b/src/engraving/dom/rangelock.h @@ -28,10 +28,10 @@ #include "measurebase.h" namespace mu::engraving { -class SystemLock +class RangeLock { public: - SystemLock(MeasureBase* start, MeasureBase* end) + RangeLock(MeasureBase* start, MeasureBase* end) : m_startMB(start), m_endMB(end) { assert(m_startMB->isMeasure() || m_startMB->isHBox()); @@ -49,19 +49,19 @@ class SystemLock MeasureBase* m_endMB; }; -class SystemLocks +class RangeLocks { public: - void add(const SystemLock* lock); - void remove(const SystemLock* lock); - void removeLockStartingAt(const MeasureBase* mb) { m_systemLocks.erase(mb); } - void clear() { m_systemLocks.clear(); } + void add(const RangeLock* lock); + void remove(const RangeLock* lock); + void removeLockStartingAt(const MeasureBase* mb) { m_rangeLocks.erase(mb); } + void clear() { m_rangeLocks.clear(); } - const SystemLock* lockStartingAt(const MeasureBase* mb) const; - const SystemLock* lockContaining(const MeasureBase* mb) const; - std::vector locksContainedInRange(const MeasureBase* start, const MeasureBase* end) const; + const RangeLock* lockStartingAt(const MeasureBase* mb) const; + const RangeLock* lockContaining(const MeasureBase* mb) const; + std::vector locksContainedInRange(const MeasureBase* start, const MeasureBase* end) const; - std::vector allLocks() const; + std::vector allLocks() const; private: #ifndef NDEBUG @@ -77,7 +77,7 @@ class SystemLocks } }; - std::map m_systemLocks; + std::map m_rangeLocks; }; class SystemLockIndicator : public IndicatorIcon @@ -86,17 +86,17 @@ class SystemLockIndicator : public IndicatorIcon DECLARE_CLASSOF(ElementType::SYSTEM_LOCK_INDICATOR) public: - SystemLockIndicator(System* parent, const SystemLock* lock); + SystemLockIndicator(System* parent, const RangeLock* lock); void setSelected(bool v) override; - const SystemLock* systemLock() const { return m_systemLock; } + const RangeLock* systemLock() const { return m_systemLock; } char16_t iconCode() const override { return 0xF487; } String formatBarsAndBeats() const override; private: - const SystemLock* m_systemLock = nullptr; + const RangeLock* m_systemLock = nullptr; }; } // namespace mu::engraving diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index 99effd01ff0dd..59c793af5a89e 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -6032,7 +6032,7 @@ void Score::autoUpdateSpatium() createPaddingTable(); } -void Score::addSystemLock(const SystemLock* lock) +void Score::addSystemLock(const RangeLock* lock) { m_systemLocks.add(lock); @@ -6040,7 +6040,7 @@ void Score::addSystemLock(const SystemLock* lock) lock->endMB()->triggerLayout(); } -void Score::removeSystemLock(const SystemLock* lock) +void Score::removeSystemLock(const RangeLock* lock) { m_systemLocks.remove(lock); diff --git a/src/engraving/dom/score.h b/src/engraving/dom/score.h index bb40e7586eb9f..6687a3c12ec56 100644 --- a/src/engraving/dom/score.h +++ b/src/engraving/dom/score.h @@ -65,7 +65,7 @@ #include "select.h" #include "spannermap.h" #include "synthesizerstate.h" -#include "systemlock.h" +#include "rangelock.h" #include "tuplet.h" namespace mu::engraving { @@ -1051,9 +1051,9 @@ class Score : public EngravingObject, public muse::Contextable void autoUpdateSpatium(); - const SystemLocks* systemLocks() const { return &m_systemLocks; } - void addSystemLock(const SystemLock* lock); - void removeSystemLock(const SystemLock* lock); + const RangeLocks* systemLocks() const { return &m_systemLocks; } + void addSystemLock(const RangeLock* lock); + void removeSystemLock(const RangeLock* lock); void clearSystemLocks() { m_systemLocks.clear(); } void rebuildFretBox(); @@ -1170,7 +1170,7 @@ class Score : public EngravingObject, public muse::Contextable std::vector m_parts; std::vector m_staves; std::vector m_systemObjectStaves; - SystemLocks m_systemLocks; + RangeLocks m_systemLocks; SpannerMap m_spanner; std::set m_unmanagedSpanner; diff --git a/src/engraving/dom/system.cpp b/src/engraving/dom/system.cpp index 757724b8dbdf4..f714acbe2dc14 100644 --- a/src/engraving/dom/system.cpp +++ b/src/engraving/dom/system.cpp @@ -314,7 +314,7 @@ bool System::isLocked() const return m_ml.front()->isStartOfSystemLock(); } -const SystemLock* System::systemLock() const +const RangeLock* System::systemLock() const { return m_ml.front()->systemLock(); } diff --git a/src/engraving/dom/system.h b/src/engraving/dom/system.h index 554f201bd7719..e934b2ee40668 100644 --- a/src/engraving/dom/system.h +++ b/src/engraving/dom/system.h @@ -38,7 +38,7 @@ class MeasureBase; class Page; class SpannerSegment; class StaffVisibilityIndicator; -class SystemLock; +class RangeLock; //--------------------------------------------------------- // SysStaff @@ -214,7 +214,7 @@ class System final : public EngravingItem void setHasStaffVisibilityIndicator(bool has); bool isLocked() const; - const SystemLock* systemLock() const; + const RangeLock* systemLock() const; const std::vector lockIndicators() const { return m_lockIndicators; } void addLockIndicator(SystemLockIndicator* sli); diff --git a/src/engraving/editing/edit.cpp b/src/engraving/editing/edit.cpp index 702cc7654d3cb..da7aaed2a1892 100644 --- a/src/engraving/editing/edit.cpp +++ b/src/engraving/editing/edit.cpp @@ -3390,7 +3390,7 @@ void Score::deleteItem(EngravingItem* el) case ElementType::SYSTEM_LOCK_INDICATOR: { - const SystemLock* systemLock = toSystemLockIndicator(el)->systemLock(); + const RangeLock* systemLock = toSystemLockIndicator(el)->systemLock(); EditSystemLocks::undoRemoveSystemLock(this, systemLock); } break; diff --git a/src/engraving/editing/editsystemlocks.cpp b/src/engraving/editing/editsystemlocks.cpp index 266926fbf6f1a..2515decb65a00 100644 --- a/src/engraving/editing/editsystemlocks.cpp +++ b/src/engraving/editing/editsystemlocks.cpp @@ -27,7 +27,7 @@ #include "../dom/measurebase.h" #include "../dom/score.h" #include "../dom/system.h" -#include "../dom/systemlock.h" +#include "../dom/rangelock.h" using namespace mu::engraving; @@ -39,9 +39,9 @@ class AddSystemLock : public UndoableCommand { OBJECT_ALLOCATOR(engraving, AddSystemLock) - const SystemLock* m_systemLock; + const RangeLock* m_systemLock; public: - AddSystemLock(const SystemLock* systemLock) + AddSystemLock(const RangeLock* systemLock) : m_systemLock(systemLock) {} void undo(EditData*) override @@ -80,9 +80,9 @@ class RemoveSystemLock : public UndoableCommand { OBJECT_ALLOCATOR(engraving, RemoveSystemLock) - const SystemLock* m_systemLock; + const RangeLock* m_systemLock; public: - RemoveSystemLock(const SystemLock* systemLock) + RemoveSystemLock(const RangeLock* systemLock) : m_systemLock(systemLock) {} void undo(EditData*) override @@ -116,21 +116,21 @@ class RemoveSystemLock : public UndoableCommand // EditSystemLocks //--------------------------------------------------------- -void EditSystemLocks::undoAddSystemLock(Score* score, const SystemLock* lock) +void EditSystemLocks::undoAddSystemLock(Score* score, const RangeLock* lock) { removeLayoutBreaksOnAddSystemLock(score, lock); score->undo(new AddSystemLock(lock)); } -void EditSystemLocks::undoRemoveSystemLock(Score* score, const SystemLock* lock) +void EditSystemLocks::undoRemoveSystemLock(Score* score, const RangeLock* lock) { score->undo(new RemoveSystemLock(lock)); } void EditSystemLocks::undoRemoveAllLocks(Score* score) { - std::vector allLocks = score->systemLocks()->allLocks(); // copy - for (const SystemLock* lock : allLocks) { + std::vector allLocks = score->systemLocks()->allLocks(); // copy + for (const RangeLock* lock : allLocks) { undoRemoveSystemLock(score, lock); } } @@ -147,12 +147,12 @@ void EditSystemLocks::toggleSystemLock(Score* score, const std::vector& for (System* system : systems) { MeasureBase* startMeas = system->first(); - const SystemLock* currentLock = score->systemLocks()->lockStartingAt(startMeas); + const RangeLock* currentLock = score->systemLocks()->lockStartingAt(startMeas); if (currentLock && unlockAll) { undoRemoveSystemLock(score, currentLock); continue; } else if (!currentLock && !unlockAll) { - SystemLock* newSystemLock = new SystemLock(startMeas, system->last()); + RangeLock* newSystemLock = new RangeLock(startMeas, system->last()); undoAddSystemLock(score, newSystemLock); } } @@ -177,12 +177,12 @@ void EditSystemLocks::toggleScoreLock(Score* score) if (!(startMeas->isMeasure() || startMeas->isHBox())) { continue; } - const SystemLock* currentLock = score->systemLocks()->lockStartingAt(startMeas); + const RangeLock* currentLock = score->systemLocks()->lockStartingAt(startMeas); if (currentLock && unlockAll) { undoRemoveSystemLock(score, currentLock); continue; } else if (!currentLock && !unlockAll) { - SystemLock* newSystemLock = new SystemLock(startMeas, system->last()); + RangeLock* newSystemLock = new RangeLock(startMeas, system->last()); undoAddSystemLock(score, newSystemLock); } } @@ -211,14 +211,14 @@ void EditSystemLocks::addRemoveSystemLocks(Score* score, int interval, bool lock break; } if (!system->isLocked()) { - undoAddSystemLock(score, new SystemLock(system->first(), system->last())); + undoAddSystemLock(score, new RangeLock(system->first(), system->last())); } } return; } - std::vector currentLocks = score->systemLocks()->locksContainedInRange(startMeasure, endMeasure); - for (const SystemLock* l : currentLocks) { + std::vector currentLocks = score->systemLocks()->locksContainedInRange(startMeasure, endMeasure); + for (const RangeLock* l : currentLocks) { undoRemoveSystemLock(score, l); } @@ -234,7 +234,7 @@ void EditSystemLocks::addRemoveSystemLocks(Score* score, int interval, bool lock } count++; if (count == interval || mb == endMeasure) { - undoAddSystemLock(score, new SystemLock(lockStart, mb)); + undoAddSystemLock(score, new RangeLock(lockStart, mb)); lockStart = nullptr; count = 0; } @@ -248,14 +248,14 @@ void EditSystemLocks::makeIntoSystem(Score* score, MeasureBase* first, MeasureBa { bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); - const SystemLock* lockContainingfirst = score->systemLocks()->lockContaining(first); - const SystemLock* lockContaininglast = score->systemLocks()->lockContaining(last); + const RangeLock* lockContainingfirst = score->systemLocks()->lockContaining(first); + const RangeLock* lockContaininglast = score->systemLocks()->lockContaining(last); if (lockContainingfirst) { undoRemoveSystemLock(score, lockContainingfirst); if (lockContainingfirst->startMB()->isBefore(first)) { MeasureBase* oneBeforeFirst = mmrests ? first->prevMM() : first->prev(); - SystemLock* newLockBefore = new SystemLock(lockContainingfirst->startMB(), oneBeforeFirst); + RangeLock* newLockBefore = new RangeLock(lockContainingfirst->startMB(), oneBeforeFirst); undoAddSystemLock(score, newLockBefore); } } @@ -266,19 +266,19 @@ void EditSystemLocks::makeIntoSystem(Score* score, MeasureBase* first, MeasureBa } if (last->isBefore(lockContaininglast->endMB())) { MeasureBase* oneAfterLast = mmrests ? last->nextMM() : last->next(); - SystemLock* newLockAfter = new SystemLock(oneAfterLast, lockContaininglast->endMB()); + RangeLock* newLockAfter = new RangeLock(oneAfterLast, lockContaininglast->endMB()); undoAddSystemLock(score, newLockAfter); } } - std::vector locksContainedInRange = score->systemLocks()->locksContainedInRange(first, last); - for (const SystemLock* lock : locksContainedInRange) { + std::vector locksContainedInRange = score->systemLocks()->locksContainedInRange(first, last); + for (const RangeLock* lock : locksContainedInRange) { if (lock != lockContainingfirst && lock != lockContaininglast) { undoRemoveSystemLock(score, lock); } } - SystemLock* newLock = new SystemLock(first, last); + RangeLock* newLock = new RangeLock(first, last); undoAddSystemLock(score, newLock); } @@ -291,24 +291,24 @@ void EditSystemLocks::moveMeasureToPrevSystem(Score* score, MeasureBase* m) MeasureBase* prevSystemFirstMeas = prevSystem->first(); - const SystemLock* prevSystemLock = score->systemLocks()->lockStartingAt(prevSystemFirstMeas); + const RangeLock* prevSystemLock = score->systemLocks()->lockStartingAt(prevSystemFirstMeas); if (prevSystemLock) { undoRemoveSystemLock(score, prevSystemLock); } const System* curSystem = m->system(); - const SystemLock* curSystemLock = score->systemLocks()->lockStartingAt(curSystem->first()); + const RangeLock* curSystemLock = score->systemLocks()->lockStartingAt(curSystem->first()); if (curSystemLock) { undoRemoveSystemLock(score, curSystemLock); if (curSystemLock->endMB() != m) { const bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); MeasureBase* nextMB = mmrests ? m->nextMM() : m->next(); - SystemLock* newLockOnCurSystem = new SystemLock(nextMB, curSystemLock->endMB()); + RangeLock* newLockOnCurSystem = new RangeLock(nextMB, curSystemLock->endMB()); undoAddSystemLock(score, newLockOnCurSystem); } } - SystemLock* sysLock = new SystemLock(prevSystemFirstMeas, m); + RangeLock* sysLock = new RangeLock(prevSystemFirstMeas, m); undoAddSystemLock(score, sysLock); } @@ -318,7 +318,7 @@ void EditSystemLocks::moveMeasureToNextSystem(Score* score, MeasureBase* m) MeasureBase* startMeas = curSystem->first(); bool refMeasureIsStartOfSystem = m == startMeas; - const SystemLock* curLock = score->systemLocks()->lockStartingAt(startMeas); + const RangeLock* curLock = score->systemLocks()->lockStartingAt(startMeas); if (curLock) { undoRemoveSystemLock(score, curLock); } @@ -326,7 +326,7 @@ void EditSystemLocks::moveMeasureToNextSystem(Score* score, MeasureBase* m) if (!refMeasureIsStartOfSystem) { bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); MeasureBase* prevMeas = mmrests ? m->prevMM() : m->prev(); - SystemLock* sysLock = new SystemLock(startMeas, prevMeas); + RangeLock* sysLock = new RangeLock(startMeas, prevMeas); undoAddSystemLock(score, sysLock); } @@ -335,13 +335,13 @@ void EditSystemLocks::moveMeasureToNextSystem(Score* score, MeasureBase* m) return; } - const SystemLock* nextSysLock = score->systemLocks()->lockStartingAt(nextSystem->first()); + const RangeLock* nextSysLock = score->systemLocks()->lockStartingAt(nextSystem->first()); if (nextSysLock) { undoRemoveSystemLock(score, nextSysLock); } if (nextSysLock || refMeasureIsStartOfSystem) { - SystemLock* newNextSysLock = new SystemLock(m, nextSystem->last()); + RangeLock* newNextSysLock = new RangeLock(m, nextSystem->last()); undoAddSystemLock(score, newNextSysLock); } } @@ -357,7 +357,7 @@ void EditSystemLocks::applyLockToSelection(Score* score) } else { for (EngravingItem* el : score->selection().elements()) { if (el->isSystemLockIndicator()) { - const SystemLock* lock = toSystemLockIndicator(el)->systemLock(); + const RangeLock* lock = toSystemLockIndicator(el)->systemLock(); first = lock->startMB(); last = lock->endMB(); break; @@ -379,7 +379,7 @@ void EditSystemLocks::applyLockToSelection(Score* score) return; } - const SystemLock* lockOnLast = score->systemLocks()->lockContaining(last); + const RangeLock* lockOnLast = score->systemLocks()->lockContaining(last); if (lockOnLast && lockOnLast->endMB() == last) { undoRemoveSystemLock(score, lockOnLast); } else if (first != last) { @@ -395,13 +395,13 @@ void EditSystemLocks::removeSystemLocksOnAddLayoutBreak(Score* score, LayoutBrea return; // NOBREAK not allowed on locked measures } - const SystemLock* lock = score->systemLocks()->lockContaining(measure); + const RangeLock* lock = score->systemLocks()->lockContaining(measure); if (lock && (breakType == LayoutBreakType::LINE || measure != lock->endMB())) { undoRemoveSystemLock(score, lock); } } -void EditSystemLocks::removeLayoutBreaksOnAddSystemLock(Score* score, const SystemLock* lock) +void EditSystemLocks::removeLayoutBreaksOnAddSystemLock(Score* score, const RangeLock* lock) { bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mmrests ? mb->nextMM() : mb->next()) { @@ -416,8 +416,8 @@ void EditSystemLocks::removeLayoutBreaksOnAddSystemLock(Score* score, const Syst void EditSystemLocks::removeSystemLocksOnRemoveMeasures(Score* score, const MeasureBase* m1, const MeasureBase* m2) { - std::vector allSysLocks = score->systemLocks()->allLocks(); - for (const SystemLock* lock : allSysLocks) { + std::vector allSysLocks = score->systemLocks()->allLocks(); + for (const RangeLock* lock : allSysLocks) { MeasureBase* lockStart = lock->startMB(); MeasureBase* lockEnd = lock->endMB(); bool lockStartIsInRange = lockStart->isAfterOrEqual(m1) && lockStart->isBeforeOrEqual(m2); @@ -428,12 +428,12 @@ void EditSystemLocks::removeSystemLocksOnRemoveMeasures(Score* score, const Meas if (lockStartIsInRange && !lockEndIsInRange) { MeasureBase* newLockStart = m2->nextMeasure(); if (newLockStart) { - undoAddSystemLock(score, new SystemLock(newLockStart, lockEnd)); + undoAddSystemLock(score, new RangeLock(newLockStart, lockEnd)); } } else if (!lockStartIsInRange && lockEndIsInRange) { MeasureBase* newLockEnd = m1->prevMeasure(); if (newLockEnd) { - undoAddSystemLock(score, new SystemLock(lockStart, newLockEnd)); + undoAddSystemLock(score, new RangeLock(lockStart, newLockEnd)); } } } @@ -441,8 +441,8 @@ void EditSystemLocks::removeSystemLocksOnRemoveMeasures(Score* score, const Meas void EditSystemLocks::removeSystemLocksContainingMMRests(Score* score) { - std::vector allLocks = score->systemLocks()->allLocks(); // copy - for (const SystemLock* lock : allLocks) { + std::vector allLocks = score->systemLocks()->allLocks(); // copy + for (const RangeLock* lock : allLocks) { for (MeasureBase* mb = lock->startMB(); mb; mb = mb->next()) { if (mb->isMeasure() && toMeasure(mb)->mmRest()) { undoRemoveSystemLock(score, lock); @@ -459,13 +459,13 @@ void EditSystemLocks::updateSystemLocksOnCreateMMRests(Score* score, Measure* fi { // NOTE: this must be done during layout as the mmRests get created. - for (const SystemLock* lock : score->systemLocks()->locksContainedInRange(first, last)) { + for (const RangeLock* lock : score->systemLocks()->locksContainedInRange(first, last)) { // These locks are inside the range of the mmRest so remove them undoRemoveSystemLock(score, lock); } - const SystemLock* lockOnFirst = score->systemLocks()->lockContaining(first); - const SystemLock* lockOnLast = score->systemLocks()->lockContaining(last); + const RangeLock* lockOnFirst = score->systemLocks()->lockContaining(first); + const RangeLock* lockOnLast = score->systemLocks()->lockContaining(last); if (lockOnFirst) { MeasureBase* startMB = lockOnFirst->startMB(); @@ -483,7 +483,7 @@ void EditSystemLocks::updateSystemLocksOnCreateMMRests(Score* score, Measure* fi if (startMB != lockOnFirst->startMB() || endMB != lockOnFirst->endMB()) { undoRemoveSystemLock(score, lockOnFirst); - undoAddSystemLock(score, new SystemLock(startMB, endMB)); + undoAddSystemLock(score, new RangeLock(startMB, endMB)); } } @@ -497,5 +497,5 @@ void EditSystemLocks::updateSystemLocksOnCreateMMRests(Score* score, Measure* fi undoRemoveSystemLock(score, lockOnLast); startMB = last->nextMM(); - undoAddSystemLock(score, new SystemLock(startMB, endMB)); + undoAddSystemLock(score, new RangeLock(startMB, endMB)); } diff --git a/src/engraving/editing/editsystemlocks.h b/src/engraving/editing/editsystemlocks.h index dbe5f053ba740..71041324f4c76 100644 --- a/src/engraving/editing/editsystemlocks.h +++ b/src/engraving/editing/editsystemlocks.h @@ -29,15 +29,15 @@ class Measure; class MeasureBase; class Score; class System; -class SystemLock; +class RangeLock; enum class LayoutBreakType : unsigned char; class EditSystemLocks { public: - static void undoAddSystemLock(Score* score, const SystemLock* lock); - static void undoRemoveSystemLock(Score* score, const SystemLock* lock); + static void undoAddSystemLock(Score* score, const RangeLock* lock); + static void undoRemoveSystemLock(Score* score, const RangeLock* lock); static void undoRemoveAllLocks(Score* score); static void toggleSystemLock(Score* score, const std::vector& systems); @@ -52,7 +52,7 @@ class EditSystemLocks static void applyLockToSelection(Score* score); static void removeSystemLocksOnAddLayoutBreak(Score* score, LayoutBreakType breakType, const MeasureBase* measure); - static void removeLayoutBreaksOnAddSystemLock(Score* score, const SystemLock* lock); + static void removeLayoutBreaksOnAddSystemLock(Score* score, const RangeLock* lock); static void removeSystemLocksOnRemoveMeasures(Score* score, const MeasureBase* m1, const MeasureBase* m2); static void removeSystemLocksContainingMMRests(Score* score); static void updateSystemLocksOnCreateMMRests(Score* score, Measure* first, Measure* last); diff --git a/src/engraving/rendering/score/layoutcontext.cpp b/src/engraving/rendering/score/layoutcontext.cpp index 25c2239e29f1f..a086a1456511f 100644 --- a/src/engraving/rendering/score/layoutcontext.cpp +++ b/src/engraving/rendering/score/layoutcontext.cpp @@ -375,7 +375,7 @@ const ChordRest* DomAccessor::findCR(Fraction tick, track_idx_t track) const return score()->findCR(tick, track); } -const SystemLocks* DomAccessor::systemLocks() const +const RangeLocks* DomAccessor::systemLocks() const { IF_ASSERT_FAILED(score()) { return nullptr; diff --git a/src/engraving/rendering/score/layoutcontext.h b/src/engraving/rendering/score/layoutcontext.h index 8fbeaa96a7eb3..471b849f43c7b 100644 --- a/src/engraving/rendering/score/layoutcontext.h +++ b/src/engraving/rendering/score/layoutcontext.h @@ -63,7 +63,7 @@ class Score; class Spanner; class SpannerMap; class System; -class SystemLocks; +class RangeLocks; class Staff; class Measure; class ChordRest; @@ -178,7 +178,7 @@ class DomAccessor const ChordRest* findCR(Fraction tick, track_idx_t track) const; - const SystemLocks* systemLocks() const; + const RangeLocks* systemLocks() const; const PaddingTable& paddingTable() const; diff --git a/src/engraving/rendering/score/scorehorizontalviewlayout.cpp b/src/engraving/rendering/score/scorehorizontalviewlayout.cpp index a3ab4ff096097..6190c542404da 100644 --- a/src/engraving/rendering/score/scorehorizontalviewlayout.cpp +++ b/src/engraving/rendering/score/scorehorizontalviewlayout.cpp @@ -259,8 +259,8 @@ void ScoreHorizontalViewLayout::layoutSystemLockIndicators(System* system) system->deleteLockIndicators(); - std::vector systemLocks = system->score()->systemLocks()->allLocks(); - for (const SystemLock* lock : systemLocks) { + std::vector systemLocks = system->score()->systemLocks()->allLocks(); + for (const RangeLock* lock : systemLocks) { SystemLockIndicator* lockIndicator = Factory::createSystemLockIndicator(system, lock); lockIndicator->setParent(system); system->addLockIndicator(lockIndicator); diff --git a/src/engraving/rendering/score/systemlayout.cpp b/src/engraving/rendering/score/systemlayout.cpp index bd53bdee1cbe8..86e7d7c397675 100644 --- a/src/engraving/rendering/score/systemlayout.cpp +++ b/src/engraving/rendering/score/systemlayout.cpp @@ -143,8 +143,8 @@ System* SystemLayout::collectSystem(LayoutContext& ctx) prevMeasureState.curHeader = ctx.state().curMeasure()->header(); prevMeasureState.curTrailer = ctx.state().curMeasure()->trailer(); - const SystemLock* systemLock = ctx.conf().viewMode() == LayoutMode::PAGE || ctx.conf().viewMode() == LayoutMode::SYSTEM - ? ctx.dom().systemLocks()->lockStartingAt(ctx.state().curMeasure()) : nullptr; + const RangeLock* systemLock = ctx.conf().viewMode() == LayoutMode::PAGE || ctx.conf().viewMode() == LayoutMode::SYSTEM + ? ctx.dom().systemLocks()->lockStartingAt(ctx.state().curMeasure()) : nullptr; if (systemLock) { StaveSharingLayout::updateStaveSharingForFullSystem(systemLock->startMB(), systemLock->endMB(), ctx); @@ -505,7 +505,7 @@ void SystemLayout::layoutSystemLockIndicators(System* system, LayoutContext& ctx assert(lockIndicators.size() <= 1); system->deleteLockIndicators(); - const SystemLock* lock = system->systemLock(); + const RangeLock* lock = system->systemLock(); if (!lock) { return; } diff --git a/src/engraving/rendering/score/tdraw.cpp b/src/engraving/rendering/score/tdraw.cpp index 63db73a5cc7fc..6272dc90d9f4f 100644 --- a/src/engraving/rendering/score/tdraw.cpp +++ b/src/engraving/rendering/score/tdraw.cpp @@ -132,7 +132,7 @@ #include "dom/symbol.h" #include "dom/systemdivider.h" #include "dom/systemtext.h" -#include "dom/systemlock.h" +#include "dom/rangelock.h" #include "dom/soundflag.h" #include "dom/tapping.h" diff --git a/src/engraving/rendering/score/tlayout.cpp b/src/engraving/rendering/score/tlayout.cpp index 2c2b40f34f951..ae2a7e0d3dde0 100644 --- a/src/engraving/rendering/score/tlayout.cpp +++ b/src/engraving/rendering/score/tlayout.cpp @@ -3711,7 +3711,7 @@ void TLayout::layoutIndicatorIcon(const IndicatorIcon* item, IndicatorIcon::Layo endMB = sli->systemLock()->endMB(); if (item->selected()) { // Draw the range rect... - const SystemLock* lock = sli->systemLock(); + const RangeLock* lock = sli->systemLock(); double xStart = lock->startMB()->x(); double xEnd = lock->endMB()->x() + lock->endMB()->width(); diff --git a/src/engraving/rw/read410/tread.cpp b/src/engraving/rw/read410/tread.cpp index a7d4362765eae..7a9e220b7ce3a 100644 --- a/src/engraving/rw/read410/tread.cpp +++ b/src/engraving/rw/read410/tread.cpp @@ -4545,5 +4545,5 @@ void TRead::readSystemLock(Score* score, XmlReader& e) return; } - score->addSystemLock(new SystemLock(startMeas, endMeas)); + score->addSystemLock(new RangeLock(startMeas, endMeas)); } diff --git a/src/engraving/rw/read460/tread.cpp b/src/engraving/rw/read460/tread.cpp index 9911446adbfc0..3be39a6767afb 100644 --- a/src/engraving/rw/read460/tread.cpp +++ b/src/engraving/rw/read460/tread.cpp @@ -4658,7 +4658,7 @@ void TRead::readSystemLock(Score* score, XmlReader& e) return; } - score->addSystemLock(new SystemLock(startMeas, endMeas)); + score->addSystemLock(new RangeLock(startMeas, endMeas)); } void TRead::readSystemDividers(Score* score, XmlReader& e, ReadContext& ctx) diff --git a/src/engraving/rw/read500/tread.cpp b/src/engraving/rw/read500/tread.cpp index 303bf72ce7753..d27e4be99d473 100644 --- a/src/engraving/rw/read500/tread.cpp +++ b/src/engraving/rw/read500/tread.cpp @@ -4795,7 +4795,7 @@ void TRead::readSystemLock(Score* score, XmlReader& e) return; } - score->addSystemLock(new SystemLock(startMeas, endMeas)); + score->addSystemLock(new RangeLock(startMeas, endMeas)); } void TRead::readSystemDividers(Score* score, XmlReader& e, ReadContext& ctx) diff --git a/src/engraving/rw/write/twrite.cpp b/src/engraving/rw/write/twrite.cpp index b697b4101f493..b61cd5a4db3ae 100644 --- a/src/engraving/rw/write/twrite.cpp +++ b/src/engraving/rw/write/twrite.cpp @@ -450,13 +450,13 @@ void TWrite::writeProperty(const EngravingItem* item, XmlWriter& xml, Pid pid, b void TWrite::writeSystemLocks(const Score* score, XmlWriter& xml) { - std::vector locks = score->systemLocks()->allLocks(); + std::vector locks = score->systemLocks()->allLocks(); if (locks.empty()) { return; } xml.startElement("SystemLocks"); - for (const SystemLock* sl : locks) { + for (const RangeLock* sl : locks) { writeSystemLock(sl, xml); } xml.endElement(); @@ -525,7 +525,7 @@ void TWrite::writeItemLink(const EngravingObject* item, XmlWriter& xml, WriteCon } } -void TWrite::writeSystemLock(const SystemLock* systemLock, XmlWriter& xml) +void TWrite::writeSystemLock(const RangeLock* systemLock, XmlWriter& xml) { xml.startElement("systemLock"); diff --git a/src/engraving/rw/write/twrite.h b/src/engraving/rw/write/twrite.h index 2693d014d638d..61baf162d4747 100644 --- a/src/engraving/rw/write/twrite.h +++ b/src/engraving/rw/write/twrite.h @@ -366,7 +366,7 @@ class TWrite static void writeTupletStart(DurationElement* item, XmlWriter& xml, WriteContext& ctx); static void writeTupletEnd(DurationElement* item, XmlWriter& xml, WriteContext& ctx); - static void writeSystemLock(const SystemLock* systemLock, XmlWriter& xml); + static void writeSystemLock(const RangeLock* systemLock, XmlWriter& xml); static muse::String lineBreakToTag(const String& str); static void writeProperties(const StaffLabel& item, XmlWriter& xml); diff --git a/src/engraving/tests/system_locks_tests.cpp b/src/engraving/tests/system_locks_tests.cpp index a3a1ad39e88a5..28b50a73efd8e 100644 --- a/src/engraving/tests/system_locks_tests.cpp +++ b/src/engraving/tests/system_locks_tests.cpp @@ -42,7 +42,7 @@ TEST_F(Engraving_SystemLocksTests, readLocksFromFile) MasterScore* score = ScoreRW::readScore(SYSTEM_LOCKS_DATA_DIR + u"system_locks-1.mscx"); EXPECT_TRUE(score); - std::vector locks = score->systemLocks()->allLocks(); + std::vector locks = score->systemLocks()->allLocks(); EXPECT_FALSE(locks.empty()); for (MeasureBase* mb = score->first(); mb; mb = mb->next()) { @@ -53,7 +53,7 @@ TEST_F(Engraving_SystemLocksTests, readLocksFromFile) EXPECT_TRUE(sys->isLocked()); } - for (const SystemLock* lock : locks) { + for (const RangeLock* lock : locks) { int measureCount = 0; for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mb->next()) { ++measureCount; @@ -69,8 +69,8 @@ TEST_F(Engraving_SystemLocksTests, lockMeasuresPerSystem) MasterScore* score = ScoreRW::readScore(SYSTEM_LOCKS_DATA_DIR + u"system_locks-1.mscx"); EXPECT_TRUE(score); - const SystemLocks* systemLocks = score->systemLocks(); - std::vector allLocks = systemLocks->allLocks(); + const RangeLocks* systemLocks = score->systemLocks(); + std::vector allLocks = systemLocks->allLocks(); EXPECT_FALSE(allLocks.empty()); score->startCmd(TranslatableString::untranslatable("Engraving system locks tests")); @@ -107,7 +107,7 @@ TEST_F(Engraving_SystemLocksTests, lockMeasuresPerSystem) score->endCmd(); allLocks = systemLocks->allLocks(); - for (const SystemLock* lock : allLocks) { + for (const RangeLock* lock : allLocks) { int measureCount = 0; for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mb->next()) { ++measureCount; From c92e225ccd391e209453889de2fe3ae316a7167f Mon Sep 17 00:00:00 2001 From: James Mizen Date: Fri, 1 May 2026 10:06:34 +0100 Subject: [PATCH 02/10] Implement page locks --- src/engraving/CMakeLists.txt | 2 + src/engraving/dom/measurebase.cpp | 87 +++ src/engraving/dom/measurebase.h | 8 + src/engraving/dom/page.cpp | 30 ++ src/engraving/dom/page.h | 7 + src/engraving/dom/rangelock.cpp | 12 +- src/engraving/dom/rangelock.h | 4 +- src/engraving/dom/score.cpp | 19 + src/engraving/dom/score.h | 7 + src/engraving/dom/select.cpp | 34 +- src/engraving/dom/select.h | 1 + src/engraving/dom/utils.cpp | 15 +- src/engraving/editing/cmd.cpp | 4 + src/engraving/editing/edit.cpp | 2 + src/engraving/editing/editpagelocks.cpp | 494 ++++++++++++++++++ src/engraving/editing/editpagelocks.h | 61 +++ src/engraving/editing/editstyle.cpp | 2 + .../rendering/score/layoutcontext.cpp | 12 +- src/engraving/rendering/score/layoutcontext.h | 3 +- .../rendering/score/measurelayout.cpp | 2 +- src/engraving/rendering/score/pagelayout.cpp | 52 +- src/engraving/rendering/score/pagelayout.h | 4 +- .../rendering/score/systemlayout.cpp | 4 +- src/engraving/rw/read500/read500.cpp | 2 + src/engraving/rw/read500/tread.cpp | 37 ++ src/engraving/rw/read500/tread.h | 2 + src/engraving/rw/write/twrite.cpp | 24 + src/engraving/rw/write/twrite.h | 2 + src/engraving/rw/write/writer.cpp | 1 + src/engraving/tests/CMakeLists.txt | 1 + .../tests/page_locks_data/page_locks-1.mscx | 453 ++++++++++++++++ .../tests/page_locks_data/page_locks-1.mscz | Bin 0 -> 25836 bytes src/engraving/tests/page_locks_tests.cpp | 214 ++++++++ src/notation/inotationinteraction.h | 5 + src/notation/inotationselection.h | 1 + src/notation/internal/notationinteraction.cpp | 50 ++ src/notation/internal/notationinteraction.h | 5 + src/notation/internal/notationparts.cpp | 4 + src/notation/internal/notationselection.cpp | 5 + src/notation/internal/notationselection.h | 2 + .../tests/mocks/notationinteractionmock.h | 5 + .../tests/mocks/notationselectionmock.h | 1 + .../internal/notationactioncontroller.cpp | 5 + .../internal/notationuiactions.cpp | 26 + .../measures/MeasuresSection.qml | 126 ++++- .../measures/measuressettingsmodel.cpp | 128 ++++- .../measures/measuressettingsmodel.h | 39 +- 47 files changed, 1942 insertions(+), 62 deletions(-) create mode 100644 src/engraving/editing/editpagelocks.cpp create mode 100644 src/engraving/editing/editpagelocks.h create mode 100644 src/engraving/tests/page_locks_data/page_locks-1.mscx create mode 100644 src/engraving/tests/page_locks_data/page_locks-1.mscz create mode 100644 src/engraving/tests/page_locks_tests.cpp diff --git a/src/engraving/CMakeLists.txt b/src/engraving/CMakeLists.txt index eebca473eaa56..b0d135de558c7 100644 --- a/src/engraving/CMakeLists.txt +++ b/src/engraving/CMakeLists.txt @@ -132,6 +132,8 @@ target_sources(engraving PRIVATE editing/editmeasures.h editing/editnote.cpp editing/editnote.h + editing/editpagelocks.cpp + editing/editpagelocks.h editing/editpart.cpp editing/editpart.h editing/editproperty.cpp diff --git a/src/engraving/dom/measurebase.cpp b/src/engraving/dom/measurebase.cpp index 2271e6fa22e98..90efb237f9efe 100644 --- a/src/engraving/dom/measurebase.cpp +++ b/src/engraving/dom/measurebase.cpp @@ -138,6 +138,43 @@ System* MeasureBase::nextNonVBoxSystem() const return nextSystem != curSystem ? nextSystem : nullptr; } +Page* MeasureBase::page() const +{ + return system() ? system()->page() : nullptr; +} + +Page* MeasureBase::prevPage() const +{ + bool mmRests = score()->style().styleB(Sid::createMultiMeasureRests); + Page* curPage = system() ? system()->page() : nullptr; + IF_ASSERT_FAILED(curPage) { + return nullptr; + } + + Page* prevPage = curPage; + for (const MeasureBase* mb = this; mb && prevPage == curPage; mb = mmRests ? mb->prevMM() : mb->prev()) { + prevPage = mb->system()->page(); + } + + return prevPage != curPage ? prevPage : nullptr; +} + +Page* MeasureBase::nextPage() const +{ + bool mmRests = score()->style().styleB(Sid::createMultiMeasureRests); + Page* curPage = system() ? system()->page() : nullptr; + IF_ASSERT_FAILED(curPage) { + return nullptr; + } + + Page* nextPage = curPage; + for (const MeasureBase* mb = this; mb && nextPage == curPage; mb = mmRests ? mb->nextMM() : mb->next()) { + nextPage = mb->system()->page(); + } + + return nextPage != curPage ? nextPage : nullptr; +} + //--------------------------------------------------------- // add /// Add new EngravingItem \a el to MeasureBase @@ -655,6 +692,23 @@ bool MeasureBase::isEndOfSystemLock() const return lock && lock->endMB() == this; } +const RangeLock* MeasureBase::pageLock() const +{ + return score()->pageLocks()->lockContaining(this); +} + +bool MeasureBase::isStartOfPageLock() const +{ + const RangeLock* lock = score()->pageLocks()->lockStartingAt(this); + return lock != nullptr; +} + +bool MeasureBase::isEndOfPageLock() const +{ + const RangeLock* lock = pageLock(); + return lock && lock->endMB() == this; +} + //--------------------------------------------------------- // sectionBreakElement //--------------------------------------------------------- @@ -902,6 +956,39 @@ Measure* MeasureBaseList::measureByTick(int tick) const return nullptr; } +MeasureBase* MeasureBaseList::measureBaseByTick(int tick) const +{ + if (empty() || tick > m_last->endTick().ticks()) { + return nullptr; + } + + auto it = m_tickIndex.upper_bound(tick); + + if (it == m_tickIndex.begin()) { + MeasureBase* mb = it->second; + + return mb; + } + + --it; + for (;; --it) { + if (it == m_tickIndex.begin()) { + MeasureBase* mb = it->second; + + return mb; + } + + MeasureBase* mb = it->second; + if (!mb) { + break; + } + + return mb; + } + + return nullptr; +} + std::vector MeasureBaseList::measureBasesAtTick(int tick) const { std::vector result; diff --git a/src/engraving/dom/measurebase.h b/src/engraving/dom/measurebase.h index b67f15b1d6c39..e52057c318cd1 100644 --- a/src/engraving/dom/measurebase.h +++ b/src/engraving/dom/measurebase.h @@ -82,6 +82,9 @@ class MeasureBase : public EngravingItem System* system() const { return toSystem(explicitParent()); } System* prevNonVBoxSystem() const; System* nextNonVBoxSystem() const; + Page* page() const; + Page* prevPage() const; + Page* nextPage() const; void setParent(System* s) { EngravingItem::setParent((EngravingObject*)(s)); } virtual void scanElements(std::function func) override; @@ -164,6 +167,10 @@ class MeasureBase : public EngravingItem bool isStartOfSystemLock() const; bool isEndOfSystemLock() const; + const RangeLock* pageLock() const; + bool isStartOfPageLock() const; + bool isEndOfPageLock() const; + protected: MeasureBase(const ElementType& type, System* system = 0); MeasureBase(const MeasureBase&); @@ -203,6 +210,7 @@ class MeasureBaseList void updateTickIndex(); Measure* measureByTick(int tick) const; + MeasureBase* measureBaseByTick(int tick) const; std::vector measureBasesAtTick(int tick) const; private: diff --git a/src/engraving/dom/page.cpp b/src/engraving/dom/page.cpp index 2bd2085452770..f070a7bcbf8a3 100644 --- a/src/engraving/dom/page.cpp +++ b/src/engraving/dom/page.cpp @@ -44,6 +44,24 @@ Page::Page(RootItem* parent) m_bspTreeValid = false; } +MeasureBase* Page::firstMeasureBase() const +{ + if (m_systems.empty()) { + return nullptr; + } + System* firstSys = m_systems.front(); + return firstSys ? firstSys->first() : nullptr; +} + +MeasureBase* Page::lastMeasureBase() const +{ + if (m_systems.empty()) { + return nullptr; + } + System* lastSys = m_systems.back(); + return lastSys ? lastSys->last() : nullptr; +} + //--------------------------------------------------------- // items //--------------------------------------------------------- @@ -274,3 +292,15 @@ Measure* Page::firstMeasure() const return nullptr; } + +bool Page::isLocked() const +{ + MeasureBase* firstMeasure = firstMeasureBase(); + return firstMeasure ? firstMeasure->isStartOfPageLock() : false; +} + +const RangeLock* Page::pageLock() const +{ + MeasureBase* firstMeasure = firstMeasureBase(); + return firstMeasure->pageLock(); +} diff --git a/src/engraving/dom/page.h b/src/engraving/dom/page.h index 58ff6032893c5..a7b7734a06004 100644 --- a/src/engraving/dom/page.h +++ b/src/engraving/dom/page.h @@ -36,6 +36,7 @@ class Factory; class System; class Text; class Measure; +class RangeLock; class Score; class MeasureBase; @@ -58,6 +59,9 @@ class Page final : public EngravingItem System* system(size_t idx) { return m_systems[idx]; } const System* system(size_t idx) const { return m_systems.at(idx); } + MeasureBase* firstMeasureBase() const; + MeasureBase* lastMeasureBase() const; + void appendSystem(System* s); page_idx_t pageNumber() const { return m_pageNumber; } @@ -85,6 +89,9 @@ class Page final : public EngravingItem void setHeaderText(int index, Text* t) { m_headerTexts.at(index) = t; } void setFooterText(int index, Text* t) { m_footerTexts.at(index) = t; } + bool isLocked() const; + const RangeLock* pageLock() const; + #ifndef ENGRAVING_NO_ACCESSIBILITY AccessibleItemPtr createAccessible() override; #endif diff --git a/src/engraving/dom/rangelock.cpp b/src/engraving/dom/rangelock.cpp index 978c135ab32d5..99d2b5f496ee8 100644 --- a/src/engraving/dom/rangelock.cpp +++ b/src/engraving/dom/rangelock.cpp @@ -116,20 +116,20 @@ void RangeLocks::sanityCheck() } const MeasureBase* curMB = curIter->first; - const RangeLock* curSysLock = curIter->second; + const RangeLock* curLock = curIter->second; - DO_ASSERT(curSysLock->startMB() == curMB); + DO_ASSERT(curLock->startMB() == curMB); const MeasureBase* nextMB = nextIter->first; - const RangeLock* nextSysLock = nextIter->second; + const RangeLock* nextLock = nextIter->second; - DO_ASSERT(nextSysLock->startMB() == nextMB); + DO_ASSERT(nextLock->startMB() == nextMB); DO_ASSERT(curMB->isBefore(nextMB)); - DO_ASSERT(curSysLock->endMB()->isBefore(nextSysLock->startMB())); + DO_ASSERT(curLock->endMB()->isBefore(nextLock->startMB())); } } -void RangeLocks::dump() +void RangeLocks::dump() const { for (auto& pair : m_rangeLocks) { const RangeLock* sl = pair.second; diff --git a/src/engraving/dom/rangelock.h b/src/engraving/dom/rangelock.h index 1aed7c8e9a35a..a16deb074806d 100644 --- a/src/engraving/dom/rangelock.h +++ b/src/engraving/dom/rangelock.h @@ -34,8 +34,6 @@ class RangeLock RangeLock(MeasureBase* start, MeasureBase* end) : m_startMB(start), m_endMB(end) { - assert(m_startMB->isMeasure() || m_startMB->isHBox()); - assert(m_endMB->isMeasure() || m_endMB->isHBox()); assert(m_startMB->isBefore(m_endMB) || m_startMB == m_endMB); } @@ -66,7 +64,7 @@ class RangeLocks private: #ifndef NDEBUG void sanityCheck(); - void dump(); + void dump() const; #endif struct Ordering diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index 59c793af5a89e..c298f295a439d 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -268,6 +268,9 @@ Score::~Score() muse::DeleteAll(m_systemLocks.allLocks()); m_systemLocks.clear(); + muse::DeleteAll(m_pageLocks.allLocks()); + m_pageLocks.clear(); + muse::DeleteAll(m_parts); m_parts.clear(); @@ -6048,6 +6051,22 @@ void Score::removeSystemLock(const RangeLock* lock) lock->endMB()->triggerLayout(); } +void Score::addPageLock(const RangeLock* lock) +{ + m_pageLocks.add(lock); + + lock->startMB()->triggerLayout(); + lock->endMB()->triggerLayout(); +} + +void Score::removePageLock(const RangeLock* lock) +{ + m_pageLocks.remove(lock); + + lock->startMB()->triggerLayout(); + lock->endMB()->triggerLayout(); +} + //--------------------------------------------------------- // updateSwing //--------------------------------------------------------- diff --git a/src/engraving/dom/score.h b/src/engraving/dom/score.h index 6687a3c12ec56..d28a4ebcaa800 100644 --- a/src/engraving/dom/score.h +++ b/src/engraving/dom/score.h @@ -1056,6 +1056,11 @@ class Score : public EngravingObject, public muse::Contextable void removeSystemLock(const RangeLock* lock); void clearSystemLocks() { m_systemLocks.clear(); } + const RangeLocks* pageLocks() const { return &m_pageLocks; } + void addPageLock(const RangeLock* lock); + void removePageLock(const RangeLock* lock); + void clearPageLocks() { m_pageLocks.clear(); } + void rebuildFretBox(); const std::map > systemDividers() const { return m_systemDividers; } @@ -1172,6 +1177,8 @@ class Score : public EngravingObject, public muse::Contextable std::vector m_systemObjectStaves; RangeLocks m_systemLocks; + RangeLocks m_pageLocks; + SpannerMap m_spanner; std::set m_unmanagedSpanner; diff --git a/src/engraving/dom/select.cpp b/src/engraving/dom/select.cpp index 83d4c75b7416f..76484371abfae 100644 --- a/src/engraving/dom/select.cpp +++ b/src/engraving/dom/select.cpp @@ -66,6 +66,7 @@ #include "stem.h" #include "stemslash.h" #include "sticking.h" +#include "system.h" #include "tie.h" #include "tremolosinglechord.h" #include "tremolotwochord.h" @@ -364,7 +365,7 @@ MeasureBase* Selection::startMeasureBase() const { EngravingItem* selectionElement = element(); if (selectionElement) { - if (selectionElement->isHBox()) { + if (selectionElement->isBox()) { return toMeasureBase(selectionElement); } MeasureBase* mb = selectionElement->findMeasureBase(); @@ -377,17 +378,14 @@ MeasureBase* Selection::startMeasureBase() const return nullptr; } - bool mmrests = m_score->style().styleB(Sid::createMultiMeasureRests); - Fraction refTick = tickStart(); - - return mmrests ? m_score->tick2measureMM(refTick) : m_score->tick2measure(refTick); + return m_score->tick2measureBase(tickStart()); } MeasureBase* Selection::endMeasureBase() const { EngravingItem* selectionElement = element(); if (selectionElement) { - if (selectionElement->isHBox()) { + if (selectionElement->isBox()) { return toMeasureBase(selectionElement); } MeasureBase* mb = selectionElement->findMeasureBase(); @@ -400,10 +398,7 @@ MeasureBase* Selection::endMeasureBase() const return nullptr; } - bool mmrests = m_score->style().styleB(Sid::createMultiMeasureRests); - Fraction refTick = tickEnd() - Fraction::eps(); - - return mmrests ? m_score->tick2measureMM(refTick) : m_score->tick2measure(refTick); + return m_score->tick2measureBase(tickEnd() - Fraction::eps()); } std::vector Selection::selectedSystems() const @@ -431,6 +426,25 @@ std::vector Selection::selectedSystems() const return systems; } +std::vector mu::engraving::Selection::selectedPages() const +{ + EngravingItem* el = element(); + // if (el && (el->pageLockIndicator())) { TODO + // return { const_cast(toIndicatorIcon(el)->system()) }; + // } + + std::vector systems = selectedSystems(); + std::vector pages; + for (System* system : systems) { + Page* page = system->page(); + if (page && std::find(pages.begin(), pages.end(), page) == pages.end()) { + pages.push_back(page); + } + } + + return pages; +} + void Selection::deselectAll() { if (m_state == SelState::RANGE) { diff --git a/src/engraving/dom/select.h b/src/engraving/dom/select.h index dc8aa23a11645..89d209aafe2d4 100644 --- a/src/engraving/dom/select.h +++ b/src/engraving/dom/select.h @@ -132,6 +132,7 @@ class Selection MeasureBase* startMeasureBase() const; MeasureBase* endMeasureBase() const; std::vector selectedSystems() const; + std::vector selectedPages() const; void update(); void updateState(); void dump(); diff --git a/src/engraving/dom/utils.cpp b/src/engraving/dom/utils.cpp index f6098627bad2c..db74071feb2bb 100644 --- a/src/engraving/dom/utils.cpp +++ b/src/engraving/dom/utils.cpp @@ -123,17 +123,14 @@ Measure* Score::tick2measureMM(const Fraction& t) const MeasureBase* Score::tick2measureBase(const Fraction& tick) const { - std::vector mbList = m_measures.measureBasesAtTick(tick.ticks()); - for (MeasureBase* mb : mbList) { - Fraction st = mb->tick(); - Fraction l = mb->ticks(); - if (tick >= st && tick < (st + l)) { - return mb; - } + if (tick == Fraction(-1, 1)) { // special number + return m_measures.last(); + } + if (tick <= Fraction(0, 1)) { + return m_measures.first(); } - LOGD("tick2measureBase %d not found", tick.ticks()); - return nullptr; + return m_measures.measureBaseByTick(tick.ticks()); } //--------------------------------------------------------- diff --git a/src/engraving/editing/cmd.cpp b/src/engraving/editing/cmd.cpp index e7aea8698029b..3864cc9e411aa 100644 --- a/src/engraving/editing/cmd.cpp +++ b/src/engraving/editing/cmd.cpp @@ -87,6 +87,7 @@ #include "editchord.h" #include "editnote.h" +#include "editpagelocks.h" #include "editproperty.h" #include "editspanner.h" #include "editstaff.h" @@ -4035,6 +4036,7 @@ void Score::cmdToggleLayoutBreak(LayoutBreakType type) val = !mb->lineBreak(); if (val) { EditSystemLocks::removeSystemLocksOnAddLayoutBreak(this, type, mb); + EditPageLocks::removePageLocksOnAddLayoutBreak(this, type, mb); } mb->undoSetBreak(val, type); // remove page break if appropriate @@ -4046,6 +4048,7 @@ void Score::cmdToggleLayoutBreak(LayoutBreakType type) val = !mb->pageBreak(); if (val) { EditSystemLocks::removeSystemLocksOnAddLayoutBreak(this, type, mb); + EditPageLocks::removePageLocksOnAddLayoutBreak(this, type, mb); } mb->undoSetBreak(val, type); // remove line break if appropriate @@ -4057,6 +4060,7 @@ void Score::cmdToggleLayoutBreak(LayoutBreakType type) val = !mb->sectionBreak(); if (val) { EditSystemLocks::removeSystemLocksOnAddLayoutBreak(this, type, mb); + EditPageLocks::removePageLocksOnAddLayoutBreak(this, type, mb); } mb->undoSetBreak(val, type); break; diff --git a/src/engraving/editing/edit.cpp b/src/engraving/editing/edit.cpp index da7aaed2a1892..13666b6eb2036 100644 --- a/src/engraving/editing/edit.cpp +++ b/src/engraving/editing/edit.cpp @@ -108,6 +108,7 @@ #include "editkeysig.h" #include "editmeasures.h" #include "editnote.h" +#include "editpagelocks.h" #include "editpart.h" #include "editproperty.h" #include "editscoreproperties.h" @@ -7541,6 +7542,7 @@ void Score::undoRemoveMeasures(Measure* m1, Measure* m2, bool preserveTies, bool } EditSystemLocks::removeSystemLocksOnRemoveMeasures(this, m1, m2); + EditPageLocks::removePageLocksOnRemoveMeasures(this, m1, m2); undo(new RemoveMeasures(m1, m2, moveStaffTypeChanges)); } diff --git a/src/engraving/editing/editpagelocks.cpp b/src/engraving/editing/editpagelocks.cpp new file mode 100644 index 0000000000000..19f46f303de37 --- /dev/null +++ b/src/engraving/editing/editpagelocks.cpp @@ -0,0 +1,494 @@ +/* + * 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 "editpagelocks.h" + +#include "undo.h" + +#include "../dom/measurebase.h" +#include "../dom/page.h" +#include "../dom/score.h" +#include "../dom/rangelock.h" + +using namespace mu::engraving; + +//--------------------------------------------------------- +// AddPageLock +//--------------------------------------------------------- + +class AddPageLock : public UndoCommand +{ + OBJECT_ALLOCATOR(engraving, AddPageLock) + + const RangeLock* m_pageLock; +public: + AddPageLock(const RangeLock* pageLock) + : m_pageLock(pageLock) {} + + void undo(EditData*) override + { + Score* score = m_pageLock->startMB()->score(); + score->removePageLock(m_pageLock); + } + + void redo(EditData*) override + { + Score* score = m_pageLock->startMB()->score(); + score->addPageLock(m_pageLock); + } + + void cleanup(bool undo) override + { + if (!undo) { + delete m_pageLock; + m_pageLock = nullptr; + } + } + + UNDO_NAME("AddPageLock") + + std::vector objectItems() const override + { + return { m_pageLock->startMB(), m_pageLock->endMB() }; + } +}; + +//--------------------------------------------------------- +// RemovePageLock +//--------------------------------------------------------- + +class RemovePageLock : public UndoCommand +{ + OBJECT_ALLOCATOR(engraving, RemovePageLock) + + const RangeLock* m_pageLock; +public: + RemovePageLock(const RangeLock* pageLock) + : m_pageLock(pageLock) {} + + void undo(EditData*) override + { + Score* score = m_pageLock->startMB()->score(); + score->addPageLock(m_pageLock); + } + + void redo(EditData*) override + { + Score* score = m_pageLock->startMB()->score(); + score->removePageLock(m_pageLock); + } + + void cleanup(bool undo) override + { + if (undo) { + delete m_pageLock; + m_pageLock = nullptr; + } + } + + UNDO_NAME("RemovePageLock") + std::vector objectItems() const override + { + return { m_pageLock->startMB(), m_pageLock->endMB() }; + } +}; + +//--------------------------------------------------------- +// EditPageLocks +//--------------------------------------------------------- + +void EditPageLocks::undoAddPageLock(Score* score, const RangeLock* lock) +{ + removeLayoutBreaksOnAddPageLock(score, lock); + score->undo(new AddPageLock(lock)); +} + +void EditPageLocks::undoRemovePageLock(Score* score, const RangeLock* lock) +{ + score->undo(new RemovePageLock(lock)); +} + +void EditPageLocks::undoRemoveAllLocks(Score* score) +{ + std::vector allLocks = score->pageLocks()->allLocks(); // copy + for (const RangeLock* lock : allLocks) { + undoRemovePageLock(score, lock); + } +} + +void EditPageLocks::togglePageLock(Score* score, const std::vector& pages) +{ + bool unlockAll = true; + for (const Page* page : pages) { + if (!page->isLocked()) { + unlockAll = false; + break; + } + } + + for (Page* page : pages) { + MeasureBase* startMeas = page->firstMeasureBase(); + const RangeLock* currentLock = score->pageLocks()->lockStartingAt(startMeas); + if (currentLock && unlockAll) { + undoRemovePageLock(score, currentLock); + continue; + } else if (!currentLock && !unlockAll) { + RangeLock* newPageLock = new RangeLock(startMeas, page->lastMeasureBase()); + undoAddPageLock(score, newPageLock); + } + } +} + +void EditPageLocks::toggleScoreLock(Score* score) +{ + bool unlockAll = true; + for (const Page* page : score->pages()) { + if (!page->isLocked()) { + unlockAll = false; + break; + } + } + + for (Page* page : score->pages()) { + MeasureBase* startMeas = page->firstMeasureBase(); + const RangeLock* currentLock = score->pageLocks()->lockStartingAt(startMeas); + if (currentLock && unlockAll) { + undoRemovePageLock(score, currentLock); + continue; + } else if (!currentLock && !unlockAll) { + RangeLock* newPageLock = new RangeLock(startMeas, page->lastMeasureBase()); + undoAddPageLock(score, newPageLock); + } + } +} + +void EditPageLocks::addRemovePageLocks(Score* score, int interval, bool lock) +{ + const bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); + + MeasureBase* startMeasure = score->selection().startMeasureBase(); + MeasureBase* endMeasure = score->selection().endMeasureBase(); + if (!endMeasure) { + endMeasure = mmrests ? score->lastMeasureMM() : score->lastMeasure(); + } + + if (!startMeasure || !endMeasure) { + return; + } + + if (lock) { + for (const Page* page : score->pages()) { + if (page->lastMeasureBase()->isBefore(startMeasure)) { + continue; + } + if (page->firstMeasureBase()->isAfter(endMeasure)) { + break; + } + if (!page->isLocked()) { + undoAddPageLock(score, new RangeLock(page->firstMeasureBase(), page->lastMeasureBase())); + } + } + return; + } + + std::vector currentLocks = score->pageLocks()->locksContainedInRange(startMeasure, endMeasure); + for (const RangeLock* l : currentLocks) { + undoRemovePageLock(score, l); + } + + if (interval == 0) { + return; + } + + int count = 0; + MeasureBase* lockStart = nullptr; + for (MeasureBase* mb = startMeasure; mb; mb = mmrests ? mb->nextMM() : mb->next()) { + if (count == 0) { + lockStart = mb; + } + count++; + if (count == interval || mb == endMeasure) { + undoAddPageLock(score, new RangeLock(lockStart, mb)); + lockStart = nullptr; + count = 0; + } + if (mb == endMeasure) { + break; + } + } +} + +void EditPageLocks::makeIntoPage(Score* score, MeasureBase* first, MeasureBase* last) +{ + bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); + + const RangeLock* lockContainingfirst = score->pageLocks()->lockContaining(first); + const RangeLock* lockContaininglast = score->pageLocks()->lockContaining(last); + + if (lockContainingfirst) { + undoRemovePageLock(score, lockContainingfirst); + if (lockContainingfirst->startMB()->isBefore(first)) { + MeasureBase* oneBeforeFirst = mmrests ? first->prevMM() : first->prev(); + RangeLock* newLockBefore = new RangeLock(lockContainingfirst->startMB(), oneBeforeFirst); + undoAddPageLock(score, newLockBefore); + } + } + + if (lockContaininglast) { + if (lockContaininglast != lockContainingfirst) { + undoRemovePageLock(score, lockContaininglast); + } + if (last->isBefore(lockContaininglast->endMB())) { + MeasureBase* oneAfterLast = mmrests ? last->nextMM() : last->next(); + RangeLock* newLockAfter = new RangeLock(oneAfterLast, lockContaininglast->endMB()); + undoAddPageLock(score, newLockAfter); + } + } + + std::vector locksContainedInRange = score->pageLocks()->locksContainedInRange(first, last); + for (const RangeLock* lock : locksContainedInRange) { + if (lock != lockContainingfirst && lock != lockContaininglast) { + undoRemovePageLock(score, lock); + } + } + + RangeLock* newLock = new RangeLock(first, last); + undoAddPageLock(score, newLock); +} + +void EditPageLocks::moveMeasureToPrevPage(Score* score, MeasureBase* m) +{ + const Page* prevPage = m->prevPage(); + if (!prevPage) { + return; + } + + MeasureBase* prevPageFirstMeas = prevPage->firstMeasureBase(); + + const RangeLock* prevPageLock = score->pageLocks()->lockStartingAt(prevPageFirstMeas); + if (prevPageLock) { + undoRemovePageLock(score, prevPageLock); + } + + const Page* curPage = m->page(); + const RangeLock* curPageLock = score->pageLocks()->lockStartingAt(curPage->firstMeasureBase()); + if (curPageLock) { + undoRemovePageLock(score, curPageLock); + if (curPageLock->endMB() != m) { + const bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); + MeasureBase* nextMB = mmrests ? m->nextMM() : m->next(); + RangeLock* newLockOnCurPage = new RangeLock(nextMB, curPageLock->endMB()); + undoAddPageLock(score, newLockOnCurPage); + } + } + + RangeLock* pageLock = new RangeLock(prevPageFirstMeas, m); + undoAddPageLock(score, pageLock); +} + +void EditPageLocks::moveMeasureToNextPage(Score* score, MeasureBase* m) +{ + const Page* curPage = m->page(); + MeasureBase* startMeas = curPage->firstMeasureBase(); + bool refMeasureIsStartOfPage = m == startMeas; + + const RangeLock* curLock = score->pageLocks()->lockStartingAt(startMeas); + if (curLock) { + undoRemovePageLock(score, curLock); + } + + if (!refMeasureIsStartOfPage) { + bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); + MeasureBase* prevMeas = mmrests ? m->prevMM() : m->prev(); + RangeLock* pageLock = new RangeLock(startMeas, prevMeas); + undoAddPageLock(score, pageLock); + } + + const Page* nextPage = m->nextPage(); + if (!nextPage) { + return; + } + + const RangeLock* nextPageLock = score->pageLocks()->lockStartingAt(nextPage->firstMeasureBase()); + if (nextPageLock) { + undoRemovePageLock(score, nextPageLock); + } + + if (nextPageLock || refMeasureIsStartOfPage) { + RangeLock* newNextPageLock = new RangeLock(m, nextPage->lastMeasureBase()); + undoAddPageLock(score, newNextPageLock); + } +} + +void EditPageLocks::applyLockToSelection(Score* score) +{ + MeasureBase* first = nullptr; + MeasureBase* last = nullptr; + + if (score->selection().isRange()) { + first = score->selection().startMeasureBase(); + last = score->selection().endMeasureBase(); + } else { + for (EngravingItem* el : score->selection().elements()) { + // if (el->isPageLockIndicator()) { TODO + // const RangeLock* lock = toPageLockIndicator(el)->pageLock(); + // first = lock->startMB(); + // last = lock->endMB(); + // break; + // } + MeasureBase* mb = el->findMeasureBase(); + if (!mb) { + continue; + } + if (!first || mb->isBefore(first)) { + first = mb; + } + if (!last || mb->isAfter(last)) { + last = mb; + } + } + } + + if (!first || !last) { + return; + } + + const RangeLock* lockOnLast = score->pageLocks()->lockContaining(last); + if (lockOnLast && lockOnLast->endMB() == last) { + undoRemovePageLock(score, lockOnLast); + } else if (first != last) { + makeIntoPage(score, first, last); + } else { + makeIntoPage(score, first->page()->firstMeasureBase(), last); + } +} + +void EditPageLocks::removePageLocksOnAddLayoutBreak(Score* score, LayoutBreakType breakType, const MeasureBase* measure) +{ + IF_ASSERT_FAILED(breakType != LayoutBreakType::NOBREAK) { + return; // NOBREAK not allowed on locked measures + } + + const RangeLock* lock = score->pageLocks()->lockContaining(measure); + if (lock && (breakType == LayoutBreakType::PAGE || measure != lock->endMB())) { + undoRemovePageLock(score, lock); + } +} + +void EditPageLocks::removeLayoutBreaksOnAddPageLock(Score* score, const RangeLock* lock) +{ + bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); + for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mmrests ? mb->nextMM() : mb->next()) { + mb->undoSetBreak(false, LayoutBreakType::LINE); + mb->undoSetBreak(false, LayoutBreakType::NOBREAK); + if (mb != lock->endMB()) { + mb->undoSetBreak(false, LayoutBreakType::SECTION); + mb->undoSetBreak(false, LayoutBreakType::PAGE); + } + } +} + +void EditPageLocks::removePageLocksOnRemoveMeasures(Score* score, const MeasureBase* m1, const MeasureBase* m2) +{ + std::vector allPageLocks = score->pageLocks()->allLocks(); + for (const RangeLock* lock : allPageLocks) { + MeasureBase* lockStart = lock->startMB(); + MeasureBase* lockEnd = lock->endMB(); + bool lockStartIsInRange = lockStart->isAfterOrEqual(m1) && lockStart->isBeforeOrEqual(m2); + bool lockEndIsInRange = lockEnd->isAfterOrEqual(m1) && lockEnd->isBeforeOrEqual(m2); + if (lockStartIsInRange || lockEndIsInRange) { + undoRemovePageLock(score, lock); + } + if (lockStartIsInRange && !lockEndIsInRange) { + MeasureBase* newLockStart = m2->nextMeasure(); + if (newLockStart) { + undoAddPageLock(score, new RangeLock(newLockStart, lockEnd)); + } + } else if (!lockStartIsInRange && lockEndIsInRange) { + MeasureBase* newLockEnd = m1->prevMeasure(); + if (newLockEnd) { + undoAddPageLock(score, new RangeLock(lockStart, newLockEnd)); + } + } + } +} + +void EditPageLocks::removePageLocksContainingMMRests(Score* score) +{ + std::vector allLocks = score->pageLocks()->allLocks(); // copy + for (const RangeLock* lock : allLocks) { + for (MeasureBase* mb = lock->startMB(); mb; mb = mb->next()) { + if (mb->isMeasure() && toMeasure(mb)->mmRest()) { + undoRemovePageLock(score, lock); + break; + } + if (mb->isAfter(lock->endMB())) { + break; + } + } + } +} + +void EditPageLocks::updatePageLocksOnCreateMMRests(Score* score, Measure* first, Measure* last) +{ + // NOTE: this must be done during layout as the mmRests get created. + + for (const RangeLock* lock : score->pageLocks()->locksContainedInRange(first, last)) { + // These locks are inside the range of the mmRest so remove them + undoRemovePageLock(score, lock); + } + + const RangeLock* lockOnFirst = score->pageLocks()->lockContaining(first); + const RangeLock* lockOnLast = score->pageLocks()->lockContaining(last); + + if (lockOnFirst) { + MeasureBase* startMB = lockOnFirst->startMB(); + MeasureBase* endMB = lockOnFirst->endMB(); + + if (startMB->isBefore(first)) { + if (endMB->isBeforeOrEqual(last)) { + endMB = first->mmRest(); + } else { + return; + } + } else { + startMB = first->mmRest(); + } + + if (startMB != lockOnFirst->startMB() || endMB != lockOnFirst->endMB()) { + undoRemovePageLock(score, lockOnFirst); + undoAddPageLock(score, new RangeLock(startMB, endMB)); + } + } + + if (!lockOnLast || lockOnLast == lockOnFirst) { + return; + } + + MeasureBase* startMB = lockOnLast->startMB(); + MeasureBase* endMB = lockOnLast->endMB(); + assert(startMB->isAfter(first) && endMB->isAfter(last)); + + undoRemovePageLock(score, lockOnLast); + startMB = last->nextMM(); + undoAddPageLock(score, new RangeLock(startMB, endMB)); +} diff --git a/src/engraving/editing/editpagelocks.h b/src/engraving/editing/editpagelocks.h new file mode 100644 index 0000000000000..c6aab16de9854 --- /dev/null +++ b/src/engraving/editing/editpagelocks.h @@ -0,0 +1,61 @@ +/* + * 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 Measure; +class MeasureBase; +class Page; +class Score; +class System; +class RangeLock; + +enum class LayoutBreakType : unsigned char; + +class EditPageLocks +{ +public: + static void undoAddPageLock(Score* score, const RangeLock* lock); + static void undoRemovePageLock(Score* score, const RangeLock* lock); + static void undoRemoveAllLocks(Score* score); + + static void togglePageLock(Score* score, const std::vector& pages); + static void toggleScoreLock(Score* score); + + static void addRemovePageLocks(Score* score, int interval, bool lock); + + static void makeIntoPage(Score* score, MeasureBase* first, MeasureBase* last); + static void moveMeasureToPrevPage(Score* score, MeasureBase* m); + static void moveMeasureToNextPage(Score* score, MeasureBase* m); + + static void applyLockToSelection(Score* score); + + static void removeLayoutBreaksOnAddPageLock(Score* score, const RangeLock* lock); + static void removePageLocksOnAddLayoutBreak(Score* score, LayoutBreakType breakType, const MeasureBase* measure); + static void removePageLocksOnRemoveMeasures(Score* score, const MeasureBase* m1, const MeasureBase* m2); + static void removePageLocksContainingMMRests(Score* score); + static void updatePageLocksOnCreateMMRests(Score* score, Measure* first, Measure* last); +}; +} diff --git a/src/engraving/editing/editstyle.cpp b/src/engraving/editing/editstyle.cpp index ea606e57fe7d8..a3b47d06acfac 100644 --- a/src/engraving/editing/editstyle.cpp +++ b/src/engraving/editing/editstyle.cpp @@ -24,6 +24,7 @@ #include "../dom/chordlist.h" #include "../dom/score.h" +#include "editing/editpagelocks.h" #include "editing/editsystemlocks.h" using namespace mu::engraving; @@ -125,6 +126,7 @@ static void changeStyleValue(Score* score, Sid idx, const PropertyValue& oldValu case Sid::createMultiMeasureRests: if (oldValue.toBool() == true && newValue.toBool() == false) { EditSystemLocks::removeSystemLocksContainingMMRests(score); + EditPageLocks::removePageLocksContainingMMRests(score); } break; default: diff --git a/src/engraving/rendering/score/layoutcontext.cpp b/src/engraving/rendering/score/layoutcontext.cpp index a086a1456511f..8abaeda7d9c87 100644 --- a/src/engraving/rendering/score/layoutcontext.cpp +++ b/src/engraving/rendering/score/layoutcontext.cpp @@ -22,6 +22,7 @@ #include "layoutcontext.h" #include "editing/addremoveelement.h" +#include "editing/editpagelocks.h" #include "editing/editsystemlocks.h" #include "editing/mscoreview.h" #include "style/defaultstyle.h" @@ -383,6 +384,14 @@ const RangeLocks* DomAccessor::systemLocks() const return score()->systemLocks(); } +const RangeLocks* DomAccessor::pageLocks() const +{ + IF_ASSERT_FAILED(score()) { + return nullptr; + } + return score()->pageLocks(); +} + const PaddingTable& DomAccessor::paddingTable() const { return score()->paddingTable(); @@ -479,11 +488,12 @@ void DomAccessor::removeElement(EngravingItem* item) score()->removeElement(item); } -void DomAccessor::updateSystemLocksOnCreateMMRest(Measure* first, Measure* last) +void DomAccessor::updateLocksOnCreateMMRest(Measure* first, Measure* last) { IF_ASSERT_FAILED(score()) { return; } + EditPageLocks::updatePageLocksOnCreateMMRests(score(), first, last); EditSystemLocks::updateSystemLocksOnCreateMMRests(score(), first, last); } diff --git a/src/engraving/rendering/score/layoutcontext.h b/src/engraving/rendering/score/layoutcontext.h index 471b849f43c7b..6fff8cb1edeb9 100644 --- a/src/engraving/rendering/score/layoutcontext.h +++ b/src/engraving/rendering/score/layoutcontext.h @@ -179,6 +179,7 @@ class DomAccessor const ChordRest* findCR(Fraction tick, track_idx_t track) const; const RangeLocks* systemLocks() const; + const RangeLocks* pageLocks() const; const PaddingTable& paddingTable() const; @@ -205,7 +206,7 @@ class DomAccessor void undo(UndoableCommand* cmd, EditData* ed = nullptr) const; void addElement(EngravingItem* item); void removeElement(EngravingItem* item); - void updateSystemLocksOnCreateMMRest(Measure* first, Measure* last); + void updateLocksOnCreateMMRest(Measure* first, Measure* last); void undoChangeParent(EngravingItem* element, EngravingItem* parent, staff_idx_t staff, bool changeLinksParents = true); void addUnmanagedSpanner(Spanner* s); diff --git a/src/engraving/rendering/score/measurelayout.cpp b/src/engraving/rendering/score/measurelayout.cpp index 86c7e5a436ec6..a6121e510bac5 100644 --- a/src/engraving/rendering/score/measurelayout.cpp +++ b/src/engraving/rendering/score/measurelayout.cpp @@ -239,7 +239,7 @@ void MeasureLayout::createMMRest(LayoutContext& ctx, Measure* firstMeasure, Meas mmrMeasure->setMMRestCount(numMeasuresInMMRest); mmrMeasure->setMeasureNumber(firstMeasure->measureNumber()); - ctx.mutDom().updateSystemLocksOnCreateMMRest(firstMeasure, lastMeasure); + ctx.mutDom().updateLocksOnCreateMMRest(firstMeasure, lastMeasure); mmrMeasure->setRepeatStart(firstMeasure->repeatStart() || lastMeasure->repeatStart()); mmrMeasure->setRepeatEnd(firstMeasure->repeatEnd() || lastMeasure->repeatEnd()); diff --git a/src/engraving/rendering/score/pagelayout.cpp b/src/engraving/rendering/score/pagelayout.cpp index 39ee8fa130714..9c504f48ab901 100644 --- a/src/engraving/rendering/score/pagelayout.cpp +++ b/src/engraving/rendering/score/pagelayout.cpp @@ -143,12 +143,22 @@ void PageLayout::collectPage(LayoutContext& ctx) System* nextSystem = nullptr; int systemIdx = -1; + auto getPageLock = [&ctx](MeasureBase* mb) -> const RangeLock* { + return mb && ctx.conf().viewMode() == LayoutMode::PAGE + ? ctx.dom().pageLocks()->lockStartingAt(mb) : nullptr; + }; + + const RangeLock* pageLock = nullptr; + // re-calculate positions for systems before current // (they may have been filled on previous layout) size_t pSystems = page->systems().size(); if (pSystems > 0) { SystemLayout::restoreLayout2(page->system(0), ctx); y = page->system(0)->y() + page->system(0)->height(); + + // Get existing page lock + pageLock = getPageLock(page->system(0)->first()); } else { y = page->tm(); } @@ -172,6 +182,8 @@ void PageLayout::collectPage(LayoutContext& ctx) distance = SystemLayout::minDistance(ctx.state().prevSystem(), ctx.state().curSystem(), ctx); } else { // this is the first system on page + // get page lock here + pageLock = getPageLock(ctx.state().curSystem()->first()); if (ctx.state().curSystem()->vbox()) { // if the header exists and there is a frame, move the frame downwards // to avoid collisions @@ -244,9 +256,15 @@ void PageLayout::collectPage(LayoutContext& ctx) assert(ctx.state().curSystem() != nextSystem); ctx.mutState().setCurSystem(nextSystem); - bool isPageBreak = !ctx.state().curSystem() || (breakPages && ctx.state().prevSystem()->pageBreak()); + MeasureBase* endMB = ctx.state().prevSystem()->lastMeasure(); + bool endOfPageLock = endMB && endMB->isEndOfPageLock(); + const MeasureBase* nextMB = endMB ? endMB->nextMM() : nullptr; + bool pageLockStart = nextMB && nextMB->isStartOfPageLock(); - if (!isPageBreak) { + bool isPageBreak = !ctx.state().curSystem() + || (breakPages && (ctx.state().prevSystem()->pageBreak() || endOfPageLock || pageLockStart)); + + if (!isPageBreak && !pageLock) { double dist = SystemLayout::minDistance(ctx.state().prevSystem(), ctx.state().curSystem(), ctx) + ctx.state().curSystem()->height(); Box* vbox = ctx.state().curSystem()->vbox(); @@ -278,7 +296,7 @@ void PageLayout::collectPage(LayoutContext& ctx) dist += footerPadding; } dist = std::max(dist, slb); - layoutPage(ctx, page, endY - (y + dist), footerPadding); + layoutPage(ctx, page, endY - (y + dist), footerPadding, pageLock); // if we collected a system we cannot fit onto this page, // we need to collect next page in order to correctly set system positions if (collected) { @@ -527,10 +545,10 @@ void PageLayout::layoutArticAndFingeringOnCrossStaffBeams(LayoutContext& ctx, Sy // systems. //--------------------------------------------------------- -void PageLayout::layoutPage(LayoutContext& ctx, Page* page, double restHeight, double footerPadding) +void PageLayout::layoutPage(LayoutContext& ctx, Page* page, double restHeight, double footerPadding, bool squeezeToFit) { TRACEFUNC; - if (restHeight < 0.0) { + if (restHeight < 0.0 && !squeezeToFit) { LOGN("restHeight < 0.0: %f\n", restHeight); restHeight = 0; } @@ -558,12 +576,17 @@ void PageLayout::layoutPage(LayoutContext& ctx, Page* page, double restHeight, d system->move(PointF(0.0, 0.0)); } } else if ((ctx.conf().viewMode() != LayoutMode::SYSTEM) && ctx.conf().isVerticalSpreadEnabled()) { - distributeStaves(ctx, page, footerPadding); + distributeStaves(ctx, page, footerPadding, squeezeToFit); } return; } + const bool compactStaves = restHeight <= 0.0; + auto pageFilled = [compactStaves](double space) -> bool { + return compactStaves ? (space >= 0.0) : (space <= 0.0); + }; + double maxDist = ctx.conf().maxSystemDistance(); // allocate space as needed to normalize system distance (bottom of one system to top of next) @@ -589,7 +612,7 @@ void PageLayout::layoutPage(LayoutContext& ctx, Page* page, double restHeight, d s->setDistance(d); } restHeight -= totalFill; // reduce available space for next iteration - if (restHeight <= 0) { + if (pageFilled(restHeight)) { break; // no space left } } @@ -617,7 +640,7 @@ void PageLayout::layoutPage(LayoutContext& ctx, Page* page, double restHeight, d page->systems().back()->mutldata()->setPosY(y); } -void PageLayout::distributeStaves(LayoutContext& ctx, Page* page, double footerPadding) +void PageLayout::distributeStaves(LayoutContext& ctx, Page* page, double footerPadding, bool squeezeToFit) { VerticalGapDataList vgdl; @@ -712,12 +735,17 @@ void PageLayout::distributeStaves(LayoutContext& ctx, Page* page, double footerP if (nextSpacer) { spaceRemaining -= std::max(0.0, nextSpacer->absoluteGap() - spacerOffset - staffLowerBorder); } - if (spaceRemaining <= 0.0) { + if (spaceRemaining <= 0.0 && !squeezeToFit) { return; } + const bool compactStaves = spaceRemaining <= 0.0; + auto breakLoop = [compactStaves](double space) -> bool { + return compactStaves ? (space >= 0.0) : (space <= 0.0); + }; + // Try to make the gaps equal, taking the spread factors and maximum spacing into account. - static const int maxPasses { 20 }; // Saveguard to prevent endless loops. + static const int maxPasses { 20 }; // Saveguard to prevent endless loops. int pass { 0 }; while (!muse::RealIsNull(spaceRemaining) && (ngaps > 0) && (++pass < maxPasses)) { ngaps = 0; @@ -747,11 +775,11 @@ void PageLayout::distributeStaves(LayoutContext& ctx, Page* page, double footerP modified.push_back(vgd); ++ngaps; } - if ((spaceRemaining - addedSpace) <= 0.0) { + if (breakLoop(spaceRemaining - addedSpace)) { break; } } - if ((spaceRemaining - addedSpace) <= 0.0) { + if (breakLoop(spaceRemaining - addedSpace)) { for (VerticalGapData* vgd : modified) { vgd->undoLastAddSpacing(); } diff --git a/src/engraving/rendering/score/pagelayout.h b/src/engraving/rendering/score/pagelayout.h index ff166cfd61d47..107dfc74ddec6 100644 --- a/src/engraving/rendering/score/pagelayout.h +++ b/src/engraving/rendering/score/pagelayout.h @@ -39,8 +39,8 @@ class PageLayout static void collectPage(LayoutContext& ctx); private: - static void layoutPage(LayoutContext& ctx, Page* page, double restHeight, double footerPadding); - static void distributeStaves(LayoutContext& ctx, Page* page, double footerPadding); + static void layoutPage(LayoutContext& ctx, Page* page, double restHeight, double footerPadding, bool squeezeToFit); + static void distributeStaves(LayoutContext& ctx, Page* page, double footerPadding, bool squeezeToFit); static void layoutCrossStaffElements(LayoutContext& ctx, Page* page); static void layoutCrossStaffSlurs(LayoutContext& ctx, System* system); diff --git a/src/engraving/rendering/score/systemlayout.cpp b/src/engraving/rendering/score/systemlayout.cpp index 86e7d7c397675..8c1c1a4e8de46 100644 --- a/src/engraving/rendering/score/systemlayout.cpp +++ b/src/engraving/rendering/score/systemlayout.cpp @@ -262,8 +262,8 @@ System* SystemLayout::collectSystem(LayoutContext& ctx) switch (ctx.conf().viewMode()) { case LayoutMode::PAGE: case LayoutMode::SYSTEM: - lineBreak = mb->pageBreak() || mb->lineBreak() || mb->sectionBreak() || mb->isEndOfSystemLock() - || (next && next->isStartOfSystemLock()); + lineBreak = mb->pageBreak() || mb->lineBreak() || mb->sectionBreak() || mb->isEndOfSystemLock() || mb->isEndOfPageLock() + || (next && next->isStartOfSystemLock()) || (next && next->isStartOfPageLock()); break; case LayoutMode::FLOAT: case LayoutMode::LINE: diff --git a/src/engraving/rw/read500/read500.cpp b/src/engraving/rw/read500/read500.cpp index 5696ceea1c873..e1879ccb85cd4 100644 --- a/src/engraving/rw/read500/read500.cpp +++ b/src/engraving/rw/read500/read500.cpp @@ -189,6 +189,8 @@ bool Read500::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx) e.skipCurrentElement(); } } + } else if (tag == "PageLocks") { + TRead::readPageLocks(score, e); } else if (tag == "SystemLocks") { TRead::readSystemLocks(score, e); } else if (tag == "SystemDividers") { diff --git a/src/engraving/rw/read500/tread.cpp b/src/engraving/rw/read500/tread.cpp index d27e4be99d473..f8424984e784c 100644 --- a/src/engraving/rw/read500/tread.cpp +++ b/src/engraving/rw/read500/tread.cpp @@ -4761,6 +4761,17 @@ void TRead::readSpanner(XmlReader& e, ReadContext& ctx, Score* current, track_id ConnectorInfoReader::readConnector(info, e, ctx); } +void TRead::readPageLocks(Score* score, XmlReader& e) +{ + while (e.readNextStartElement()) { + if (e.name() == "pageLock") { + readPageLock(score, e); + } else { + e.unknown(); + } + } +} + void TRead::readSystemLocks(Score* score, XmlReader& e) { while (e.readNextStartElement()) { @@ -4772,6 +4783,32 @@ void TRead::readSystemLocks(Score* score, XmlReader& e) } } +void TRead::readPageLock(Score* score, XmlReader& e) +{ + MeasureBase* startMeas = nullptr; + MeasureBase* endMeas = nullptr; + EIDRegister* eidRegister = score->masterScore()->eidRegister(); + + while (e.readNextStartElement()) { + AsciiStringView tag(e.name()); + if (tag == "startMeasure") { + EID startMeasId = EID::fromStdString(e.readAsciiText()); + startMeas = toMeasureBase(eidRegister->itemFromEID(startMeasId)); + } else if (tag == "endMeasure") { + EID endMeasId = EID::fromStdString(e.readAsciiText()); + endMeas = toMeasureBase(eidRegister->itemFromEID(endMeasId)); + } else { + e.unknown(); + } + } + + IF_ASSERT_FAILED(startMeas && endMeas) { + return; + } + + score->addPageLock(new RangeLock(startMeas, endMeas)); +} + void TRead::readSystemLock(Score* score, XmlReader& e) { MeasureBase* startMeas = nullptr; diff --git a/src/engraving/rw/read500/tread.h b/src/engraving/rw/read500/tread.h index 0f2cad9c2f49b..5094d1157d385 100644 --- a/src/engraving/rw/read500/tread.h +++ b/src/engraving/rw/read500/tread.h @@ -391,6 +391,7 @@ class TRead static void readSpanner(XmlReader& e, ReadContext& ctx, EngravingItem* current, track_idx_t track); static void readSpanner(XmlReader& e, ReadContext& ctx, Score* current, track_idx_t track); + static void readPageLocks(Score* score, XmlReader& e); static void readSystemLocks(Score* score, XmlReader& e); static void readSystemDividers(Score* score, XmlReader& e, ReadContext& ctx); @@ -409,6 +410,7 @@ class TRead static bool readProperties(TextBase* t, XmlReader& xml, ReadContext& ctx); static bool readProperties(StaffTextBase* t, XmlReader& xml, ReadContext& ctx); + static void readPageLock(Score* score, XmlReader& e); static void readSystemLock(Score* score, XmlReader& e); static void readHopoText(HammerOnPullOffSegment* hopoSeg, XmlReader& xml, ReadContext& ctx, int idx); diff --git a/src/engraving/rw/write/twrite.cpp b/src/engraving/rw/write/twrite.cpp index b61cd5a4db3ae..31c6d3df379e8 100644 --- a/src/engraving/rw/write/twrite.cpp +++ b/src/engraving/rw/write/twrite.cpp @@ -448,6 +448,20 @@ void TWrite::writeProperty(const EngravingItem* item, XmlWriter& xml, Pid pid, b xml.tagProperty(pid, p, d); } +void TWrite::writePageLocks(const Score* score, XmlWriter& xml) +{ + std::vector locks = score->pageLocks()->allLocks(); + if (locks.empty()) { + return; + } + + xml.startElement("PageLocks"); + for (const RangeLock* sl : locks) { + writePageLock(sl, xml); + } + xml.endElement(); +} + void TWrite::writeSystemLocks(const Score* score, XmlWriter& xml) { std::vector locks = score->systemLocks()->allLocks(); @@ -525,6 +539,16 @@ void TWrite::writeItemLink(const EngravingObject* item, XmlWriter& xml, WriteCon } } +void TWrite::writePageLock(const RangeLock* pageLock, XmlWriter& xml) +{ + xml.startElement("pageLock"); + + xml.tag("startMeasure", pageLock->startMB()->eid().toStdString()); + xml.tag("endMeasure", pageLock->endMB()->eid().toStdString()); + + xml.endElement(); +} + void TWrite::writeSystemLock(const RangeLock* systemLock, XmlWriter& xml) { xml.startElement("systemLock"); diff --git a/src/engraving/rw/write/twrite.h b/src/engraving/rw/write/twrite.h index 61baf162d4747..b02a24f87495f 100644 --- a/src/engraving/rw/write/twrite.h +++ b/src/engraving/rw/write/twrite.h @@ -315,6 +315,7 @@ class TWrite static void writeProperty(const EngravingItem* item, XmlWriter& xml, Pid pid, bool force = false); + static void writePageLocks(const Score* score, XmlWriter& xml); static void writeSystemLocks(const Score* score, XmlWriter& xml); static void writeSystemDividers(const Score* score, XmlWriter& xml, WriteContext& ctx); @@ -366,6 +367,7 @@ class TWrite static void writeTupletStart(DurationElement* item, XmlWriter& xml, WriteContext& ctx); static void writeTupletEnd(DurationElement* item, XmlWriter& xml, WriteContext& ctx); + static void writePageLock(const RangeLock* pageLock, XmlWriter& xml); static void writeSystemLock(const RangeLock* systemLock, XmlWriter& xml); static muse::String lineBreakToTag(const String& str); diff --git a/src/engraving/rw/write/writer.cpp b/src/engraving/rw/write/writer.cpp index 58a2c0bb881d1..9d10f6cda3d30 100644 --- a/src/engraving/rw/write/writer.cpp +++ b/src/engraving/rw/write/writer.cpp @@ -264,6 +264,7 @@ void Writer::write(Score* score, XmlWriter& xml, WriteContext& ctx, compat::Writ hook.onWriteExcerpts302(score, xml, ctx); + TWrite::writePageLocks(score, xml); TWrite::writeSystemLocks(score, xml); TWrite::writeSystemDividers(score, xml, ctx); diff --git a/src/engraving/tests/CMakeLists.txt b/src/engraving/tests/CMakeLists.txt index d04f597955a2c..f7642a0fb9a35 100644 --- a/src/engraving/tests/CMakeLists.txt +++ b/src/engraving/tests/CMakeLists.txt @@ -101,6 +101,7 @@ set(MODULE_TEST_SRC ${CMAKE_CURRENT_LIST_DIR}/engraving_xml_tests.cpp ${CMAKE_CURRENT_LIST_DIR}/parentheses_tests.cpp ${CMAKE_CURRENT_LIST_DIR}/lyrics_tests.cpp + ${CMAKE_CURRENT_LIST_DIR}/page_locks_tests.cpp ${CMAKE_CURRENT_LIST_DIR}/automation/automation_tests.cpp ${CMAKE_CURRENT_LIST_DIR}/mocks/engravingconfigurationmock.h diff --git a/src/engraving/tests/page_locks_data/page_locks-1.mscx b/src/engraving/tests/page_locks_data/page_locks-1.mscx new file mode 100644 index 0000000000000..160ee2149f86a --- /dev/null +++ b/src/engraving/tests/page_locks_data/page_locks-1.mscx @@ -0,0 +1,453 @@ + + + 5.0.0 + + + qPWhSMRUykP_W/MlN2Kt4nD + 480 + 1 + 1 + 1 + 0 + 1 + + + Composer / arranger + + 2026-05-05 + + + + Apple Macintosh + + + + + Untitled score + + Orchestra + + Keyboards + +
+ flutes + oboes + clarinets + saxophones + bassoons + +
+
+ horns + trumpets + cornets + flugelhorns + trombones + tubas + +
+
+ timpani +
+
+ keyboard-percussion + + drums + unpitched-metal-percussion + unpitched-wooden-percussion + other-percussion + +
+ keyboards + harps + organs + synths + + +
+ voices + voice-groups +
+
+ orchestral-strings +
+
+ + + GQERcLYCU6_ZSmULnUuGWM + + stdNormal + + + Brace + 2 + 0 + + 1 + + + hlkW7+iqlNC_SQbW3IBd0FL + + stdNormal + + F + + + + Piano + Pno. + + Piano + 21 + 108 + 21 + 108 + keyboard.piano + F + + + Fluid + + + + + + 10 + ZnrX6gjNRqP_fENQ2ZwNDjP + + hUREh/Mon9M_zXAxVMK8HGO + + Untitled score + + + eOq1Se/RUxF_2tcz7wpdFeP + + Composer / arranger + + + + b2Ne9atCTCP_lSOkaEok6y + + + EPs5A1miv/K_jL2Oxy5bhzL + 0 + + + U8xHrKfOuCB_512l3I6YlCF + 4 + 4 + + + wVbht8CppiK_1eYOpdLmsAN + measure + 4/4 + + + + + J4bsq34eXWE_AMWXmPXDQTN + + + /w7ke81RsuO_3vvVvBaKPhD + measure + 4/4 + + + + + SihhuIs6gLF_EUvNUMmmAwG + + + kx0lnvJ0MBJ_c1+Yo+8CuZE + measure + 4/4 + + + + + C/JT1AUzOTF_vfDoGdy8/WC + + + AAO7JlAOTIF_KxpEMX7n/vJ + measure + 4/4 + + + + + Z/ZKntWrCDO_+bIQMUIqMGL + + + xX/QHsuZYEC_aKU7/bee3fL + measure + 4/4 + + + + + UgqzYhdv3oM_NTSr5GwCFCB + + + Hl2ZKj2BZtM_iKPWfG+gwBE + measure + 4/4 + + + + + Oi9mCHZyRVN_Aln8MKQuw6B + + + rFT7PA3HdCF_F2dV9Ec5wFH + measure + 4/4 + + + + + ZCg0MA/+ESL_l1si0+FjAII + + + 193GhkKF0HM_FM+Mz+f5XuO + measure + 4/4 + + + + + W+WgRPE0yKO_LRCoFcvuB+M + + + bdq149gW4LP_YvC+1dNlusI + measure + 4/4 + + + + + A/E1clXWqBC_bf5KQDGTb6N + + + uyi+6guy0RB_EZzoKg1DVWG + measure + 4/4 + + + + + VB8rIiES6cM_CBn25MQYluB + + + bVqXlhnq6RD_8tWn0hKvby + measure + 4/4 + + + + + CGmTWY6K/aE_XQRBuF8990J + + + HJWn6fnK8DG_bobyyqX7iwG + measure + 4/4 + + + + + TDqSDzlDYSD_5muw1c7/sgG + + + YD/q71KLq2N_0bvgnlj/UyO + measure + 4/4 + + + + + 73uaqgGIAfI_Fe2DQAnx7pM + + + GPNWyGWv0bI_SdYrlH+Vn6H + measure + 4/4 + + + + + VHVyOd2VE2B_DFa0Hp0Lpz + + + gHDZR90MNkO_ySGkNod6KMJ + measure + 4/4 + + + + + + + + + lTevhag90HI_dLz48ZAWonC + 0 + + + H7zn0oAbtrC_PHR3kJggExL + 4 + 4 + + + Pmu0ldSxpwM_bUgLZcDoU9O + measure + 4/4 + + + + + + + ILLWOkFFMEG_W9SUzJot7VE + measure + 4/4 + + + + + + + rEJJPgiKGTE_OYjIFcYVpgG + measure + 4/4 + + + + + + + e2gfoKuBKWP_bA3Dbhxw8a + measure + 4/4 + + + + + + + q8wJ3RqrszL_MC4XF2RwToI + measure + 4/4 + + + + + + + VMpSfdoDYbE_mt4WdS/oCAI + measure + 4/4 + + + + + + + SCRPrVWNumI_j7wh1b3E9PI + measure + 4/4 + + + + + + + YVATa6TOZqP_DQNE5cX/jyO + measure + 4/4 + + + + + + + RcViisuBF6N_gCZbRJay/l + measure + 4/4 + + + + + + + 4muATKmrUnI_FaoW/fJnAsH + measure + 4/4 + + + + + + + Sa+B3q8zKwP_79vRlzo/rsE + measure + 4/4 + + + + + + + mCYWwLWaR8N_QnWildcTsdO + measure + 4/4 + + + + + + + 0YfXsJeF3NK_SH65vHQPk/H + measure + 4/4 + + + + + + + PRIO/nj4TSL_h5ak2RcWFBK + measure + 4/4 + + + + + + + q168qRx7bFL_pAoazlawWxJ + measure + 4/4 + + + + + + + ZnrX6gjNRqP_fENQ2ZwNDjP + SihhuIs6gLF_EUvNUMmmAwG + + + C/JT1AUzOTF_vfDoGdy8/WC + Oi9mCHZyRVN_Aln8MKQuw6B + + + ZCg0MA/+ESL_l1si0+FjAII + VB8rIiES6cM_CBn25MQYluB + + + CGmTWY6K/aE_XQRBuF8990J + VHVyOd2VE2B_DFa0Hp0Lpz + + +
+
\ No newline at end of file diff --git a/src/engraving/tests/page_locks_data/page_locks-1.mscz b/src/engraving/tests/page_locks_data/page_locks-1.mscz new file mode 100644 index 0000000000000000000000000000000000000000..9688bb83c400f31581646a23cb2d83ef305188a9 GIT binary patch literal 25836 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 literal 0 HcmV?d00001 diff --git a/src/engraving/tests/page_locks_tests.cpp b/src/engraving/tests/page_locks_tests.cpp new file mode 100644 index 0000000000000..660f4ac4fca67 --- /dev/null +++ b/src/engraving/tests/page_locks_tests.cpp @@ -0,0 +1,214 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2021 MuseScore Limited + * + * 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 + +#include "engraving/dom/page.h" +#include "engraving/editing/editpagelocks.h" + +#include "utils/scorerw.h" +#include "utils/scorecomp.h" +#include "utils/testutils.h" + +using namespace mu::engraving; + +static const String PAGE_LOCKS_DATA_DIR("page_locks_data/"); + +class Engraving_PageLocksTests : public ::testing::Test +{ +}; + +TEST_F(Engraving_PageLocksTests, readLocksFromFile) +{ + MasterScore* score = ScoreRW::readScore(PAGE_LOCKS_DATA_DIR + u"page_locks-1.mscx"); + EXPECT_TRUE(score); + + std::vector locks = score->pageLocks()->allLocks(); + EXPECT_FALSE(locks.empty()); + + for (MeasureBase* mb = score->first()->next(); mb; mb = mb->next()) { + EXPECT_TRUE(mb->pageLock()); + } + + for (Page* page : score->pages()) { + EXPECT_TRUE(page->isLocked()); + } + + for (const RangeLock* lock : locks) { + int measureCount = 0; + for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mb->next()) { + ++measureCount; + } + EXPECT_EQ(measureCount, 4); + } + + delete score; +} + +TEST_F(Engraving_PageLocksTests, lockMeasuresPerPage) +{ + MasterScore* score = ScoreRW::readScore(PAGE_LOCKS_DATA_DIR + u"page_locks-1.mscx"); + EXPECT_TRUE(score); + + const RangeLocks* pagelocks = score->pageLocks(); + std::vector allLocks = pagelocks->allLocks(); + EXPECT_FALSE(allLocks.empty()); + + score->startCmd(TranslatableString::untranslatable("Engraving page locks tests")); + score->cmdSelectAll(); + score->endCmd(); + + score->startCmd(TranslatableString::untranslatable("Engraving page locks tests")); + EditPageLocks::addRemovePageLocks(score, 0, false); // Remove all locks + score->endCmd(); + + allLocks = pagelocks->allLocks(); + EXPECT_TRUE(allLocks.empty()); + + std::vector measuresAtPageStart; + std::vector measuresAtPageEnd; + for (Page* page : score->pages()) { + measuresAtPageStart.push_back(page->firstMeasureBase()); + measuresAtPageEnd.push_back(page->lastMeasureBase()); + } + + score->startCmd(TranslatableString::untranslatable("Engraving page locks tests")); + EditPageLocks::addRemovePageLocks(score, 0, true); // Lock current layout + score->endCmd(); + + for (MeasureBase* mb : measuresAtPageStart) { + EXPECT_TRUE(mb->isStartOfPageLock()); + } + for (MeasureBase* mb : measuresAtPageEnd) { + EXPECT_TRUE(mb->isEndOfPageLock()); + } + + score->startCmd(TranslatableString::untranslatable("Engraving page locks tests")); + EditPageLocks::addRemovePageLocks(score, 4, false); // Add locks every 4 measures + score->endCmd(); + + allLocks = pagelocks->allLocks(); + for (const RangeLock* lock : allLocks) { + int measureCount = 0; + for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mb->next()) { + ++measureCount; + } + EXPECT_EQ(measureCount, 4); + } + + delete score; +} + +TEST_F(Engraving_PageLocksTests, makeIntoPage) +{ + MasterScore* score = ScoreRW::readScore(PAGE_LOCKS_DATA_DIR + u"page_locks-1.mscx"); + EXPECT_TRUE(score); + + MeasureBase* thirdMeasure = score->first()->next()->next(); + EXPECT_TRUE(thirdMeasure); + MeasureBase* sixthMeasure = thirdMeasure->next()->next()->next(); + EXPECT_TRUE(sixthMeasure); + + EXPECT_NE(thirdMeasure->system(), sixthMeasure->system()); + + score->startCmd(TranslatableString::untranslatable("Engraving page locks tests")); + EditPageLocks::makeIntoPage(score, thirdMeasure, sixthMeasure); + score->endCmd(); + + EXPECT_TRUE(thirdMeasure->prev()->isEndOfPageLock()); + + EXPECT_TRUE(thirdMeasure->isStartOfPageLock()); + EXPECT_TRUE(sixthMeasure->isEndOfPageLock()); + + EXPECT_TRUE(sixthMeasure->next()->isStartOfPageLock()); + + delete score; +} + +TEST_F(Engraving_PageLocksTests, moveToPreviousNext) +{ + MasterScore* score = ScoreRW::readScore(PAGE_LOCKS_DATA_DIR + u"page_locks-1.mscx"); + EXPECT_TRUE(score); + + MeasureBase* thirdMeasure = score->first()->next()->next(); + EXPECT_TRUE(thirdMeasure); + MeasureBase* sixthMeasure = thirdMeasure->next()->next()->next(); + EXPECT_TRUE(sixthMeasure); + + EXPECT_NE(thirdMeasure->system(), sixthMeasure->system()); + + score->startCmd(TranslatableString::untranslatable("Engraving page locks tests")); + EditPageLocks::moveMeasureToPrevPage(score, sixthMeasure); + score->endCmd(); + + EXPECT_TRUE(sixthMeasure->isEndOfPageLock()); + EXPECT_TRUE(sixthMeasure->next()->isStartOfPageLock()); + + score->startCmd(TranslatableString::untranslatable("Engraving page locks tests")); + EditPageLocks::moveMeasureToNextPage(score, thirdMeasure); + score->endCmd(); + + EXPECT_TRUE(thirdMeasure->prev()->isEndOfPageLock()); + EXPECT_TRUE(thirdMeasure->isStartOfPageLock()); + + delete score; +} + +TEST_F(Engraving_PageLocksTests, togglePageLock) +{ + MasterScore* score = ScoreRW::readScore(PAGE_LOCKS_DATA_DIR + u"page_locks-1.mscx"); + EXPECT_TRUE(score); + + EXPECT_TRUE(score->pages().front()->isLocked()); + + score->select(score->firstMeasure(), SelectType::RANGE); + + score->startCmd(TranslatableString::untranslatable("Engraving page locks tests")); + EditPageLocks::togglePageLock(score, score->selection().selectedPages()); + score->endCmd(); + + EXPECT_FALSE(score->pages().front()->isLocked()); + + score->startCmd(TranslatableString::untranslatable("Engraving page locks tests")); + EditPageLocks::togglePageLock(score, score->selection().selectedPages()); + score->endCmd(); + + EXPECT_TRUE(score->pages().front()->isLocked()); + + score->startCmd(TranslatableString::untranslatable("Engraving page locks tests")); + EditPageLocks::toggleScoreLock(score); + score->endCmd(); + + for (Page* page : score->pages()) { + EXPECT_FALSE(page->isLocked()); + } + + score->startCmd(TranslatableString::untranslatable("Engraving page locks tests")); + EditPageLocks::toggleScoreLock(score); + score->endCmd(); + + for (Page* page : score->pages()) { + EXPECT_TRUE(page->isLocked()); + } + + delete score; +} diff --git a/src/notation/inotationinteraction.h b/src/notation/inotationinteraction.h index a8a33b6bbd578..780c6de186575 100644 --- a/src/notation/inotationinteraction.h +++ b/src/notation/inotationinteraction.h @@ -231,6 +231,11 @@ class INotationInteraction virtual void toggleScoreLock() = 0; virtual void makeIntoSystem() = 0; virtual void applySystemLock() = 0; + virtual void moveMeasureToPrevPage() = 0; + virtual void moveMeasureToNextPage() = 0; + virtual void togglePageLock() = 0; + virtual void makeIntoPage() = 0; + virtual void applyPageLock() = 0; virtual void addRemoveSystemLocks(AddRemoveSystemLockType intervalType, int interval = 0) = 0; virtual bool transpose(const TransposeOptions& options) = 0; diff --git a/src/notation/inotationselection.h b/src/notation/inotationselection.h index ea15711eb0198..740380c3d521d 100644 --- a/src/notation/inotationselection.h +++ b/src/notation/inotationselection.h @@ -58,6 +58,7 @@ class INotationSelection virtual mu::engraving::MeasureBase* startMeasureBase() const = 0; virtual mu::engraving::MeasureBase* endMeasureBase() const = 0; virtual std::vector selectedSystems() const = 0; + virtual std::vector selectedPages() const = 0; virtual bool elementsSelected(const mu::engraving::ElementTypeSet& types) const = 0; }; diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index 09c8f36819b37..9c6243ebbeea7 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -92,6 +92,7 @@ #include "engraving/editing/editchord.h" #include "engraving/editing/editnote.h" #include "engraving/editing/editpart.h" +#include "engraving/editing/editpagelocks.h" #include "engraving/editing/editsystemlocks.h" #include "engraving/editing/exchangevoices.h" #include "engraving/editing/implodeexplode.h" @@ -6119,6 +6120,55 @@ void NotationInteraction::applySystemLock() apply(); } +void NotationInteraction::moveMeasureToPrevPage() +{ + MeasureBase* m = score()->selection().endMeasureBase(); + if (!m) { + return; + } + startEdit(TranslatableString("undoableAction", "Move measure to previous page")); + EditPageLocks::moveMeasureToPrevPage(score(), m); + apply(); +} + +void NotationInteraction::moveMeasureToNextPage() +{ + MeasureBase* m = score()->selection().startMeasureBase(); + if (!m) { + return; + } + startEdit(TranslatableString("undoableAction", "Move measure to next page")); + EditPageLocks::moveMeasureToNextPage(score(), m); + apply(); +} + +void NotationInteraction::togglePageLock() +{ + startEdit(TranslatableString("undoableAction", "Lock/unlock selected page(s)")); + EditPageLocks::togglePageLock(score(), selection()->selectedPages()); + apply(); +} + +void NotationInteraction::makeIntoPage() +{ + MeasureBase* first = score()->selection().startMeasureBase(); + MeasureBase* last = score()->selection().endMeasureBase(); + if (!first || !last) { + return; + } + + startEdit(TranslatableString("undoableAction", "Create page from selection")); + EditPageLocks::makeIntoPage(score(), first, last); + apply(); +} + +void NotationInteraction::applyPageLock() +{ + startEdit(TranslatableString("undoableAction", "Apply page lock to selection")); + EditPageLocks::applyLockToSelection(score()); + apply(); +} + void NotationInteraction::addRemoveSystemLocks(AddRemoveSystemLockType intervalType, int interval) { interval = intervalType == AddRemoveSystemLockType::MeasuresInterval ? interval : 0; diff --git a/src/notation/internal/notationinteraction.h b/src/notation/internal/notationinteraction.h index 0640e4fc8cb7f..8da06e0b70a44 100644 --- a/src/notation/internal/notationinteraction.h +++ b/src/notation/internal/notationinteraction.h @@ -246,6 +246,11 @@ class NotationInteraction : public INotationInteraction, public muse::Contextabl void toggleScoreLock() override; void makeIntoSystem() override; void applySystemLock() override; + void moveMeasureToPrevPage() override; + void moveMeasureToNextPage() override; + void togglePageLock() override; + void makeIntoPage() override; + void applyPageLock() override; void addRemoveSystemLocks(AddRemoveSystemLockType intervalType, int interval = 0) override; bool transpose(const TransposeOptions& options) override; diff --git a/src/notation/internal/notationparts.cpp b/src/notation/internal/notationparts.cpp index 1651f4842c3df..e47bc0b486794 100644 --- a/src/notation/internal/notationparts.cpp +++ b/src/notation/internal/notationparts.cpp @@ -31,6 +31,7 @@ #include "engraving/dom/page.h" #include "engraving/editing/addremoveelement.h" #include "engraving/editing/editexcerpt.h" +#include "engraving/editing/editpagelocks.h" #include "engraving/editing/editpart.h" #include "engraving/editing/editscoreproperties.h" #include "engraving/editing/editstaff.h" @@ -246,6 +247,7 @@ void NotationParts::setPartVisible(const ID& partId, bool visible) mu::engraving::EditPart::setPartVisible(score(), part, visible); if (visible) { + EditPageLocks::removePageLocksContainingMMRests(score()); EditSystemLocks::removeSystemLocksContainingMMRests(score()); } @@ -570,6 +572,7 @@ void NotationParts::setStaffVisible(const ID& staffId, bool visible) doSetStaffConfig(staff, config); if (visible) { + EditPageLocks::removePageLocksContainingMMRests(score()); EditSystemLocks::removeSystemLocksContainingMMRests(score()); } @@ -730,6 +733,7 @@ void NotationParts::insertPart(Part* part, size_t index) startEdit(TranslatableString("undoableAction", "Add instrument")); + EditPageLocks::removePageLocksContainingMMRests(score()); EditSystemLocks::removeSystemLocksContainingMMRests(score()); doInsertPart(part, index); diff --git a/src/notation/internal/notationselection.cpp b/src/notation/internal/notationselection.cpp index 145f768829447..40d51028b4d82 100644 --- a/src/notation/internal/notationselection.cpp +++ b/src/notation/internal/notationselection.cpp @@ -160,6 +160,11 @@ std::vector NotationSelection::selectedSystems() const return score()->selection().selectedSystems(); } +std::vector NotationSelection::selectedPages() const +{ + return score()->selection().selectedPages(); +} + EngravingItem* NotationSelection::lastElementHit() const { return m_lastElementHit; diff --git a/src/notation/internal/notationselection.h b/src/notation/internal/notationselection.h index 121aec8c17bd0..0da78884f6390 100644 --- a/src/notation/internal/notationselection.h +++ b/src/notation/internal/notationselection.h @@ -30,6 +30,7 @@ namespace mu::engraving { class MeasureBase; class Score; class System; +class Page; } namespace mu::notation { @@ -62,6 +63,7 @@ class NotationSelection : public INotationSelection mu::engraving::MeasureBase* startMeasureBase() const override; mu::engraving::MeasureBase* endMeasureBase() const override; std::vector selectedSystems() const override; + std::vector selectedPages() const override; bool elementsSelected(const mu::engraving::ElementTypeSet& types) const override; diff --git a/src/notation/tests/mocks/notationinteractionmock.h b/src/notation/tests/mocks/notationinteractionmock.h index 9522e49d92105..807e535e745fb 100644 --- a/src/notation/tests/mocks/notationinteractionmock.h +++ b/src/notation/tests/mocks/notationinteractionmock.h @@ -191,6 +191,11 @@ class NotationInteractionMock : public INotationInteraction MOCK_METHOD(void, toggleScoreLock, (), (override)); MOCK_METHOD(void, makeIntoSystem, (), (override)); MOCK_METHOD(void, applySystemLock, (), (override)); + MOCK_METHOD(void, moveMeasureToPrevPage, (), (override)); + MOCK_METHOD(void, moveMeasureToNextPage, (), (override)); + MOCK_METHOD(void, togglePageLock, (), (override)); + MOCK_METHOD(void, makeIntoPage, (), (override)); + MOCK_METHOD(void, applyPageLock, (), (override)); MOCK_METHOD(void, addRemoveSystemLocks, (AddRemoveSystemLockType, int), (override)); MOCK_METHOD(bool, transpose, (const TransposeOptions&), (override)); diff --git a/src/notation/tests/mocks/notationselectionmock.h b/src/notation/tests/mocks/notationselectionmock.h index 4fc61e9d9aa9a..779e4a2caffda 100644 --- a/src/notation/tests/mocks/notationselectionmock.h +++ b/src/notation/tests/mocks/notationselectionmock.h @@ -52,6 +52,7 @@ class NotationSelectionMock : public INotationSelection MOCK_METHOD(mu::engraving::MeasureBase*, startMeasureBase, (), (const, override)); MOCK_METHOD(mu::engraving::MeasureBase*, endMeasureBase, (), (const, override)); MOCK_METHOD(std::vector, selectedSystems, (), (const, override)); + MOCK_METHOD(std::vector, selectedPages, (), (const, override)); MOCK_METHOD(bool, elementsSelected, (const mu::engraving::ElementTypeSet&), (const, override)); }; diff --git a/src/notationscene/internal/notationactioncontroller.cpp b/src/notationscene/internal/notationactioncontroller.cpp index 1921d036761b8..28ed377ea52cd 100644 --- a/src/notationscene/internal/notationactioncontroller.cpp +++ b/src/notationscene/internal/notationactioncontroller.cpp @@ -281,6 +281,11 @@ void NotationActionController::init() registerAction("toggle-system-lock", &Interaction::toggleSystemLock); registerAction("toggle-score-lock", &Interaction::toggleScoreLock); registerAction("make-into-system", &Interaction::makeIntoSystem); + registerAction("apply-page-lock", &Interaction::applyPageLock); + registerAction("move-measure-to-prev-page", &Interaction::moveMeasureToPrevPage); + registerAction("move-measure-to-next-page", &Interaction::moveMeasureToNextPage); + registerAction("toggle-page-lock", &Interaction::togglePageLock); + registerAction("make-into-page", &Interaction::makeIntoPage); registerAction("split-measure", &Interaction::splitSelectedMeasure); registerAction("join-measures", &Interaction::joinSelectedMeasures); diff --git a/src/notationscene/internal/notationuiactions.cpp b/src/notationscene/internal/notationuiactions.cpp index bfeb066164946..8d4a59aa22c2b 100644 --- a/src/notationscene/internal/notationuiactions.cpp +++ b/src/notationscene/internal/notationuiactions.cpp @@ -670,6 +670,32 @@ const UiActionList NotationUiActions::s_actions = { TranslatableString("action", "Create system from selection"), TranslatableString("action", "Create system from selection") ), + UiAction("apply-page-lock", + mu::context::UiCtxProjectOpened, + mu::context::CTX_NOTATION_FOCUSED, + TranslatableString("action", "Add/remove page lock"), + TranslatableString("action", "Add/remove page lock") + ), + UiAction("move-measure-to-prev-page", + mu::context::UiCtxProjectOpened, + mu::context::CTX_NOTATION_FOCUSED, + TranslatableString("action", "Move measure to previous page"), + TranslatableString("action", "Move measure to previous page"), + IconCode::Code::ARROW_UP + ), + UiAction("move-measure-to-next-page", + mu::context::UiCtxProjectOpened, + mu::context::CTX_NOTATION_FOCUSED, + TranslatableString("action", "Move measure to next page"), + TranslatableString("action", "Move measure to next page"), + IconCode::Code::ARROW_DOWN + ), + UiAction("make-into-page", + mu::context::UiCtxProjectOpened, + mu::context::CTX_NOTATION_FOCUSED, + TranslatableString("action", "Create page from selection"), + TranslatableString("action", "Create page from selection") + ), UiAction("section-break", mu::context::UiCtxProjectOpened, mu::context::CTX_NOTATION_OPENED, diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/MeasuresSection.qml b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/MeasuresSection.qml index 1d0db146976f3..ea9f4021704e6 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/MeasuresSection.qml +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/MeasuresSection.qml @@ -119,10 +119,10 @@ PropertiesPanelSection { text: qsTrc("propertiespanel", "Previous") toolTipTitle: qsTrc("propertiespanel", "Move measure(s) to previous system") - toolTipShortcut: root.model?.shortcutMoveMeasureUp ?? "" + toolTipShortcut: root.model?.shortcutMoveMeasureUpSystem ?? "" onClicked: { - root.model?.moveMeasureUp?.() + root.model?.moveMeasureUpSystem?.() } } @@ -133,17 +133,17 @@ PropertiesPanelSection { navigation.panel: root.navigationPanel navigation.name: "SystemDown" - navigation.row: upSystem.navigation.row + 1 + navigation.row: upPage.navigation.row + 1 orientation: Qt.Horizontal icon: IconCode.ARROW_DOWN text: qsTrc("propertiespanel", "Next") toolTipTitle: qsTrc("propertiespanel", "Move measure(s) to next system") - toolTipShortcut: root.model ? root.model.shortcutMoveMeasureDown : "" + toolTipShortcut: root.model ? root.model.shortcutMoveMeasureDownSystem : "" onClicked: { - root.model?.moveMeasureDown?.() + root.model?.moveMeasureDownSystem?.() } } } @@ -201,5 +201,121 @@ PropertiesPanelSection { root.model?.makeIntoSystem?.() } } + + Column { + width: parent.width + spacing: 8 + + StyledTextLabel { + width: parent.width + visible: root.model ? root.model.scoreIsInPageView : false + horizontalAlignment: Qt.AlignLeft + text: qsTrc("inspector", "Move to page") + } + + Row { + id: movePageLayout + + visible: root.model ? root.model.scoreIsInPageView : false + + width: parent.width + spacing: 4 + + FlatButton { + id: upPage + + width: (movePageLayout.width - movePageLayout.spacing) / 2 + + navigation.panel: root.navigationPanel + navigation.name: "PageUp" + navigation.row: deleteButton.navigation.row + 1 + + orientation: Qt.Horizontal + icon: IconCode.ARROW_UP + text: qsTrc("inspector", "Previous") + + toolTipTitle: qsTrc("inspector", "Move measure(s) to previous page") + toolTipShortcut: root.model?.shortcutMoveMeasureUpPage ?? "" + + onClicked: { + root.model?.moveMeasureUpPage?.() + } + } + + FlatButton { + id: downPage + + width: (movePageLayout.width - movePageLayout.spacing) / 2 + + navigation.panel: root.navigationPanel + navigation.name: "PageDown" + navigation.row: upPage.navigation.row + 1 + + orientation: Qt.Horizontal + icon: IconCode.ARROW_DOWN + text: qsTrc("inspector", "Next") + + toolTipTitle: qsTrc("inspector", "Move measure(s) to next page") + toolTipShortcut: root.model ? root.model.shortcutMoveMeasureDownPage : "" + + onClicked: { + root.model?.moveMeasureDownPage?.() + } + } + } + } + + FlatButton { + id: togglePageLock + visible: root.model ? root.model.scoreIsInPageView : false + + width: parent.width + + navigation.panel: root.navigationPanel + navigation.name: "PageLock" + navigation.row: downPage.navigation.row + 1 + + orientation: Qt.Horizontal + icon: root.model && root.model.allPagesAreLocked ? IconCode.LOCK_CLOSED : IconCode.LOCK_OPEN + text: root.model ? (root.model.allPagesAreLocked ? root.model.pageCount > 1 ? qsTrc("inspector", "Unlock selected pages") + : qsTrc("inspector", "Unlock selected page") + : root.model.pageCount > 1 ? qsTrc("inspector", "Lock selected pages") + : qsTrc("inspector", "Lock selected page")) + : "" + + toolTipTitle: qsTrc("inspector", "Lock/unlock selected page(s)") + toolTipDescription: qsTrc("inspector", "Keep measures on the selected page(s) together and prevent them from reflowing to the next page") + toolTipShortcut: root.model ? root.model.shortcutToggleSystemLock : "" + + accentButton: root.model ? root.model.allPagesAreLocked : false + + onClicked: { + root.model?.togglePageLock?.() + } + } + + FlatButton { + id: makeIntoOnePage + visible: root.model ? root.model.scoreIsInPageView : false + enabled: root.model ? root.model.isMakeIntoPageAvailable : false + + width: parent.width + + navigation.panel: root.navigationPanel + navigation.name: "MakePage" + navigation.row: togglePageLock.navigation.row + 1 + + orientation: Qt.Horizontal + //icon: TODO maybe + text: qsTrc("inspector", "Create page from selection") + + toolTipTitle: qsTrc("inspector", "Create page from selection") + toolTipDescription: qsTrc("inspector", "Create a page containing only the selected measure(s)") + toolTipShortcut: root.model ? root.model.shortcutMakeIntoPage : "" + + onClicked: { + root.model?.makeIntoPage?.() + } + } } } diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.cpp b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.cpp index 426facecc7f45..c52922139f658 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.cpp +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.cpp @@ -22,6 +22,7 @@ #include "measuressettingsmodel.h" #include "engraving/dom/score.h" +#include "engraving/dom/page.h" #include "translation.h" @@ -41,9 +42,12 @@ MeasuresSettingsModel::MeasuresSettingsModel(QObject* parent, const muse::modula void MeasuresSettingsModel::loadProperties() { updateAllSystemsAreLocked(); + updateAllPagesAreLocked(); updateScoreIsInPageView(); updateIsMakeIntoSystemAvailable(); + updateIsMakeIntoPageAvailable(); updateSystemCount(); + updatePageCount(); } bool MeasuresSettingsModel::shouldUpdateOnEmptyPropertyAndStyleIdSets() const @@ -56,6 +60,48 @@ void MeasuresSettingsModel::onNotationChanged(const engraving::PropertyIdSet&, c loadProperties(); } +void MeasuresSettingsModel::moveMeasureDownPage() +{ + if (!currentNotation()) { + return; + } + + currentNotation()->interaction()->moveMeasureToNextPage(); +} + +void MeasuresSettingsModel::moveMeasureUpPage() +{ + if (!currentNotation()) { + return; + } + + currentNotation()->interaction()->moveMeasureToPrevPage(); +} + +QString MeasuresSettingsModel::shortcutMoveMeasureUpPage() const +{ + return shortcutsForActionCode("move-measure-to-prev-page"); +} + +QString MeasuresSettingsModel::shortcutMoveMeasureDownPage() const +{ + return shortcutsForActionCode("move-measure-to-next-page"); +} + +QString MeasuresSettingsModel::shortcutMakeIntoPage() const +{ + return shortcutsForActionCode("make-into-page"); +} + +void MeasuresSettingsModel::makeIntoPage() +{ + if (!currentNotation()) { + return; + } + + currentNotation()->interaction()->makeIntoPage(); +} + bool MeasuresSettingsModel::isEmpty() const { INotationSelectionPtr selection = this->selection(); @@ -91,7 +137,7 @@ void MeasuresSettingsModel::deleteSelectedMeasures() currentNotation()->interaction()->removeSelectedMeasures(); } -void MeasuresSettingsModel::moveMeasureUp() +void MeasuresSettingsModel::moveMeasureUpSystem() { if (!currentNotation()) { return; @@ -100,12 +146,12 @@ void MeasuresSettingsModel::moveMeasureUp() currentNotation()->interaction()->moveMeasureToPrevSystem(); } -QString MeasuresSettingsModel::shortcutMoveMeasureUp() const +QString MeasuresSettingsModel::shortcutMoveMeasureUpSystem() const { return shortcutsForActionCode("move-measure-to-prev-system"); } -void MeasuresSettingsModel::moveMeasureDown() +void MeasuresSettingsModel::moveMeasureDownSystem() { if (!currentNotation()) { return; @@ -114,7 +160,7 @@ void MeasuresSettingsModel::moveMeasureDown() currentNotation()->interaction()->moveMeasureToNextSystem(); } -QString MeasuresSettingsModel::shortcutMoveMeasureDown() const +QString MeasuresSettingsModel::shortcutMoveMeasureDownSystem() const { return shortcutsForActionCode("move-measure-to-next-system"); } @@ -133,11 +179,30 @@ QString MeasuresSettingsModel::shortcutToggleSystemLock() const return shortcutsForActionCode("toggle-system-lock"); } +void MeasuresSettingsModel::togglePageLock() +{ + if (!currentNotation()) { + return; + } + + currentNotation()->interaction()->togglePageLock(); +} + +QString mu::inspector::MeasuresSettingsModel::shortcutTogglePageLock() const +{ + return shortcutsForActionCode("toggle-page-lock"); +} + bool MeasuresSettingsModel::allSystemsAreLocked() const { return m_allSystemsAreLocked; } +bool MeasuresSettingsModel::allPagesAreLocked() const +{ + return m_allPagesAreLocked; +} + void MeasuresSettingsModel::updateAllSystemsAreLocked() { if (isEmpty()) { @@ -160,6 +225,28 @@ void MeasuresSettingsModel::updateAllSystemsAreLocked() } } +void MeasuresSettingsModel::updateAllPagesAreLocked() +{ + if (isEmpty()) { + return; + } + + std::vector pages = selection()->selectedPages(); + + bool allLocked = true; + for (Page* page : pages) { + if (!page->isLocked()) { + allLocked = false; + break; + } + } + + if (m_allPagesAreLocked != allLocked) { + m_allPagesAreLocked = allLocked; + emit allPagesAreLockedChanged(m_allPagesAreLocked); + } +} + bool MeasuresSettingsModel::scoreIsInPageView() const { return m_scoreIsInPageView; @@ -170,11 +257,21 @@ bool MeasuresSettingsModel::isMakeIntoSystemAvailable() const return m_isMakeIntoSystemAvailable; } +bool MeasuresSettingsModel::isMakeIntoPageAvailable() const +{ + return m_isMakeIntoPageAvailable; +} + int MeasuresSettingsModel::systemCount() const { return static_cast(m_systemCount); } +int MeasuresSettingsModel::pageCount() const +{ + return static_cast(m_pageCount); +} + void MeasuresSettingsModel::updateScoreIsInPageView() { bool isInPageView = currentNotation()->viewMode() != LayoutMode::LINE; @@ -208,6 +305,16 @@ void MeasuresSettingsModel::updateIsMakeIntoSystemAvailable() } } +void MeasuresSettingsModel::updateIsMakeIntoPageAvailable() +{ + bool available = !isEmpty(); + + if (m_isMakeIntoPageAvailable != available) { + m_isMakeIntoPageAvailable = available; + emit isMakeIntoPageAvailableChanged(m_isMakeIntoPageAvailable); + } +} + void MeasuresSettingsModel::updateSystemCount() { if (isEmpty()) { @@ -221,6 +328,19 @@ void MeasuresSettingsModel::updateSystemCount() } } +void MeasuresSettingsModel::updatePageCount() +{ + if (isEmpty()) { + return; + } + + size_t count = selection()->selectedPages().size(); + if (count != m_pageCount) { + m_pageCount = count; + emit pageCountChanged(static_cast(count)); + } +} + void MeasuresSettingsModel::makeIntoSystem() { if (!currentNotation()) { diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.h b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.h index b72684c10a430..545a37a26c750 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.h +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.h @@ -33,14 +33,21 @@ class MeasuresSettingsModel : public PropertiesPanelAbstractModel QML_ELEMENT; QML_UNCREATABLE("Not creatable from QML") - Q_PROPERTY(QString shortcutMoveMeasureUp READ shortcutMoveMeasureUp CONSTANT) - Q_PROPERTY(QString shortcutMoveMeasureDown READ shortcutMoveMeasureDown CONSTANT) + Q_PROPERTY(QString shortcutMoveMeasureUpPage READ shortcutMoveMeasureUpPage CONSTANT) + Q_PROPERTY(QString shortcutMoveMeasureDownPage READ shortcutMoveMeasureDownPage CONSTANT) + Q_PROPERTY(QString shortcutMoveMeasureUpSystem READ shortcutMoveMeasureUpSystem CONSTANT) + Q_PROPERTY(QString shortcutMoveMeasureDownSystem READ shortcutMoveMeasureDownSystem CONSTANT) Q_PROPERTY(QString shortcutToggleSystemLock READ shortcutToggleSystemLock CONSTANT) + Q_PROPERTY(QString shortcutTogglePageLock READ shortcutTogglePageLock CONSTANT) Q_PROPERTY(QString shortcutMakeIntoSystem READ shortcutMakeIntoSystem CONSTANT) + Q_PROPERTY(QString shortcutMakeIntoPage READ shortcutMakeIntoPage CONSTANT) Q_PROPERTY(bool allSystemsAreLocked READ allSystemsAreLocked NOTIFY allSystemsAreLockedChanged) + Q_PROPERTY(bool allPagesAreLocked READ allPagesAreLocked NOTIFY allPagesAreLockedChanged) Q_PROPERTY(bool scoreIsInPageView READ scoreIsInPageView NOTIFY scoreIsInPageViewChanged) Q_PROPERTY(bool isMakeIntoSystemAvailable READ isMakeIntoSystemAvailable NOTIFY isMakeIntoSystemAvailableChanged) + Q_PROPERTY(bool isMakeIntoPageAvailable READ isMakeIntoPageAvailable NOTIFY isMakeIntoPageAvailableChanged) Q_PROPERTY(int systemCount READ systemCount NOTIFY systemCountChanged) + Q_PROPERTY(int pageCount READ pageCount NOTIFY pageCountChanged) public: explicit MeasuresSettingsModel(QObject* parent, const muse::modularity::ContextPtr& iocCtx, IElementRepositoryService* repository); @@ -63,43 +70,63 @@ class MeasuresSettingsModel : public PropertiesPanelAbstractModel Q_INVOKABLE void insertMeasures(int numberOfMeasures, InsertMeasuresTarget target); Q_INVOKABLE void deleteSelectedMeasures(); - Q_INVOKABLE void moveMeasureUp(); - QString shortcutMoveMeasureUp() const; + Q_INVOKABLE void moveMeasureUpPage(); + QString shortcutMoveMeasureUpPage() const; + Q_INVOKABLE void moveMeasureUpSystem(); + QString shortcutMoveMeasureUpSystem() const; - Q_INVOKABLE void moveMeasureDown(); - QString shortcutMoveMeasureDown() const; + Q_INVOKABLE void moveMeasureDownPage(); + QString shortcutMoveMeasureDownPage() const; + Q_INVOKABLE void moveMeasureDownSystem(); + QString shortcutMoveMeasureDownSystem() const; Q_INVOKABLE void toggleSystemLock(); QString shortcutToggleSystemLock() const; + Q_INVOKABLE void togglePageLock(); + QString shortcutTogglePageLock() const; bool allSystemsAreLocked() const; + bool allPagesAreLocked() const; Q_INVOKABLE void makeIntoSystem(); QString shortcutMakeIntoSystem() const; + Q_INVOKABLE void makeIntoPage(); + QString shortcutMakeIntoPage() const; bool scoreIsInPageView() const; bool isMakeIntoSystemAvailable() const; + bool isMakeIntoPageAvailable() const; int systemCount() const; + int pageCount() const; protected: void onNotationChanged(const mu::engraving::PropertyIdSet&, const mu::engraving::StyleIdSet&) override; private: void updateAllSystemsAreLocked(); + void updateAllPagesAreLocked(); void updateScoreIsInPageView(); void updateIsMakeIntoSystemAvailable(); + void updateIsMakeIntoPageAvailable(); void updateSystemCount(); + void updatePageCount(); signals: void allSystemsAreLockedChanged(bool allLocked); + void allPagesAreLockedChanged(bool allLocked); void scoreIsInPageViewChanged(bool isInPageView); void isMakeIntoSystemAvailableChanged(bool isMakeIntoSystemAvailable); + void isMakeIntoPageAvailableChanged(bool isMakeIntoPageAvailable); void systemCountChanged(int count); + void pageCountChanged(int count); private: bool m_allSystemsAreLocked = false; + bool m_allPagesAreLocked = false; bool m_scoreIsInPageView = false; bool m_isMakeIntoSystemAvailable = false; + bool m_isMakeIntoPageAvailable = false; size_t m_systemCount = 0; + size_t m_pageCount = 0; }; } From 736258a37d252820a28dc2a9b2e8b0d99759835f Mon Sep 17 00:00:00 2001 From: James Mizen Date: Wed, 6 May 2026 07:37:20 +0100 Subject: [PATCH 03/10] Remove unnecessary mmrest checks --- src/engraving/dom/measurebase.cpp | 12 ++++-------- src/engraving/dom/select.cpp | 3 +-- src/engraving/editing/editpagelocks.cpp | 21 +++++++-------------- src/engraving/editing/editsystemlocks.cpp | 21 +++++++-------------- 4 files changed, 19 insertions(+), 38 deletions(-) diff --git a/src/engraving/dom/measurebase.cpp b/src/engraving/dom/measurebase.cpp index 90efb237f9efe..ff1a57532c197 100644 --- a/src/engraving/dom/measurebase.cpp +++ b/src/engraving/dom/measurebase.cpp @@ -100,14 +100,13 @@ MeasureBase::~MeasureBase() System* MeasureBase::prevNonVBoxSystem() const { - bool mmRests = score()->style().styleB(Sid::createMultiMeasureRests); System* curSystem = system(); IF_ASSERT_FAILED(curSystem) { return nullptr; } System* prevSystem = curSystem; - for (const MeasureBase* mb = this; mb && prevSystem == curSystem; mb = mmRests ? mb->prevMM() : mb->prev()) { + for (const MeasureBase* mb = this; mb && prevSystem == curSystem; mb = mb->prevMM()) { if (mb->isMeasure() || mb->isHBox()) { prevSystem = mb->system(); } else { @@ -120,14 +119,13 @@ System* MeasureBase::prevNonVBoxSystem() const System* MeasureBase::nextNonVBoxSystem() const { - bool mmRests = score()->style().styleB(Sid::createMultiMeasureRests); System* curSystem = system(); IF_ASSERT_FAILED(curSystem) { return nullptr; } System* nextSystem = curSystem; - for (const MeasureBase* mb = this; mb && nextSystem == curSystem; mb = mmRests ? mb->nextMM() : mb->next()) { + for (const MeasureBase* mb = this; mb && nextSystem == curSystem; mb = mb->nextMM()) { if (mb->isMeasure() || mb->isHBox()) { nextSystem = mb->system(); } else { @@ -145,14 +143,13 @@ Page* MeasureBase::page() const Page* MeasureBase::prevPage() const { - bool mmRests = score()->style().styleB(Sid::createMultiMeasureRests); Page* curPage = system() ? system()->page() : nullptr; IF_ASSERT_FAILED(curPage) { return nullptr; } Page* prevPage = curPage; - for (const MeasureBase* mb = this; mb && prevPage == curPage; mb = mmRests ? mb->prevMM() : mb->prev()) { + for (const MeasureBase* mb = this; mb && prevPage == curPage; mb = mb->prevMM()) { prevPage = mb->system()->page(); } @@ -161,14 +158,13 @@ Page* MeasureBase::prevPage() const Page* MeasureBase::nextPage() const { - bool mmRests = score()->style().styleB(Sid::createMultiMeasureRests); Page* curPage = system() ? system()->page() : nullptr; IF_ASSERT_FAILED(curPage) { return nullptr; } Page* nextPage = curPage; - for (const MeasureBase* mb = this; mb && nextPage == curPage; mb = mmRests ? mb->nextMM() : mb->next()) { + for (const MeasureBase* mb = this; mb && nextPage == curPage; mb = mb->nextMM()) { nextPage = mb->system()->page(); } diff --git a/src/engraving/dom/select.cpp b/src/engraving/dom/select.cpp index 76484371abfae..8a1fe2f34d46a 100644 --- a/src/engraving/dom/select.cpp +++ b/src/engraving/dom/select.cpp @@ -414,9 +414,8 @@ std::vector Selection::selectedSystems() const return {}; } - bool mmrests = score()->style().styleB(Sid::createMultiMeasureRests); std::vector systems; - for (const MeasureBase* mb = startMB; mb && mb->isBeforeOrEqual(endMB); mb = mmrests ? mb->nextMM() : mb->next()) { + for (const MeasureBase* mb = startMB; mb && mb->isBeforeOrEqual(endMB); mb = mb->nextMM()) { System* sys = mb->system(); if ((mb->isMeasure() || mb->isHBox()) && (systems.empty() || sys != systems.back())) { systems.push_back(sys); diff --git a/src/engraving/editing/editpagelocks.cpp b/src/engraving/editing/editpagelocks.cpp index 19f46f303de37..df78253ac8434 100644 --- a/src/engraving/editing/editpagelocks.cpp +++ b/src/engraving/editing/editpagelocks.cpp @@ -183,12 +183,10 @@ void EditPageLocks::toggleScoreLock(Score* score) void EditPageLocks::addRemovePageLocks(Score* score, int interval, bool lock) { - const bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); - MeasureBase* startMeasure = score->selection().startMeasureBase(); MeasureBase* endMeasure = score->selection().endMeasureBase(); if (!endMeasure) { - endMeasure = mmrests ? score->lastMeasureMM() : score->lastMeasure(); + endMeasure = score->lastMeasureMM(); } if (!startMeasure || !endMeasure) { @@ -221,7 +219,7 @@ void EditPageLocks::addRemovePageLocks(Score* score, int interval, bool lock) int count = 0; MeasureBase* lockStart = nullptr; - for (MeasureBase* mb = startMeasure; mb; mb = mmrests ? mb->nextMM() : mb->next()) { + for (MeasureBase* mb = startMeasure; mb; mb = mb->nextMM()) { if (count == 0) { lockStart = mb; } @@ -239,15 +237,13 @@ void EditPageLocks::addRemovePageLocks(Score* score, int interval, bool lock) void EditPageLocks::makeIntoPage(Score* score, MeasureBase* first, MeasureBase* last) { - bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); - const RangeLock* lockContainingfirst = score->pageLocks()->lockContaining(first); const RangeLock* lockContaininglast = score->pageLocks()->lockContaining(last); if (lockContainingfirst) { undoRemovePageLock(score, lockContainingfirst); if (lockContainingfirst->startMB()->isBefore(first)) { - MeasureBase* oneBeforeFirst = mmrests ? first->prevMM() : first->prev(); + MeasureBase* oneBeforeFirst = first->prevMM(); RangeLock* newLockBefore = new RangeLock(lockContainingfirst->startMB(), oneBeforeFirst); undoAddPageLock(score, newLockBefore); } @@ -258,7 +254,7 @@ void EditPageLocks::makeIntoPage(Score* score, MeasureBase* first, MeasureBase* undoRemovePageLock(score, lockContaininglast); } if (last->isBefore(lockContaininglast->endMB())) { - MeasureBase* oneAfterLast = mmrests ? last->nextMM() : last->next(); + MeasureBase* oneAfterLast = last->nextMM(); RangeLock* newLockAfter = new RangeLock(oneAfterLast, lockContaininglast->endMB()); undoAddPageLock(score, newLockAfter); } @@ -294,8 +290,7 @@ void EditPageLocks::moveMeasureToPrevPage(Score* score, MeasureBase* m) if (curPageLock) { undoRemovePageLock(score, curPageLock); if (curPageLock->endMB() != m) { - const bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); - MeasureBase* nextMB = mmrests ? m->nextMM() : m->next(); + MeasureBase* nextMB = m->nextMM(); RangeLock* newLockOnCurPage = new RangeLock(nextMB, curPageLock->endMB()); undoAddPageLock(score, newLockOnCurPage); } @@ -317,8 +312,7 @@ void EditPageLocks::moveMeasureToNextPage(Score* score, MeasureBase* m) } if (!refMeasureIsStartOfPage) { - bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); - MeasureBase* prevMeas = mmrests ? m->prevMM() : m->prev(); + MeasureBase* prevMeas = m->prevMM(); RangeLock* pageLock = new RangeLock(startMeas, prevMeas); undoAddPageLock(score, pageLock); } @@ -396,8 +390,7 @@ void EditPageLocks::removePageLocksOnAddLayoutBreak(Score* score, LayoutBreakTyp void EditPageLocks::removeLayoutBreaksOnAddPageLock(Score* score, const RangeLock* lock) { - bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); - for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mmrests ? mb->nextMM() : mb->next()) { + for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mb->nextMM()) { mb->undoSetBreak(false, LayoutBreakType::LINE); mb->undoSetBreak(false, LayoutBreakType::NOBREAK); if (mb != lock->endMB()) { diff --git a/src/engraving/editing/editsystemlocks.cpp b/src/engraving/editing/editsystemlocks.cpp index 2515decb65a00..ea082c27fa742 100644 --- a/src/engraving/editing/editsystemlocks.cpp +++ b/src/engraving/editing/editsystemlocks.cpp @@ -190,12 +190,10 @@ void EditSystemLocks::toggleScoreLock(Score* score) void EditSystemLocks::addRemoveSystemLocks(Score* score, int interval, bool lock) { - const bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); - MeasureBase* startMeasure = score->selection().startMeasureBase(); MeasureBase* endMeasure = score->selection().endMeasureBase(); if (!endMeasure) { - endMeasure = mmrests ? score->lastMeasureMM() : score->lastMeasure(); + endMeasure = score->lastMeasureMM(); } if (!startMeasure || !endMeasure) { @@ -228,7 +226,7 @@ void EditSystemLocks::addRemoveSystemLocks(Score* score, int interval, bool lock int count = 0; MeasureBase* lockStart = nullptr; - for (MeasureBase* mb = startMeasure; mb; mb = mmrests ? mb->nextMM() : mb->next()) { + for (MeasureBase* mb = startMeasure; mb; mb = mb->nextMM()) { if (count == 0) { lockStart = mb; } @@ -246,15 +244,13 @@ void EditSystemLocks::addRemoveSystemLocks(Score* score, int interval, bool lock void EditSystemLocks::makeIntoSystem(Score* score, MeasureBase* first, MeasureBase* last) { - bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); - const RangeLock* lockContainingfirst = score->systemLocks()->lockContaining(first); const RangeLock* lockContaininglast = score->systemLocks()->lockContaining(last); if (lockContainingfirst) { undoRemoveSystemLock(score, lockContainingfirst); if (lockContainingfirst->startMB()->isBefore(first)) { - MeasureBase* oneBeforeFirst = mmrests ? first->prevMM() : first->prev(); + MeasureBase* oneBeforeFirst = first->prevMM(); RangeLock* newLockBefore = new RangeLock(lockContainingfirst->startMB(), oneBeforeFirst); undoAddSystemLock(score, newLockBefore); } @@ -265,7 +261,7 @@ void EditSystemLocks::makeIntoSystem(Score* score, MeasureBase* first, MeasureBa undoRemoveSystemLock(score, lockContaininglast); } if (last->isBefore(lockContaininglast->endMB())) { - MeasureBase* oneAfterLast = mmrests ? last->nextMM() : last->next(); + MeasureBase* oneAfterLast = last->nextMM(); RangeLock* newLockAfter = new RangeLock(oneAfterLast, lockContaininglast->endMB()); undoAddSystemLock(score, newLockAfter); } @@ -301,8 +297,7 @@ void EditSystemLocks::moveMeasureToPrevSystem(Score* score, MeasureBase* m) if (curSystemLock) { undoRemoveSystemLock(score, curSystemLock); if (curSystemLock->endMB() != m) { - const bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); - MeasureBase* nextMB = mmrests ? m->nextMM() : m->next(); + MeasureBase* nextMB = m->nextMM(); RangeLock* newLockOnCurSystem = new RangeLock(nextMB, curSystemLock->endMB()); undoAddSystemLock(score, newLockOnCurSystem); } @@ -324,8 +319,7 @@ void EditSystemLocks::moveMeasureToNextSystem(Score* score, MeasureBase* m) } if (!refMeasureIsStartOfSystem) { - bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); - MeasureBase* prevMeas = mmrests ? m->prevMM() : m->prev(); + MeasureBase* prevMeas = m->prevMM(); RangeLock* sysLock = new RangeLock(startMeas, prevMeas); undoAddSystemLock(score, sysLock); } @@ -403,8 +397,7 @@ void EditSystemLocks::removeSystemLocksOnAddLayoutBreak(Score* score, LayoutBrea void EditSystemLocks::removeLayoutBreaksOnAddSystemLock(Score* score, const RangeLock* lock) { - bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); - for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mmrests ? mb->nextMM() : mb->next()) { + for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mb->nextMM()) { mb->undoSetBreak(false, LayoutBreakType::LINE); mb->undoSetBreak(false, LayoutBreakType::NOBREAK); if (mb != lock->endMB()) { From 5cd205824bbd0fe384ddda650a4c8808b0058039 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Mon, 8 Jun 2026 16:42:49 +0100 Subject: [PATCH 04/10] UI update --- src/notation/inotationinteraction.h | 4 +- src/notation/internal/notationinteraction.cpp | 20 +- src/notation/internal/notationinteraction.h | 4 +- .../tests/mocks/notationinteractionmock.h | 4 +- .../internal/notationactioncontroller.cpp | 4 +- .../MuseScore/PropertiesPanel/CMakeLists.txt | 5 + .../PropertiesPanelSectionDelegate.qml | 17 + .../measures/MeasuresFlowSection.qml | 170 +++++++++ .../measures/MeasuresSection.qml | 233 +------------ .../measures/measuressettingsmodel.cpp | 271 --------------- .../measures/measuressettingsmodel.h | 72 ---- .../propertiespanelabstractmodel.cpp | 1 + .../propertiespanelabstractmodel.h | 1 + .../propertiespanellistmodel.cpp | 4 + .../systemlayout/SystemLayoutSection.qml | 158 +++++++++ .../systemlayoutsettingsmodel.cpp | 327 ++++++++++++++++++ .../systemlayout/systemlayoutsettingsmodel.h | 121 +++++++ 17 files changed, 825 insertions(+), 591 deletions(-) create mode 100644 src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/MeasuresFlowSection.qml create mode 100644 src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/SystemLayoutSection.qml create mode 100644 src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/systemlayoutsettingsmodel.cpp create mode 100644 src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/systemlayoutsettingsmodel.h diff --git a/src/notation/inotationinteraction.h b/src/notation/inotationinteraction.h index 780c6de186575..751e08484399a 100644 --- a/src/notation/inotationinteraction.h +++ b/src/notation/inotationinteraction.h @@ -231,8 +231,8 @@ class INotationInteraction virtual void toggleScoreLock() = 0; virtual void makeIntoSystem() = 0; virtual void applySystemLock() = 0; - virtual void moveMeasureToPrevPage() = 0; - virtual void moveMeasureToNextPage() = 0; + virtual void moveSystemToPrevPage() = 0; + virtual void moveSystemToNextPage() = 0; virtual void togglePageLock() = 0; virtual void makeIntoPage() = 0; virtual void applyPageLock() = 0; diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index 9c6243ebbeea7..9546aeecf5356 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -6120,25 +6120,29 @@ void NotationInteraction::applySystemLock() apply(); } -void NotationInteraction::moveMeasureToPrevPage() +void NotationInteraction::moveSystemToPrevPage() { MeasureBase* m = score()->selection().endMeasureBase(); - if (!m) { + System* sys = m ? m->system() : nullptr; + if (!sys) { return; } - startEdit(TranslatableString("undoableAction", "Move measure to previous page")); - EditPageLocks::moveMeasureToPrevPage(score(), m); + MeasureBase* lastMb = sys->last(); + startEdit(TranslatableString("undoableAction", "Move system to previous page")); + EditPageLocks::moveMeasureToPrevPage(score(), lastMb); apply(); } -void NotationInteraction::moveMeasureToNextPage() +void NotationInteraction::moveSystemToNextPage() { MeasureBase* m = score()->selection().startMeasureBase(); - if (!m) { + System* sys = m ? m->system() : nullptr; + if (!sys) { return; } - startEdit(TranslatableString("undoableAction", "Move measure to next page")); - EditPageLocks::moveMeasureToNextPage(score(), m); + MeasureBase* firstMb = sys->first(); + startEdit(TranslatableString("undoableAction", "Move system to next page")); + EditPageLocks::moveMeasureToNextPage(score(), firstMb); apply(); } diff --git a/src/notation/internal/notationinteraction.h b/src/notation/internal/notationinteraction.h index 8da06e0b70a44..357700cf9c18a 100644 --- a/src/notation/internal/notationinteraction.h +++ b/src/notation/internal/notationinteraction.h @@ -246,8 +246,8 @@ class NotationInteraction : public INotationInteraction, public muse::Contextabl void toggleScoreLock() override; void makeIntoSystem() override; void applySystemLock() override; - void moveMeasureToPrevPage() override; - void moveMeasureToNextPage() override; + void moveSystemToPrevPage() override; + void moveSystemToNextPage() override; void togglePageLock() override; void makeIntoPage() override; void applyPageLock() override; diff --git a/src/notation/tests/mocks/notationinteractionmock.h b/src/notation/tests/mocks/notationinteractionmock.h index 807e535e745fb..c54afa5e37396 100644 --- a/src/notation/tests/mocks/notationinteractionmock.h +++ b/src/notation/tests/mocks/notationinteractionmock.h @@ -191,8 +191,8 @@ class NotationInteractionMock : public INotationInteraction MOCK_METHOD(void, toggleScoreLock, (), (override)); MOCK_METHOD(void, makeIntoSystem, (), (override)); MOCK_METHOD(void, applySystemLock, (), (override)); - MOCK_METHOD(void, moveMeasureToPrevPage, (), (override)); - MOCK_METHOD(void, moveMeasureToNextPage, (), (override)); + MOCK_METHOD(void, moveSystemToPrevPage, (), (override)); + MOCK_METHOD(void, moveSystemToNextPage, (), (override)); MOCK_METHOD(void, togglePageLock, (), (override)); MOCK_METHOD(void, makeIntoPage, (), (override)); MOCK_METHOD(void, applyPageLock, (), (override)); diff --git a/src/notationscene/internal/notationactioncontroller.cpp b/src/notationscene/internal/notationactioncontroller.cpp index 28ed377ea52cd..872d31a1e08d5 100644 --- a/src/notationscene/internal/notationactioncontroller.cpp +++ b/src/notationscene/internal/notationactioncontroller.cpp @@ -282,8 +282,8 @@ void NotationActionController::init() registerAction("toggle-score-lock", &Interaction::toggleScoreLock); registerAction("make-into-system", &Interaction::makeIntoSystem); registerAction("apply-page-lock", &Interaction::applyPageLock); - registerAction("move-measure-to-prev-page", &Interaction::moveMeasureToPrevPage); - registerAction("move-measure-to-next-page", &Interaction::moveMeasureToNextPage); + registerAction("move-measure-to-prev-page", &Interaction::moveSystemToPrevPage); + registerAction("move-measure-to-next-page", &Interaction::moveSystemToNextPage); registerAction("toggle-page-lock", &Interaction::togglePageLock); registerAction("make-into-page", &Interaction::makeIntoPage); diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/CMakeLists.txt b/src/propertiespanel/qml/MuseScore/PropertiesPanel/CMakeLists.txt index 9d23fb3f23987..990de56334ead 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/CMakeLists.txt +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/CMakeLists.txt @@ -58,6 +58,8 @@ qt_add_qml_module(propertiespanel_qml propertiespanelpopupcontrollermodel.h measures/measuressettingsmodel.cpp measures/measuressettingsmodel.h + systemlayout/systemlayoutsettingsmodel.cpp + systemlayout/systemlayoutsettingsmodel.h notation/accidentals/accidentalsettingsmodel.cpp notation/accidentals/accidentalsettingsmodel.h notation/ambituses/ambitussettingsmodel.cpp @@ -275,6 +277,8 @@ qt_add_qml_module(propertiespanel_qml PropertiesPanelSectionDelegate.qml measures/InsertMeasuresPopup.qml measures/MeasuresSection.qml + measures/MeasuresFlowSection.qml + systemlayout/SystemLayoutSection.qml notation/accidentals/AccidentalSettings.qml notation/ambituses/AmbitusSettings.qml notation/articulations/ArticulationSettings.qml @@ -437,4 +441,5 @@ target_include_directories(propertiespanel_qml PRIVATE text text/textstylepopup types + systemlayout ) diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/PropertiesPanelSectionDelegate.qml b/src/propertiespanel/qml/MuseScore/PropertiesPanel/PropertiesPanelSectionDelegate.qml index c378d0d812374..335369b5088fd 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/PropertiesPanelSectionDelegate.qml +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/PropertiesPanelSectionDelegate.qml @@ -30,6 +30,7 @@ import MuseScore.PropertiesPanel import "common" import "general" import "measures" +import "systemlayout" import "emptystaves" import "notation" import "text" @@ -66,6 +67,7 @@ ExpandableBlank { switch (root.sectionModel.sectionType) { case PropertiesPanelAbstractModel.SECTION_GENERAL: return generalSection case PropertiesPanelAbstractModel.SECTION_MEASURES: return measuresSection + case PropertiesPanelAbstractModel.SECTION_SYSTEM_LAYOUT: return systemLayoutSection case PropertiesPanelAbstractModel.SECTION_EMPTY_STAVES: return emptyStavesSection case PropertiesPanelAbstractModel.SECTION_TEXT: return textSection case PropertiesPanelAbstractModel.SECTION_NOTATION: @@ -112,6 +114,21 @@ ExpandableBlank { } } + Component { + id: systemLayoutSection + + SystemLayoutSection { + model: root.sectionModel as SystemLayoutSettingsModel + navigationPanel: root.navigationPanel + navigationRowStart: root.navigation.row + 1 + anchorItem: root.anchorItem + + onEnsureContentVisibleRequested: function(invisibleContentHeight) { + root.ensureContentVisibleRequested(-invisibleContentHeight) + } + } + } + Component { id: emptyStavesSection diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/MeasuresFlowSection.qml b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/MeasuresFlowSection.qml new file mode 100644 index 0000000000000..9c78288fb36e6 --- /dev/null +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/MeasuresFlowSection.qml @@ -0,0 +1,170 @@ +/* + * 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 ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts + +import Muse.UiComponents +import Muse.Ui +import MuseScore.PropertiesPanel + +Column { + id: root + width: parent.width + spacing: 12 + + property var navigationPanel + property int navigationRowStart: 0 + property bool isLocked: false + property int itemCount: 0 + + property string lockLabelSingular: "" + property string lockLabelPlural: "" + property string unlockLabelSingular: "" + property string unlockLabelPlural: "" + + property string lockToolTipTitle: "" + property string lockToolTipDescription: "" + property string lockToolTipShortcut: "" + property string lockNavigationName: "" + + property string moveSectionTitle: "" + property string movePrevText: "" + property string moveNextText: "" + + property string movePreviousToolTipTitle: "" + property string moveNextToolTipTitle: "" + property string movePreviousShortcut: "" + property string moveNextShortcut: "" + + property string previousNavigationName: "" + property string nextNavigationName: "" + + property string makeIntoText: "" + property string makeIntoToolTipTitle: "" + property string makeIntoToolTipDescription: "" + property string makeIntoShortcut: "" + property string makeIntoNavigationName: "" + + property bool isMakeIntoAvailable: false + + property var controller + + FlatButton { + id: toggleLockButton + visible: root.visible + width: parent.width + + navigation.panel: root.navigationPanel + navigation.name: root.lockNavigationName + navigation.row: root.navigationRowStart + 1 + + orientation: Qt.Horizontal + icon: root.isLocked ? IconCode.LOCK_CLOSED : IconCode.LOCK_OPEN + text: root.isLocked + ? (root.itemCount > 1 ? root.unlockLabelPlural : root.unlockLabelSingular) + : (root.itemCount > 1 ? root.lockLabelPlural : root.lockLabelSingular) + + toolTipTitle: root.lockToolTipTitle + toolTipDescription: root.lockToolTipDescription + toolTipShortcut: root.lockToolTipShortcut + + accentButton: root.isLocked + + onClicked: root.controller.toggleLock() + } + + Column { + width: parent.width + spacing: 8 + + StyledTextLabel { + width: parent.width + visible: root.visible + horizontalAlignment: Qt.AlignLeft + text: root.moveSectionTitle + } + + Row { + id: moveButtons + visible: root.visible + width: parent.width + spacing: 4 + + FlatButton { + width: (moveButtons.width - moveButtons.spacing) / 2 + + navigation.panel: root.navigationPanel + navigation.name: root.previousNavigationName + navigation.row: root.navigationRowStart + 2 + + orientation: Qt.Horizontal + icon: IconCode.ARROW_UP + text: root.movePrevText + + toolTipTitle: root.movePreviousToolTipTitle + toolTipShortcut: root.movePreviousShortcut + + onClicked: root.controller.movePrevious() + } + + FlatButton { + id: moveNextButton + width: (moveButtons.width - moveButtons.spacing) / 2 + + navigation.panel: root.navigationPanel + navigation.name: root.nextNavigationName + navigation.row: root.navigationRowStart + 3 + + orientation: Qt.Horizontal + icon: IconCode.ARROW_DOWN + text: root.moveNextText + + toolTipTitle: root.moveNextToolTipTitle + toolTipShortcut: root.moveNextShortcut + + onClicked: root.controller.moveNext() + } + } + } + + FlatButton { + id: makeIntoButton + visible: root.visible + enabled: root.isMakeIntoAvailable + width: parent.width + + navigation.panel: root.navigationPanel + navigation.name: root.makeIntoNavigationName + navigation.row: root.navigationRowStart + 4 + + orientation: Qt.Horizontal + text: root.makeIntoText + + toolTipTitle: root.makeIntoToolTipTitle + toolTipDescription: root.makeIntoToolTipDescription + toolTipShortcut: root.makeIntoShortcut + + onClicked: root.controller.makeInto() + } +} diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/MeasuresSection.qml b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/MeasuresSection.qml index ea9f4021704e6..69fc51ed4cf2c 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/MeasuresSection.qml +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/MeasuresSection.qml @@ -29,6 +29,7 @@ import Muse.Ui import MuseScore.PropertiesPanel import "../common" +import "." PropertiesPanelSection { id: root @@ -85,237 +86,5 @@ PropertiesPanelSection { } } } - - Column { - width: parent.width - spacing: 8 - - StyledTextLabel { - width: parent.width - visible: root.model ? root.model.scoreIsInPageView : false - horizontalAlignment: Qt.AlignLeft - text: qsTrc("propertiespanel", "Move to system") - } - - Row { - id: moveSystemLayout - - visible: root.model ? root.model.scoreIsInPageView : false - - width: parent.width - spacing: 4 - - FlatButton { - id: upSystem - - width: (moveSystemLayout.width - moveSystemLayout.spacing) / 2 - - navigation.panel: root.navigationPanel - navigation.name: "SystemUp" - navigation.row: deleteButton.navigation.row + 1 - - orientation: Qt.Horizontal - icon: IconCode.ARROW_UP - text: qsTrc("propertiespanel", "Previous") - - toolTipTitle: qsTrc("propertiespanel", "Move measure(s) to previous system") - toolTipShortcut: root.model?.shortcutMoveMeasureUpSystem ?? "" - - onClicked: { - root.model?.moveMeasureUpSystem?.() - } - } - - FlatButton { - id: downSystem - - width: (moveSystemLayout.width - moveSystemLayout.spacing) / 2 - - navigation.panel: root.navigationPanel - navigation.name: "SystemDown" - navigation.row: upPage.navigation.row + 1 - - orientation: Qt.Horizontal - icon: IconCode.ARROW_DOWN - text: qsTrc("propertiespanel", "Next") - - toolTipTitle: qsTrc("propertiespanel", "Move measure(s) to next system") - toolTipShortcut: root.model ? root.model.shortcutMoveMeasureDownSystem : "" - - onClicked: { - root.model?.moveMeasureDownSystem?.() - } - } - } - } - - FlatButton { - id: toggleSystemLock - visible: root.model ? root.model.scoreIsInPageView : false - - width: parent.width - - navigation.panel: root.navigationPanel - navigation.name: "SystemLock" - navigation.row: downSystem.navigation.row + 1 - - orientation: Qt.Horizontal - icon: root.model && root.model.allSystemsAreLocked ? IconCode.LOCK_CLOSED : IconCode.LOCK_OPEN - text: root.model ? (root.model.allSystemsAreLocked ? root.model.systemCount > 1 ? qsTrc("propertiespanel", "Unlock selected systems") - : qsTrc("propertiespanel", "Unlock selected system") - : root.model.systemCount > 1 ? qsTrc("propertiespanel", "Lock selected systems") - : qsTrc("propertiespanel", "Lock selected system")) - : "" - - toolTipTitle: qsTrc("propertiespanel", "Lock/unlock selected system(s)") - toolTipDescription: qsTrc("propertiespanel", "Keep measures on the selected system(s) together and prevent them from reflowing to the next system") - toolTipShortcut: root.model ? root.model.shortcutToggleSystemLock : "" - - accentButton: root.model ? root.model.allSystemsAreLocked : false - - onClicked: { - root.model?.toggleSystemLock?.() - } - } - - FlatButton { - id: makeIntoOneSystem - visible: root.model ? root.model.scoreIsInPageView : false - enabled: root.model ? root.model.isMakeIntoSystemAvailable : false - - width: parent.width - - navigation.panel: root.navigationPanel - navigation.name: "MakeSystem" - navigation.row: toggleSystemLock.navigation.row + 1 - - orientation: Qt.Horizontal - //icon: TODO maybe - text: qsTrc("propertiespanel", "Create system from selection") - - toolTipTitle: qsTrc("propertiespanel", "Create system from selection") - toolTipDescription: qsTrc("propertiespanel", "Create a system containing only the selected measure(s)") - toolTipShortcut: root.model ? root.model.shortcutMakeIntoSystem : "" - - onClicked: { - root.model?.makeIntoSystem?.() - } - } - - Column { - width: parent.width - spacing: 8 - - StyledTextLabel { - width: parent.width - visible: root.model ? root.model.scoreIsInPageView : false - horizontalAlignment: Qt.AlignLeft - text: qsTrc("inspector", "Move to page") - } - - Row { - id: movePageLayout - - visible: root.model ? root.model.scoreIsInPageView : false - - width: parent.width - spacing: 4 - - FlatButton { - id: upPage - - width: (movePageLayout.width - movePageLayout.spacing) / 2 - - navigation.panel: root.navigationPanel - navigation.name: "PageUp" - navigation.row: deleteButton.navigation.row + 1 - - orientation: Qt.Horizontal - icon: IconCode.ARROW_UP - text: qsTrc("inspector", "Previous") - - toolTipTitle: qsTrc("inspector", "Move measure(s) to previous page") - toolTipShortcut: root.model?.shortcutMoveMeasureUpPage ?? "" - - onClicked: { - root.model?.moveMeasureUpPage?.() - } - } - - FlatButton { - id: downPage - - width: (movePageLayout.width - movePageLayout.spacing) / 2 - - navigation.panel: root.navigationPanel - navigation.name: "PageDown" - navigation.row: upPage.navigation.row + 1 - - orientation: Qt.Horizontal - icon: IconCode.ARROW_DOWN - text: qsTrc("inspector", "Next") - - toolTipTitle: qsTrc("inspector", "Move measure(s) to next page") - toolTipShortcut: root.model ? root.model.shortcutMoveMeasureDownPage : "" - - onClicked: { - root.model?.moveMeasureDownPage?.() - } - } - } - } - - FlatButton { - id: togglePageLock - visible: root.model ? root.model.scoreIsInPageView : false - - width: parent.width - - navigation.panel: root.navigationPanel - navigation.name: "PageLock" - navigation.row: downPage.navigation.row + 1 - - orientation: Qt.Horizontal - icon: root.model && root.model.allPagesAreLocked ? IconCode.LOCK_CLOSED : IconCode.LOCK_OPEN - text: root.model ? (root.model.allPagesAreLocked ? root.model.pageCount > 1 ? qsTrc("inspector", "Unlock selected pages") - : qsTrc("inspector", "Unlock selected page") - : root.model.pageCount > 1 ? qsTrc("inspector", "Lock selected pages") - : qsTrc("inspector", "Lock selected page")) - : "" - - toolTipTitle: qsTrc("inspector", "Lock/unlock selected page(s)") - toolTipDescription: qsTrc("inspector", "Keep measures on the selected page(s) together and prevent them from reflowing to the next page") - toolTipShortcut: root.model ? root.model.shortcutToggleSystemLock : "" - - accentButton: root.model ? root.model.allPagesAreLocked : false - - onClicked: { - root.model?.togglePageLock?.() - } - } - - FlatButton { - id: makeIntoOnePage - visible: root.model ? root.model.scoreIsInPageView : false - enabled: root.model ? root.model.isMakeIntoPageAvailable : false - - width: parent.width - - navigation.panel: root.navigationPanel - navigation.name: "MakePage" - navigation.row: togglePageLock.navigation.row + 1 - - orientation: Qt.Horizontal - //icon: TODO maybe - text: qsTrc("inspector", "Create page from selection") - - toolTipTitle: qsTrc("inspector", "Create page from selection") - toolTipDescription: qsTrc("inspector", "Create a page containing only the selected measure(s)") - toolTipShortcut: root.model ? root.model.shortcutMakeIntoPage : "" - - onClicked: { - root.model?.makeIntoPage?.() - } - } } } diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.cpp b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.cpp index c52922139f658..667df969580be 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.cpp +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.cpp @@ -21,9 +21,6 @@ */ #include "measuressettingsmodel.h" -#include "engraving/dom/score.h" -#include "engraving/dom/page.h" - #include "translation.h" using namespace mu::propertiespanel; @@ -41,13 +38,6 @@ MeasuresSettingsModel::MeasuresSettingsModel(QObject* parent, const muse::modula void MeasuresSettingsModel::loadProperties() { - updateAllSystemsAreLocked(); - updateAllPagesAreLocked(); - updateScoreIsInPageView(); - updateIsMakeIntoSystemAvailable(); - updateIsMakeIntoPageAvailable(); - updateSystemCount(); - updatePageCount(); } bool MeasuresSettingsModel::shouldUpdateOnEmptyPropertyAndStyleIdSets() const @@ -57,49 +47,6 @@ bool MeasuresSettingsModel::shouldUpdateOnEmptyPropertyAndStyleIdSets() const void MeasuresSettingsModel::onNotationChanged(const engraving::PropertyIdSet&, const engraving::StyleIdSet&) { - loadProperties(); -} - -void MeasuresSettingsModel::moveMeasureDownPage() -{ - if (!currentNotation()) { - return; - } - - currentNotation()->interaction()->moveMeasureToNextPage(); -} - -void MeasuresSettingsModel::moveMeasureUpPage() -{ - if (!currentNotation()) { - return; - } - - currentNotation()->interaction()->moveMeasureToPrevPage(); -} - -QString MeasuresSettingsModel::shortcutMoveMeasureUpPage() const -{ - return shortcutsForActionCode("move-measure-to-prev-page"); -} - -QString MeasuresSettingsModel::shortcutMoveMeasureDownPage() const -{ - return shortcutsForActionCode("move-measure-to-next-page"); -} - -QString MeasuresSettingsModel::shortcutMakeIntoPage() const -{ - return shortcutsForActionCode("make-into-page"); -} - -void MeasuresSettingsModel::makeIntoPage() -{ - if (!currentNotation()) { - return; - } - - currentNotation()->interaction()->makeIntoPage(); } bool MeasuresSettingsModel::isEmpty() const @@ -136,221 +83,3 @@ void MeasuresSettingsModel::deleteSelectedMeasures() currentNotation()->interaction()->removeSelectedMeasures(); } - -void MeasuresSettingsModel::moveMeasureUpSystem() -{ - if (!currentNotation()) { - return; - } - - currentNotation()->interaction()->moveMeasureToPrevSystem(); -} - -QString MeasuresSettingsModel::shortcutMoveMeasureUpSystem() const -{ - return shortcutsForActionCode("move-measure-to-prev-system"); -} - -void MeasuresSettingsModel::moveMeasureDownSystem() -{ - if (!currentNotation()) { - return; - } - - currentNotation()->interaction()->moveMeasureToNextSystem(); -} - -QString MeasuresSettingsModel::shortcutMoveMeasureDownSystem() const -{ - return shortcutsForActionCode("move-measure-to-next-system"); -} - -void MeasuresSettingsModel::toggleSystemLock() -{ - if (!currentNotation()) { - return; - } - - currentNotation()->interaction()->toggleSystemLock(); -} - -QString MeasuresSettingsModel::shortcutToggleSystemLock() const -{ - return shortcutsForActionCode("toggle-system-lock"); -} - -void MeasuresSettingsModel::togglePageLock() -{ - if (!currentNotation()) { - return; - } - - currentNotation()->interaction()->togglePageLock(); -} - -QString mu::inspector::MeasuresSettingsModel::shortcutTogglePageLock() const -{ - return shortcutsForActionCode("toggle-page-lock"); -} - -bool MeasuresSettingsModel::allSystemsAreLocked() const -{ - return m_allSystemsAreLocked; -} - -bool MeasuresSettingsModel::allPagesAreLocked() const -{ - return m_allPagesAreLocked; -} - -void MeasuresSettingsModel::updateAllSystemsAreLocked() -{ - if (isEmpty()) { - return; - } - - std::vector systems = selection()->selectedSystems(); - - bool allLocked = true; - for (System* system : systems) { - if (!system->isLocked()) { - allLocked = false; - break; - } - } - - if (m_allSystemsAreLocked != allLocked) { - m_allSystemsAreLocked = allLocked; - emit allSystemsAreLockedChanged(m_allSystemsAreLocked); - } -} - -void MeasuresSettingsModel::updateAllPagesAreLocked() -{ - if (isEmpty()) { - return; - } - - std::vector pages = selection()->selectedPages(); - - bool allLocked = true; - for (Page* page : pages) { - if (!page->isLocked()) { - allLocked = false; - break; - } - } - - if (m_allPagesAreLocked != allLocked) { - m_allPagesAreLocked = allLocked; - emit allPagesAreLockedChanged(m_allPagesAreLocked); - } -} - -bool MeasuresSettingsModel::scoreIsInPageView() const -{ - return m_scoreIsInPageView; -} - -bool MeasuresSettingsModel::isMakeIntoSystemAvailable() const -{ - return m_isMakeIntoSystemAvailable; -} - -bool MeasuresSettingsModel::isMakeIntoPageAvailable() const -{ - return m_isMakeIntoPageAvailable; -} - -int MeasuresSettingsModel::systemCount() const -{ - return static_cast(m_systemCount); -} - -int MeasuresSettingsModel::pageCount() const -{ - return static_cast(m_pageCount); -} - -void MeasuresSettingsModel::updateScoreIsInPageView() -{ - bool isInPageView = currentNotation()->viewMode() != LayoutMode::LINE; - - if (m_scoreIsInPageView != isInPageView) { - m_scoreIsInPageView = isInPageView; - emit scoreIsInPageViewChanged(m_scoreIsInPageView); - } -} - -void MeasuresSettingsModel::updateIsMakeIntoSystemAvailable() -{ - if (isEmpty()) { - return; - } - - const MeasureBase* startMB = selection()->startMeasureBase(); - const MeasureBase* endMB = selection()->endMeasureBase(); - if (!startMB || !endMB) { - return; - } - - bool available = true; - if (startMB->isStartOfSystemLock() && endMB->isEndOfSystemLock() && startMB->systemLock() == endMB->systemLock()) { - available = false; - } - - if (m_isMakeIntoSystemAvailable != available) { - m_isMakeIntoSystemAvailable = available; - emit isMakeIntoSystemAvailableChanged(m_isMakeIntoSystemAvailable); - } -} - -void MeasuresSettingsModel::updateIsMakeIntoPageAvailable() -{ - bool available = !isEmpty(); - - if (m_isMakeIntoPageAvailable != available) { - m_isMakeIntoPageAvailable = available; - emit isMakeIntoPageAvailableChanged(m_isMakeIntoPageAvailable); - } -} - -void MeasuresSettingsModel::updateSystemCount() -{ - if (isEmpty()) { - return; - } - - size_t count = selection()->selectedSystems().size(); - if (count != m_systemCount) { - m_systemCount = count; - emit systemCountChanged(static_cast(count)); - } -} - -void MeasuresSettingsModel::updatePageCount() -{ - if (isEmpty()) { - return; - } - - size_t count = selection()->selectedPages().size(); - if (count != m_pageCount) { - m_pageCount = count; - emit pageCountChanged(static_cast(count)); - } -} - -void MeasuresSettingsModel::makeIntoSystem() -{ - if (!currentNotation()) { - return; - } - - currentNotation()->interaction()->makeIntoSystem(); -} - -QString MeasuresSettingsModel::shortcutMakeIntoSystem() const -{ - return shortcutsForActionCode("make-into-system"); -} diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.h b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.h index 545a37a26c750..6c239df0150c8 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.h +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.h @@ -33,22 +33,6 @@ class MeasuresSettingsModel : public PropertiesPanelAbstractModel QML_ELEMENT; QML_UNCREATABLE("Not creatable from QML") - Q_PROPERTY(QString shortcutMoveMeasureUpPage READ shortcutMoveMeasureUpPage CONSTANT) - Q_PROPERTY(QString shortcutMoveMeasureDownPage READ shortcutMoveMeasureDownPage CONSTANT) - Q_PROPERTY(QString shortcutMoveMeasureUpSystem READ shortcutMoveMeasureUpSystem CONSTANT) - Q_PROPERTY(QString shortcutMoveMeasureDownSystem READ shortcutMoveMeasureDownSystem CONSTANT) - Q_PROPERTY(QString shortcutToggleSystemLock READ shortcutToggleSystemLock CONSTANT) - Q_PROPERTY(QString shortcutTogglePageLock READ shortcutTogglePageLock CONSTANT) - Q_PROPERTY(QString shortcutMakeIntoSystem READ shortcutMakeIntoSystem CONSTANT) - Q_PROPERTY(QString shortcutMakeIntoPage READ shortcutMakeIntoPage CONSTANT) - Q_PROPERTY(bool allSystemsAreLocked READ allSystemsAreLocked NOTIFY allSystemsAreLockedChanged) - Q_PROPERTY(bool allPagesAreLocked READ allPagesAreLocked NOTIFY allPagesAreLockedChanged) - Q_PROPERTY(bool scoreIsInPageView READ scoreIsInPageView NOTIFY scoreIsInPageViewChanged) - Q_PROPERTY(bool isMakeIntoSystemAvailable READ isMakeIntoSystemAvailable NOTIFY isMakeIntoSystemAvailableChanged) - Q_PROPERTY(bool isMakeIntoPageAvailable READ isMakeIntoPageAvailable NOTIFY isMakeIntoPageAvailableChanged) - Q_PROPERTY(int systemCount READ systemCount NOTIFY systemCountChanged) - Q_PROPERTY(int pageCount READ pageCount NOTIFY pageCountChanged) - public: explicit MeasuresSettingsModel(QObject* parent, const muse::modularity::ContextPtr& iocCtx, IElementRepositoryService* repository); @@ -70,63 +54,7 @@ class MeasuresSettingsModel : public PropertiesPanelAbstractModel Q_INVOKABLE void insertMeasures(int numberOfMeasures, InsertMeasuresTarget target); Q_INVOKABLE void deleteSelectedMeasures(); - Q_INVOKABLE void moveMeasureUpPage(); - QString shortcutMoveMeasureUpPage() const; - Q_INVOKABLE void moveMeasureUpSystem(); - QString shortcutMoveMeasureUpSystem() const; - - Q_INVOKABLE void moveMeasureDownPage(); - QString shortcutMoveMeasureDownPage() const; - Q_INVOKABLE void moveMeasureDownSystem(); - QString shortcutMoveMeasureDownSystem() const; - - Q_INVOKABLE void toggleSystemLock(); - QString shortcutToggleSystemLock() const; - Q_INVOKABLE void togglePageLock(); - QString shortcutTogglePageLock() const; - bool allSystemsAreLocked() const; - bool allPagesAreLocked() const; - - Q_INVOKABLE void makeIntoSystem(); - QString shortcutMakeIntoSystem() const; - Q_INVOKABLE void makeIntoPage(); - QString shortcutMakeIntoPage() const; - - bool scoreIsInPageView() const; - bool isMakeIntoSystemAvailable() const; - bool isMakeIntoPageAvailable() const; - - int systemCount() const; - int pageCount() const; - protected: void onNotationChanged(const mu::engraving::PropertyIdSet&, const mu::engraving::StyleIdSet&) override; - -private: - void updateAllSystemsAreLocked(); - void updateAllPagesAreLocked(); - void updateScoreIsInPageView(); - void updateIsMakeIntoSystemAvailable(); - void updateIsMakeIntoPageAvailable(); - void updateSystemCount(); - void updatePageCount(); - -signals: - void allSystemsAreLockedChanged(bool allLocked); - void allPagesAreLockedChanged(bool allLocked); - void scoreIsInPageViewChanged(bool isInPageView); - void isMakeIntoSystemAvailableChanged(bool isMakeIntoSystemAvailable); - void isMakeIntoPageAvailableChanged(bool isMakeIntoPageAvailable); - void systemCountChanged(int count); - void pageCountChanged(int count); - -private: - bool m_allSystemsAreLocked = false; - bool m_allPagesAreLocked = false; - bool m_scoreIsInPageView = false; - bool m_isMakeIntoSystemAvailable = false; - bool m_isMakeIntoPageAvailable = false; - size_t m_systemCount = 0; - size_t m_pageCount = 0; }; } diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanelabstractmodel.cpp b/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanelabstractmodel.cpp index bbd4f649b0cb7..948c7e1e98548 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanelabstractmodel.cpp +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanelabstractmodel.cpp @@ -400,6 +400,7 @@ PropertiesPanelSectionTypeSet PropertiesPanelAbstractModel::sectionTypesByElemen if (isRange) { types << PropertiesPanelSectionType::SECTION_MEASURES; + types << PropertiesPanelSectionType::SECTION_SYSTEM_LAYOUT; types << PropertiesPanelSectionType::SECTION_EMPTY_STAVES; } diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanelabstractmodel.h b/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanelabstractmodel.h index def2ac58d6542..98e32b7621198 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanelabstractmodel.h +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanelabstractmodel.h @@ -74,6 +74,7 @@ class PropertiesPanelAbstractModel : public QObject, public muse::async::Asyncab SECTION_UNDEFINED = -1, SECTION_GENERAL, SECTION_MEASURES, + SECTION_SYSTEM_LAYOUT, SECTION_EMPTY_STAVES, SECTION_NOTATION, SECTION_TEXT, diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanellistmodel.cpp b/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanellistmodel.cpp index dd820c46ea162..57807d3ea2d8c 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanellistmodel.cpp +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanellistmodel.cpp @@ -22,6 +22,7 @@ #include "propertiespanellistmodel.h" #include "general/generalsettingsmodel.h" +#include "systemlayout/systemlayoutsettingsmodel.h" #include "measures/measuressettingsmodel.h" #include "emptystaves/emptystavesvisiblitysettingsmodel.h" #include "notation/notationsettingsproxymodel.h" @@ -225,6 +226,9 @@ void PropertiesPanelListModel::createModelsBySectionType(const PropertiesPanelSe case PropertiesPanelSectionType::SECTION_MEASURES: newModel = new MeasuresSettingsModel(this, iocContext(), m_repository.get()); break; + case PropertiesPanelSectionType::SECTION_SYSTEM_LAYOUT: + newModel = new SystemLayoutSettingsModel(this, iocContext(), m_repository.get()); + break; case PropertiesPanelSectionType::SECTION_EMPTY_STAVES: newModel = new EmptyStavesVisibilitySettingsModel(this, iocContext(), m_repository.get()); break; diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/SystemLayoutSection.qml b/src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/SystemLayoutSection.qml new file mode 100644 index 0000000000000..0dbc561e0e51a --- /dev/null +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/SystemLayoutSection.qml @@ -0,0 +1,158 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2021 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 ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts + +import Muse.UiComponents +import Muse.Ui +import MuseScore.PropertiesPanel + +import "../common" +import "../measures" +import "." + +PropertiesPanelSection { + id: root + + required property SystemLayoutSettingsModel model + + implicitHeight: contentColumn.implicitHeight + + Column { + id: contentColumn + + width: parent.width + spacing: 12 + + PropertiesPanelTabBar { + id: tabBar + + PropertiesPanelTabButton { + text: qsTrc("inspector", "System") + + navigation.name: "SystemTab" + navigation.panel: root.navigationPanel + navigation.row: root.navigationRowStart + 1 + } + + PropertiesPanelTabButton { + text: qsTrc("inspector", "Page") + + navigation.name: "PageTab" + navigation.panel: root.navigationPanel + navigation.row: root.navigationRowStart + 2 + } + } + + StackLayout { + id: lockMenu + width: parent.width + currentIndex: tabBar.currentIndex + + height: itemAt(currentIndex).implicitHeight + + MeasuresFlowSection { + id: systemSection + visible: root.model ? root.model.scoreIsInPageView : false + navigationPanel: root.navigationPanel + navigationRowStart: root.navigationRowStart + isLocked: root.model ? root.model.allSystemsAreLocked : false + itemCount: root.model ? root.model.systemCount : 0 + lockLabelSingular: qsTrc("inspector", "Lock selected system") + lockLabelPlural: qsTrc("inspector", "Lock selected systems") + unlockLabelSingular: qsTrc("inspector", "Unlock selected system") + unlockLabelPlural: qsTrc("inspector", "Unlock selected systems") + lockToolTipTitle: qsTrc("inspector", "Lock/unlock selected system(s)") + lockToolTipDescription: qsTrc("inspector", "Keep measures on the selected system(s) together and prevent them from reflowing to the next system") + lockToolTipShortcut: root.model ? root.model.shortcutToggleSystemLock : "" + lockNavigationName: "SystemLock" + + moveSectionTitle: qsTrc("inspector", "Move measures across systems") + movePrevText: qsTrc("inspector", "Previous") + moveNextText: qsTrc("inspector", "Next") + movePreviousToolTipTitle: qsTrc("inspector", "Move measure(s) to previous system") + moveNextToolTipTitle: qsTrc("inspector", "Move measure(s) to next system") + movePreviousShortcut: root.model?.shortcutMoveMeasureUpSystem ?? "" + moveNextShortcut: root.model ? root.model.shortcutMoveMeasureDownSystem : "" + previousNavigationName: "SystemUp" + nextNavigationName: "SystemDown" + + makeIntoText: qsTrc("inspector", "New system from selection") + makeIntoToolTipTitle: qsTrc("inspector", "New system from selection") + makeIntoToolTipDescription: qsTrc("inspector", "Create a system containing only the selected measure(s)") + makeIntoShortcut: root.model ? root.model.shortcutMakeIntoSystem : "" + makeIntoNavigationName: "MakeSystem" + isMakeIntoAvailable: root.model ? root.model.isMakeIntoSystemAvailable : false + + controller: ({ + movePrevious: () => root.model.moveMeasureUpSystem(), + moveNext: () => root.model.moveMeasureDownSystem(), + toggleLock: () => root.model.toggleSystemLock(), + makeInto: () => root.model.makeIntoSystem() + }) + } + + MeasuresFlowSection { + id: pageSection + visible: root.model ? root.model.scoreIsInPageView : false + navigationPanel: root.navigationPanel + navigationRowStart: root.navigationRowStart + isLocked: root.model ? root.model.allPagesAreLocked : false + itemCount: root.model ? root.model.pageCount : 0 + lockLabelSingular: qsTrc("inspector", "Lock selected page") + lockLabelPlural: qsTrc("inspector", "Lock selected pages") + unlockLabelSingular: qsTrc("inspector", "Unlock selected page") + unlockLabelPlural: qsTrc("inspector", "Unlock selected pages") + lockToolTipTitle: qsTrc("inspector", "Lock/unlock selected page(s)") + lockToolTipDescription: qsTrc("inspector", "Keep measures on the selected page(s) together and prevent them from reflowing to the next page") + lockToolTipShortcut: root.model ? root.model.shortcutTogglePageLock : "" + lockNavigationName: "PageLock" + + moveSectionTitle: qsTrc("inspector", "Move systems across pages") + movePrevText: qsTrc("inspector", "Previous") + moveNextText: qsTrc("inspector", "Next") + movePreviousToolTipTitle: qsTrc("inspector", "Move system(s) to previous page") + moveNextToolTipTitle: qsTrc("inspector", "Move system(s) to next page") + movePreviousShortcut: root.model?.shortcutMoveSystemUpPage ?? "" + moveNextShortcut: root.model ? root.model.shortcutMoveSystemDownPage : "" + previousNavigationName: "PageUp" + nextNavigationName: "PageDown" + + makeIntoText: qsTrc("inspector", "New page from selection") + makeIntoToolTipTitle: qsTrc("inspector", "New page from selection") + makeIntoToolTipDescription: qsTrc("inspector", "Create a page containing only the selected measure(s)") + makeIntoShortcut: root.model ? root.model.shortcutMakeIntoPage : "" + makeIntoNavigationName: "MakePage" + isMakeIntoAvailable: root.model ? root.model.isMakeIntoPageAvailable : false + + controller: ({ + movePrevious: () => root.model.moveSystemUpPage(), + moveNext: () => root.model.moveSystemDownPage(), + toggleLock: () => root.model.togglePageLock(), + makeInto: () => root.model.makeIntoPage() + }) + } + } + } +} diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/systemlayoutsettingsmodel.cpp b/src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/systemlayoutsettingsmodel.cpp new file mode 100644 index 0000000000000..535153049968d --- /dev/null +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/systemlayoutsettingsmodel.cpp @@ -0,0 +1,327 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2021 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 "systemlayoutsettingsmodel.h" + +#include "engraving/dom/score.h" +#include "engraving/dom/page.h" + +#include "translation.h" + +using namespace mu::propertiespanel; +using namespace mu::notation; +using namespace mu::engraving; + +SystemLayoutSettingsModel::SystemLayoutSettingsModel(QObject* parent, const muse::modularity::ContextPtr& iocCtx, + IElementRepositoryService* repository) + : PropertiesPanelAbstractModel(parent, iocCtx, repository) +{ + setSectionType(PropertiesPanelSectionType::SECTION_SYSTEM_LAYOUT); + setTitle(muse::qtrc("inspector", "System layout")); +} + +void SystemLayoutSettingsModel::loadProperties() +{ + updateAllSystemsAreLocked(); + updateAllPagesAreLocked(); + updateScoreIsInPageView(); + updateIsMakeIntoSystemAvailable(); + updateIsMakeIntoPageAvailable(); + updateSystemCount(); + updatePageCount(); +} + +bool SystemLayoutSettingsModel::shouldUpdateOnEmptyPropertyAndStyleIdSets() const +{ + return true; +} + +void SystemLayoutSettingsModel::onNotationChanged(const engraving::PropertyIdSet&, const engraving::StyleIdSet&) +{ + loadProperties(); +} + +void SystemLayoutSettingsModel::moveSystemDownPage() +{ + if (!currentNotation()) { + return; + } + + currentNotation()->interaction()->moveSystemToNextPage(); +} + +void SystemLayoutSettingsModel::moveSystemUpPage() +{ + if (!currentNotation()) { + return; + } + + currentNotation()->interaction()->moveSystemToPrevPage(); +} + +QString SystemLayoutSettingsModel::shortcutMoveSystemUpPage() const +{ + return shortcutsForActionCode("move-system-to-prev-page"); +} + +QString SystemLayoutSettingsModel::shortcutMoveSystemDownPage() const +{ + return shortcutsForActionCode("move-system-to-next-page"); +} + +QString SystemLayoutSettingsModel::shortcutMakeIntoPage() const +{ + return shortcutsForActionCode("make-into-page"); +} + +void SystemLayoutSettingsModel::makeIntoPage() +{ + if (!currentNotation()) { + return; + } + + currentNotation()->interaction()->makeIntoPage(); +} + +bool SystemLayoutSettingsModel::isEmpty() const +{ + INotationSelectionPtr selection = this->selection(); + return !selection || !selection->isRange(); +} + +void SystemLayoutSettingsModel::moveMeasureUpSystem() +{ + if (!currentNotation()) { + return; + } + + currentNotation()->interaction()->moveMeasureToPrevSystem(); +} + +QString SystemLayoutSettingsModel::shortcutMoveMeasureUpSystem() const +{ + return shortcutsForActionCode("move-measure-to-prev-system"); +} + +void SystemLayoutSettingsModel::moveMeasureDownSystem() +{ + if (!currentNotation()) { + return; + } + + currentNotation()->interaction()->moveMeasureToNextSystem(); +} + +QString SystemLayoutSettingsModel::shortcutMoveMeasureDownSystem() const +{ + return shortcutsForActionCode("move-measure-to-next-system"); +} + +void SystemLayoutSettingsModel::toggleSystemLock() +{ + if (!currentNotation()) { + return; + } + + currentNotation()->interaction()->toggleSystemLock(); +} + +QString SystemLayoutSettingsModel::shortcutToggleSystemLock() const +{ + return shortcutsForActionCode("toggle-system-lock"); +} + +void SystemLayoutSettingsModel::togglePageLock() +{ + if (!currentNotation()) { + return; + } + + currentNotation()->interaction()->togglePageLock(); +} + +QString SystemLayoutSettingsModel::shortcutTogglePageLock() const +{ + return shortcutsForActionCode("toggle-page-lock"); +} + +bool SystemLayoutSettingsModel::allSystemsAreLocked() const +{ + return m_allSystemsAreLocked; +} + +bool SystemLayoutSettingsModel::allPagesAreLocked() const +{ + return m_allPagesAreLocked; +} + +void SystemLayoutSettingsModel::updateAllSystemsAreLocked() +{ + if (isEmpty()) { + return; + } + + std::vector systems = selection()->selectedSystems(); + + bool allLocked = true; + for (System* system : systems) { + if (!system->isLocked()) { + allLocked = false; + break; + } + } + + if (m_allSystemsAreLocked != allLocked) { + m_allSystemsAreLocked = allLocked; + emit allSystemsAreLockedChanged(m_allSystemsAreLocked); + } +} + +void SystemLayoutSettingsModel::updateAllPagesAreLocked() +{ + if (isEmpty()) { + return; + } + + std::vector pages = selection()->selectedPages(); + + bool allLocked = true; + for (Page* page : pages) { + if (!page->isLocked()) { + allLocked = false; + break; + } + } + + if (m_allPagesAreLocked != allLocked) { + m_allPagesAreLocked = allLocked; + emit allPagesAreLockedChanged(m_allPagesAreLocked); + } +} + +bool SystemLayoutSettingsModel::scoreIsInPageView() const +{ + return m_scoreIsInPageView; +} + +bool SystemLayoutSettingsModel::isMakeIntoSystemAvailable() const +{ + return m_isMakeIntoSystemAvailable; +} + +bool SystemLayoutSettingsModel::isMakeIntoPageAvailable() const +{ + return m_isMakeIntoPageAvailable; +} + +int SystemLayoutSettingsModel::systemCount() const +{ + return static_cast(m_systemCount); +} + +int SystemLayoutSettingsModel::pageCount() const +{ + return static_cast(m_pageCount); +} + +void SystemLayoutSettingsModel::updateScoreIsInPageView() +{ + bool isInPageView = currentNotation()->viewMode() != LayoutMode::LINE; + + if (m_scoreIsInPageView != isInPageView) { + m_scoreIsInPageView = isInPageView; + emit scoreIsInPageViewChanged(m_scoreIsInPageView); + } +} + +void SystemLayoutSettingsModel::updateIsMakeIntoSystemAvailable() +{ + if (isEmpty()) { + return; + } + + const MeasureBase* startMB = selection()->startMeasureBase(); + const MeasureBase* endMB = selection()->endMeasureBase(); + if (!startMB || !endMB) { + return; + } + + bool available = true; + if (startMB->isStartOfSystemLock() && endMB->isEndOfSystemLock() && startMB->systemLock() == endMB->systemLock()) { + available = false; + } + + if (m_isMakeIntoSystemAvailable != available) { + m_isMakeIntoSystemAvailable = available; + emit isMakeIntoSystemAvailableChanged(m_isMakeIntoSystemAvailable); + } +} + +void SystemLayoutSettingsModel::updateIsMakeIntoPageAvailable() +{ + bool available = !isEmpty(); + + if (m_isMakeIntoPageAvailable != available) { + m_isMakeIntoPageAvailable = available; + emit isMakeIntoPageAvailableChanged(m_isMakeIntoPageAvailable); + } +} + +void SystemLayoutSettingsModel::updateSystemCount() +{ + if (isEmpty()) { + return; + } + + size_t count = selection()->selectedSystems().size(); + if (count != m_systemCount) { + m_systemCount = count; + emit systemCountChanged(static_cast(count)); + } +} + +void SystemLayoutSettingsModel::updatePageCount() +{ + if (isEmpty()) { + return; + } + + size_t count = selection()->selectedPages().size(); + if (count != m_pageCount) { + m_pageCount = count; + emit pageCountChanged(static_cast(count)); + } +} + +void SystemLayoutSettingsModel::makeIntoSystem() +{ + if (!currentNotation()) { + return; + } + + currentNotation()->interaction()->makeIntoSystem(); +} + +QString SystemLayoutSettingsModel::shortcutMakeIntoSystem() const +{ + return shortcutsForActionCode("make-into-system"); +} diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/systemlayoutsettingsmodel.h b/src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/systemlayoutsettingsmodel.h new file mode 100644 index 0000000000000..f15edaff343d3 --- /dev/null +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/systemlayoutsettingsmodel.h @@ -0,0 +1,121 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2021 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 + +#include "propertiespanelabstractmodel.h" + +namespace mu::propertiespanel { +class SystemLayoutSettingsModel : public PropertiesPanelAbstractModel +{ + Q_OBJECT + QML_ELEMENT; + QML_UNCREATABLE("Not creatable from QML") + + Q_PROPERTY(QString shortcutMoveSystemUpPage READ shortcutMoveSystemUpPage CONSTANT) + Q_PROPERTY(QString shortcutMoveSystemDownPage READ shortcutMoveSystemDownPage CONSTANT) + Q_PROPERTY(QString shortcutMoveMeasureUpSystem READ shortcutMoveMeasureUpSystem CONSTANT) + Q_PROPERTY(QString shortcutMoveMeasureDownSystem READ shortcutMoveMeasureDownSystem CONSTANT) + Q_PROPERTY(QString shortcutToggleSystemLock READ shortcutToggleSystemLock CONSTANT) + Q_PROPERTY(QString shortcutTogglePageLock READ shortcutTogglePageLock CONSTANT) + Q_PROPERTY(QString shortcutMakeIntoSystem READ shortcutMakeIntoSystem CONSTANT) + Q_PROPERTY(QString shortcutMakeIntoPage READ shortcutMakeIntoPage CONSTANT) + Q_PROPERTY(bool allSystemsAreLocked READ allSystemsAreLocked NOTIFY allSystemsAreLockedChanged) + Q_PROPERTY(bool allPagesAreLocked READ allPagesAreLocked NOTIFY allPagesAreLockedChanged) + Q_PROPERTY(bool scoreIsInPageView READ scoreIsInPageView NOTIFY scoreIsInPageViewChanged) + Q_PROPERTY(bool isMakeIntoSystemAvailable READ isMakeIntoSystemAvailable NOTIFY isMakeIntoSystemAvailableChanged) + Q_PROPERTY(bool isMakeIntoPageAvailable READ isMakeIntoPageAvailable NOTIFY isMakeIntoPageAvailableChanged) + Q_PROPERTY(int systemCount READ systemCount NOTIFY systemCountChanged) + Q_PROPERTY(int pageCount READ pageCount NOTIFY pageCountChanged) + +public: + explicit SystemLayoutSettingsModel(QObject* parent, const muse::modularity::ContextPtr& iocCtx, IElementRepositoryService* repository); + + void createProperties() override { } + void loadProperties() override; + void requestElements() override { } + + bool isEmpty() const override; + bool shouldUpdateOnEmptyPropertyAndStyleIdSets() const override; + + Q_INVOKABLE void moveSystemUpPage(); + QString shortcutMoveSystemUpPage() const; + Q_INVOKABLE void moveMeasureUpSystem(); + QString shortcutMoveMeasureUpSystem() const; + + Q_INVOKABLE void moveSystemDownPage(); + QString shortcutMoveSystemDownPage() const; + Q_INVOKABLE void moveMeasureDownSystem(); + QString shortcutMoveMeasureDownSystem() const; + + Q_INVOKABLE void toggleSystemLock(); + QString shortcutToggleSystemLock() const; + Q_INVOKABLE void togglePageLock(); + QString shortcutTogglePageLock() const; + bool allSystemsAreLocked() const; + bool allPagesAreLocked() const; + + Q_INVOKABLE void makeIntoSystem(); + QString shortcutMakeIntoSystem() const; + Q_INVOKABLE void makeIntoPage(); + QString shortcutMakeIntoPage() const; + + bool scoreIsInPageView() const; + bool isMakeIntoSystemAvailable() const; + bool isMakeIntoPageAvailable() const; + + int systemCount() const; + int pageCount() const; + +protected: + void onNotationChanged(const mu::engraving::PropertyIdSet&, const mu::engraving::StyleIdSet&) override; + +private: + void updateAllSystemsAreLocked(); + void updateAllPagesAreLocked(); + void updateScoreIsInPageView(); + void updateIsMakeIntoSystemAvailable(); + void updateIsMakeIntoPageAvailable(); + void updateSystemCount(); + void updatePageCount(); + +signals: + void allSystemsAreLockedChanged(bool allLocked); + void allPagesAreLockedChanged(bool allLocked); + void scoreIsInPageViewChanged(bool isInPageView); + void isMakeIntoSystemAvailableChanged(bool isMakeIntoSystemAvailable); + void isMakeIntoPageAvailableChanged(bool isMakeIntoPageAvailable); + void systemCountChanged(int count); + void pageCountChanged(int count); + +private: + bool m_allSystemsAreLocked = false; + bool m_allPagesAreLocked = false; + bool m_scoreIsInPageView = false; + bool m_isMakeIntoSystemAvailable = false; + bool m_isMakeIntoPageAvailable = false; + size_t m_systemCount = 0; + size_t m_pageCount = 0; +}; +} From 0cc86f2e5a166392b5157e9cc47afcdc89ecf9a0 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Tue, 2 Jun 2026 15:11:14 +0100 Subject: [PATCH 05/10] Add page lock indicator --- src/engraving/api/v1/apitypes.h | 1 + src/engraving/dom/engravingobject.h | 5 +- src/engraving/dom/factory.cpp | 10 ++++ src/engraving/dom/factory.h | 3 ++ src/engraving/dom/navigate.cpp | 12 +++++ src/engraving/dom/rangelock.cpp | 26 +++++++++ src/engraving/dom/rangelock.h | 26 +++++++++ src/engraving/dom/select.cpp | 8 +-- src/engraving/dom/system.cpp | 18 +++++++ src/engraving/dom/system.h | 4 ++ src/engraving/editing/edit.cpp | 7 +++ src/engraving/editing/editpagelocks.cpp | 12 ++--- src/engraving/rendering/score/pagelayout.cpp | 1 + .../rendering/score/systemlayout.cpp | 18 +++++++ src/engraving/rendering/score/systemlayout.h | 1 + src/engraving/rendering/score/tdraw.cpp | 32 ++++++++++- src/engraving/rendering/score/tdraw.h | 2 + src/engraving/rendering/score/tlayout.cpp | 53 +++++++++++++++++++ src/engraving/rendering/score/tlayout.h | 1 + src/engraving/types/types.h | 1 + src/engraving/types/typesconv.cpp | 3 ++ 21 files changed, 232 insertions(+), 12 deletions(-) diff --git a/src/engraving/api/v1/apitypes.h b/src/engraving/api/v1/apitypes.h index 9fb8290cda134..46fa95798ca6e 100644 --- a/src/engraving/api/v1/apitypes.h +++ b/src/engraving/api/v1/apitypes.h @@ -422,6 +422,7 @@ enum class ElementType { TAB_DURATION_SYMBOL = int(mu::engraving::ElementType::TAB_DURATION_SYMBOL), FSYMBOL = int(mu::engraving::ElementType::FSYMBOL), PAGE = int(mu::engraving::ElementType::PAGE), + PAGE_LOCK_INDICATOR = int(mu::engraving::ElementType::PAGE_LOCK_INDICATOR), HAIRPIN = int(mu::engraving::ElementType::HAIRPIN), OTTAVA = int(mu::engraving::ElementType::OTTAVA), PEDAL = int(mu::engraving::ElementType::PEDAL), diff --git a/src/engraving/dom/engravingobject.h b/src/engraving/dom/engravingobject.h index ae02eaadd36ec..30d23c0a2e108 100644 --- a/src/engraving/dom/engravingobject.h +++ b/src/engraving/dom/engravingobject.h @@ -127,6 +127,7 @@ class Ornament; class Ottava; class OttavaSegment; class Page; +class PageLockIndicator; class PalmMute; class PalmMuteSegment; class Parenthesis; @@ -437,6 +438,7 @@ class EngravingObject CONVERT(FretDiagram, FRET_DIAGRAM) CONVERT(HarpPedalDiagram, HARP_DIAGRAM) CONVERT(Page, PAGE) + CONVERT(PageLockIndicator, PAGE_LOCK_INDICATOR) CONVERT(Text, TEXT) CONVERT(MeasureNumber, MEASURE_NUMBER) CONVERT(MMRestRange, MMREST_RANGE) @@ -584,7 +586,7 @@ class EngravingObject return isArticulationFamily() || isFermata(); } - bool isIndicatorIcon() const { return isSystemLockIndicator() || isStaffVisibilityIndicator(); } + bool isIndicatorIcon() const { return isSystemLockIndicator() || isPageLockIndicator() || isStaffVisibilityIndicator(); } }; //--------------------------------------------------- @@ -766,6 +768,7 @@ CONVERT(ChordLine) CONVERT(FretDiagram) CONVERT(HarpPedalDiagram) CONVERT(Page) +CONVERT(PageLockIndicator) CONVERT(SystemText) CONVERT(BracketItem) CONVERT(Staff) diff --git a/src/engraving/dom/factory.cpp b/src/engraving/dom/factory.cpp index 6e02e2c03a0b7..d26ae23ee5ed4 100644 --- a/src/engraving/dom/factory.cpp +++ b/src/engraving/dom/factory.cpp @@ -300,6 +300,7 @@ EngravingItem* Factory::doCreateItem(ElementType type, EngravingItem* parent) case ElementType::FIGURED_BASS_ITEM: case ElementType::DUMMY: case ElementType::SYSTEM_LOCK_INDICATOR: + case ElementType::PAGE_LOCK_INDICATOR: case ElementType::HAMMER_ON_PULL_OFF_SEGMENT: case ElementType::HAMMER_ON_PULL_OFF_TEXT: case ElementType::TAPPING_HALF_SLUR: @@ -475,6 +476,15 @@ MAKE_ITEM_IMPL(NoteLine, Note); CREATE_ITEM_IMPL(Page, RootItem, isAccessibleEnabled) +PageLockIndicator* Factory::createPageLockIndicator(System * parent, const RangeLock * lock, bool isAccessibleEnabled) +{ + PageLockIndicator* sli = new PageLockIndicator(parent, lock); + sli->setAccessibleEnabled(isAccessibleEnabled); + return sli; +} + +COPY_ITEM_IMPL(PageLockIndicator) + CREATE_ITEM_IMPL(PartialTie, Note, isAccessibleEnabled) COPY_ITEM_IMPL(PartialTie) diff --git a/src/engraving/dom/factory.h b/src/engraving/dom/factory.h index 568808a49106a..76d199bed96b6 100644 --- a/src/engraving/dom/factory.h +++ b/src/engraving/dom/factory.h @@ -148,6 +148,9 @@ class Factory static Page* createPage(RootItem* parent, bool isAccessibleEnabled = true); + static PageLockIndicator* createPageLockIndicator(System* parent, const RangeLock* lock, bool isAccessibleEnabled = true); + static PageLockIndicator* copyPageLockIndicator(const PageLockIndicator& src); + static Parenthesis* createParenthesis(EngravingItem* parent, bool isAccessibleEnabled = true); static Parenthesis* copyParenthesis(const Parenthesis& src); diff --git a/src/engraving/dom/navigate.cpp b/src/engraving/dom/navigate.cpp index 44d0ee9064c44..695b3d2da10ae 100644 --- a/src/engraving/dom/navigate.cpp +++ b/src/engraving/dom/navigate.cpp @@ -902,6 +902,12 @@ EngravingItem* Score::nextElement() e = toSystemLockIndicator(e)->systemLock()->endMB(); continue; } + case ElementType::PAGE_LOCK_INDICATOR: + { + staffId = 0; + e = toPageLockIndicator(e)->pageLock()->endMB(); + continue; + } case ElementType::SOUND_FLAG: if (EngravingItem* parent = toSoundFlag(e)->parentItem()) { return parent; @@ -1151,6 +1157,12 @@ EngravingItem* Score::prevElement() e = toSystemLockIndicator(e)->systemLock()->endMB(); continue; } + case ElementType::PAGE_LOCK_INDICATOR: + { + staffId = 0; + e = toPageLockIndicator(e)->pageLock()->endMB(); + continue; + } case ElementType::HARMONY: { Harmony* harmony = toHarmony(e); if (harmony->isInFretBox()) { diff --git a/src/engraving/dom/rangelock.cpp b/src/engraving/dom/rangelock.cpp index 99d2b5f496ee8..38bfbf3ae156d 100644 --- a/src/engraving/dom/rangelock.cpp +++ b/src/engraving/dom/rangelock.cpp @@ -162,4 +162,30 @@ String SystemLockIndicator::formatBarsAndBeats() const const int endMeasureNum = endMeasure ? endMeasure->measureNumber() : -1; return muse::mtrc("engraving", "Start measure: %1; End measure: %2").arg(startMeasureNum).arg(endMeasureNum); } + +PageLockIndicator::PageLockIndicator(System* parent, const RangeLock* lock) + : IndicatorIcon(ElementType::PAGE_LOCK_INDICATOR, parent, ElementFlag::SYSTEM | ElementFlag::GENERATED), m_pageLock(lock) {} + +void PageLockIndicator::setSelected(bool v) +{ + EngravingItem::setSelected(v); + renderer()->layoutItem(this); + system()->page()->invalidateBspTree(); +} + +const Page* PageLockIndicator::page() const +{ + return system() ? system()->page() : nullptr; +} + +String PageLockIndicator::formatBarsAndBeats() const +{ + const MeasureBase* startMB = m_pageLock->startMB(); + const MeasureBase* endMB = m_pageLock->endMB(); + const Measure* startMeasure = startMB->isMeasure() ? toMeasure(startMB) : toMeasure(startMB->prevMeasure()); + const Measure* endMeasure = endMB->isMeasure() ? toMeasure(endMB) : toMeasure(endMB->prevMeasure()); + const int startMeasureNum = startMeasure ? startMeasure->measureNumber() : -1; + const int endMeasureNum = endMeasure ? endMeasure->measureNumber() : -1; + return muse::mtrc("engraving", "Start measure: %1; End measure: %2").arg(startMeasureNum).arg(endMeasureNum); +} } // namespace mu::engraving diff --git a/src/engraving/dom/rangelock.h b/src/engraving/dom/rangelock.h index a16deb074806d..05ba9fe1b273b 100644 --- a/src/engraving/dom/rangelock.h +++ b/src/engraving/dom/rangelock.h @@ -97,4 +97,30 @@ class SystemLockIndicator : public IndicatorIcon private: const RangeLock* m_systemLock = nullptr; }; + +class PageLockIndicator : public IndicatorIcon +{ + OBJECT_ALLOCATOR(engraving, PageLockIndicator) + DECLARE_CLASSOF(ElementType::PAGE_LOCK_INDICATOR) + +public: + PageLockIndicator(System* parent, const RangeLock* lock); + + void setSelected(bool v) override; + + const RangeLock* pageLock() const { return m_pageLock; } + const Page* page() const; + + char16_t iconCode() const override { return 0xF4C3; } + + String formatBarsAndBeats() const override; + + struct LayoutData : public IndicatorIcon::LayoutData { + ld_field innerRangeRect = { "[PageLockIndicator] innerRangeRect", RectF() }; + }; + DECLARE_LAYOUTDATA_METHODS(PageLockIndicator) + +private: + const RangeLock* m_pageLock = nullptr; +}; } // namespace mu::engraving diff --git a/src/engraving/dom/select.cpp b/src/engraving/dom/select.cpp index 8a1fe2f34d46a..79dc48bde2a23 100644 --- a/src/engraving/dom/select.cpp +++ b/src/engraving/dom/select.cpp @@ -404,7 +404,7 @@ MeasureBase* Selection::endMeasureBase() const std::vector Selection::selectedSystems() const { EngravingItem* el = element(); - if (el && (el->isSystemLockIndicator() /*TODO: || el->isStaffVisibilityIndicator*/)) { + if (el && (el->isSystemLockIndicator() || el->isPageLockIndicator() /*TODO: || el->isStaffVisibilityIndicator*/)) { return { const_cast(toIndicatorIcon(el)->system()) }; } @@ -428,9 +428,9 @@ std::vector Selection::selectedSystems() const std::vector mu::engraving::Selection::selectedPages() const { EngravingItem* el = element(); - // if (el && (el->pageLockIndicator())) { TODO - // return { const_cast(toIndicatorIcon(el)->system()) }; - // } + if (el && (el->isPageLockIndicator())) { + return { const_cast(toPageLockIndicator(el)->page()) }; + } std::vector systems = selectedSystems(); std::vector pages; diff --git a/src/engraving/dom/system.cpp b/src/engraving/dom/system.cpp index f714acbe2dc14..329bd7fe0ed84 100644 --- a/src/engraving/dom/system.cpp +++ b/src/engraving/dom/system.cpp @@ -159,6 +159,9 @@ System::~System() if (m_staffVisibilityIndicator) { delete m_staffVisibilityIndicator; } + if (m_pageLockIndicator) { + delete m_pageLockIndicator; + } } #ifndef ENGRAVING_NO_ACCESSIBILITY @@ -331,6 +334,17 @@ void System::deleteLockIndicators() m_lockIndicators.clear(); } +void System::setPageLockIndicator(PageLockIndicator* pli) +{ + m_pageLockIndicator = pli; +} + +void System::deletePageLockIndicator() +{ + delete m_pageLockIndicator; + m_pageLockIndicator = nullptr; +} + //--------------------------------------------------------- // nextVisibleStaff //--------------------------------------------------------- @@ -724,6 +738,10 @@ void System::scanElements(std::function func) func(m_staffVisibilityIndicator); } + if (m_pageLockIndicator) { + func(m_pageLockIndicator); + } + for (auto i : m_lockIndicators) { func(i); } diff --git a/src/engraving/dom/system.h b/src/engraving/dom/system.h index e934b2ee40668..7c79ddcd2486b 100644 --- a/src/engraving/dom/system.h +++ b/src/engraving/dom/system.h @@ -220,6 +220,9 @@ class System final : public EngravingItem void addLockIndicator(SystemLockIndicator* sli); void deleteLockIndicators(); + void setPageLockIndicator(PageLockIndicator* pli); + void deletePageLockIndicator(); + struct LayoutData : public EngravingItem::LayoutData { public: bool useLongNames() const { return m_useLongNames; } @@ -279,6 +282,7 @@ class System final : public EngravingItem std::vector m_brackets; std::list m_spannerSegments; std::vector m_lockIndicators; + PageLockIndicator* m_pageLockIndicator = nullptr; StaffVisibilityIndicator* m_staffVisibilityIndicator = nullptr; diff --git a/src/engraving/editing/edit.cpp b/src/engraving/editing/edit.cpp index 13666b6eb2036..4410a50338797 100644 --- a/src/engraving/editing/edit.cpp +++ b/src/engraving/editing/edit.cpp @@ -2800,6 +2800,7 @@ void Score::deleteItem(EngravingItem* el) case ElementType::KEYSIG: case ElementType::MEASURE_NUMBER: case ElementType::SYSTEM_LOCK_INDICATOR: + case ElementType::PAGE_LOCK_INDICATOR: case ElementType::HAMMER_ON_PULL_OFF_TEXT: case ElementType::PLAY_COUNT_TEXT: case ElementType::LYRICSLINE_SEGMENT: @@ -3395,6 +3396,12 @@ void Score::deleteItem(EngravingItem* el) EditSystemLocks::undoRemoveSystemLock(this, systemLock); } break; + case ElementType::PAGE_LOCK_INDICATOR: + { + const RangeLock* pageLock = toPageLockIndicator(el)->pageLock(); + EditPageLocks::undoRemovePageLock(this, pageLock); + } + break; default: undoRemoveElement(el); diff --git a/src/engraving/editing/editpagelocks.cpp b/src/engraving/editing/editpagelocks.cpp index df78253ac8434..7cf97e38773a4 100644 --- a/src/engraving/editing/editpagelocks.cpp +++ b/src/engraving/editing/editpagelocks.cpp @@ -343,12 +343,12 @@ void EditPageLocks::applyLockToSelection(Score* score) last = score->selection().endMeasureBase(); } else { for (EngravingItem* el : score->selection().elements()) { - // if (el->isPageLockIndicator()) { TODO - // const RangeLock* lock = toPageLockIndicator(el)->pageLock(); - // first = lock->startMB(); - // last = lock->endMB(); - // break; - // } + if (el->isPageLockIndicator()) { + const RangeLock* lock = toPageLockIndicator(el)->pageLock(); + first = lock->startMB(); + last = lock->endMB(); + break; + } MeasureBase* mb = el->findMeasureBase(); if (!mb) { continue; diff --git a/src/engraving/rendering/score/pagelayout.cpp b/src/engraving/rendering/score/pagelayout.cpp index 9c504f48ab901..addd8229f357b 100644 --- a/src/engraving/rendering/score/pagelayout.cpp +++ b/src/engraving/rendering/score/pagelayout.cpp @@ -390,6 +390,7 @@ void PageLayout::collectPage(LayoutContext& ctx) MeasureLayout::layout2(m, ctx); } SystemLayout::layoutSystemLockIndicators(s, ctx); + SystemLayout::layoutPageLockIndicators(s); if (StaffVisibilityIndicator* visibilityIndicator = s->staffVisibilityIndicator()) { TLayout::layoutIndicatorIcon(visibilityIndicator, visibilityIndicator->mutldata()); diff --git a/src/engraving/rendering/score/systemlayout.cpp b/src/engraving/rendering/score/systemlayout.cpp index 8c1c1a4e8de46..667c9bb71ac71 100644 --- a/src/engraving/rendering/score/systemlayout.cpp +++ b/src/engraving/rendering/score/systemlayout.cpp @@ -45,6 +45,7 @@ #include "dom/mmrestrange.h" #include "dom/note.h" #include "dom/ornament.h" +#include "dom/page.h" #include "dom/part.h" #include "dom/parenthesis.h" #include "dom/pedal.h" @@ -517,6 +518,23 @@ void SystemLayout::layoutSystemLockIndicators(System* system, LayoutContext& ctx TLayout::layoutIndicatorIcon(lockIndicator, lockIndicator->mutldata()); } +void SystemLayout::layoutPageLockIndicators(System* system) +{ + Page* page = system->page(); + system->deletePageLockIndicator(); + + const RangeLock* lock = page->pageLock(); + if (!lock || page->systems().back() != system) { + return; + } + + PageLockIndicator* lockIndicator = new PageLockIndicator(system, lock); + lockIndicator->setParent(system); + system->setPageLockIndicator(lockIndicator); + + TLayout::layoutPageLockIndicator(lockIndicator, lockIndicator->mutldata()); +} + //--------------------------------------------------------- // getNextSystem //--------------------------------------------------------- diff --git a/src/engraving/rendering/score/systemlayout.h b/src/engraving/rendering/score/systemlayout.h index f78636f9efbc2..11fbbc61c1be4 100644 --- a/src/engraving/rendering/score/systemlayout.h +++ b/src/engraving/rendering/score/systemlayout.h @@ -85,6 +85,7 @@ class SystemLayout static void removeElementFromSkyline(EngravingItem* element, const System* system); static void layoutSystemLockIndicators(System* system, LayoutContext& ctx); + static void layoutPageLockIndicators(System* system); private: struct MeasureState diff --git a/src/engraving/rendering/score/tdraw.cpp b/src/engraving/rendering/score/tdraw.cpp index 6272dc90d9f4f..74e58ae3471ae 100644 --- a/src/engraving/rendering/score/tdraw.cpp +++ b/src/engraving/rendering/score/tdraw.cpp @@ -316,7 +316,8 @@ void TDraw::drawItem(const EngravingItem* item, Painter* painter, const PaintOpt break; case ElementType::OTTAVA_SEGMENT: draw(item_cast(item), painter, opt); break; - + case ElementType::PAGE_LOCK_INDICATOR: draw(item_cast(item), painter, opt); + break; case ElementType::PARENTHESIS: draw(item_cast(item), painter, opt); break; case ElementType::PARTIAL_TIE_SEGMENT: draw(item_cast(item), painter, opt); @@ -2450,6 +2451,35 @@ void TDraw::draw(const OttavaSegment* item, Painter* painter, const PaintOptions drawTextLineBaseSegment(item, painter, opt); } +void TDraw::draw(const PageLockIndicator* item, Painter* painter, const PaintOptions& opt) +{ + TRACE_DRAW_ITEM; + + if (opt.isPrinting || !item->score()->showUnprintable()) { + return; + } + + Pen pen(item->selected() ? item->configuration()->selectionColor() : item->configuration()->formattingColor()); + painter->setPen(pen); + painter->setFont(item->font()); + painter->drawSymbol(PointF(), item->iconCode()); + + if (item->selected()) { + Color lockedAreaColor = item->configuration()->selectionColor(); + lockedAreaColor.setAlpha(38); + Brush brush(lockedAreaColor); + painter->setBrush(brush); + painter->setNoPen(); + double radius = 0.5 * item->spatium(); + + PainterPath path; + path.setFillRule(PainterPath::FillRule::OddEvenFill); + path.addRoundedRect(item->ldata()->rangeRect, radius, radius); + path.addRect(item->ldata()->innerRangeRect); + painter->drawPath(path); + } +} + void TDraw::draw(const Parenthesis* item, muse::draw::Painter* painter, const PaintOptions& opt) { TRACE_DRAW_ITEM; diff --git a/src/engraving/rendering/score/tdraw.h b/src/engraving/rendering/score/tdraw.h index 57f6ac600a37e..b11c4e0462210 100644 --- a/src/engraving/rendering/score/tdraw.h +++ b/src/engraving/rendering/score/tdraw.h @@ -112,6 +112,7 @@ class Ornament; class Ottava; class OttavaSegment; +class PageLockIndicator; class PalmMute; class PalmMuteSegment; class Parenthesis; @@ -268,6 +269,7 @@ class TDraw static void draw(const Ornament* item, muse::draw::Painter* painter, const PaintOptions& opt); static void draw(const OttavaSegment* item, muse::draw::Painter* painter, const PaintOptions& opt); + static void draw(const PageLockIndicator* item, muse::draw::Painter* painter, const PaintOptions& opt); static void draw(const Parenthesis* item, muse::draw::Painter* painter, const PaintOptions& opt); static void draw(const PartialTieSegment* item, muse::draw::Painter* painter, const PaintOptions& opt); static void draw(const PalmMuteSegment* item, muse::draw::Painter* painter, const PaintOptions& opt); diff --git a/src/engraving/rendering/score/tlayout.cpp b/src/engraving/rendering/score/tlayout.cpp index ae2a7e0d3dde0..72bd27854ad47 100644 --- a/src/engraving/rendering/score/tlayout.cpp +++ b/src/engraving/rendering/score/tlayout.cpp @@ -384,6 +384,9 @@ void TLayout::layoutItem(EngravingItem* item, LayoutContext& ctx) break; case ElementType::PALM_MUTE_SEGMENT: layoutPalmMuteSegment(item_cast(item), ctx); break; + case ElementType::PAGE_LOCK_INDICATOR: + layoutPageLockIndicator(item_cast(item), static_cast(ldata)); + break; case ElementType::PARENTHESIS: layoutParenthesis(item_cast(item), static_cast(ldata), ctx); break; case ElementType::PEDAL: layoutPedal(item_cast(item), ctx); @@ -4340,6 +4343,56 @@ void TLayout::layoutOttavaSegment(OttavaSegment* item, LayoutContext& ctx) Autoplace::autoplaceSpannerSegment(item, ldata, ctx.conf().spatium()); } +void TLayout::layoutPageLockIndicator(const PageLockIndicator* item, PageLockIndicator::LayoutData* ldata) +{ + if (!item->configuration()->canLayoutIcons()) { + return; + } + const Page* page = item->page(); + const System* sys = item->system(); + + IF_ASSERT_FAILED(page && sys) { + return; + } + + Shape shape; + + const double spatium = item->spatium(); + + const FontMetrics metrics(item->font()); + const RectF iconBox = metrics.boundingRect(item->iconCode()); + shape.add(iconBox, item); + + // Inset 2.5sp from edge of page + PointF posOnPage = PointF(page->width() - iconBox.width() - iconBox.left() - 2.5 * spatium, page->height() - 2.5 * spatium); + PointF posOnSystem = posOnPage - sys->pos(); + ldata->setPos(posOnSystem); + + if (item->selected()) { + // Outer range rectangle + RectF rangeRect = page->shape().bbox(); + PointF rectPagePos = rangeRect.topLeft() - ldata->pos() - sys->pos(); + rangeRect.translate(rectPagePos); + rangeRect.adjust(spatium * 2, spatium * 2, -spatium * 2, -spatium * 2); + ldata->rangeRect = rangeRect; + + // Inner rectangle cutout + // Leaves border with rounded corners when painted + double borderWidth = iconBox.height() + spatium; + RectF innerRect = rangeRect.adjusted(borderWidth, borderWidth, -borderWidth, -borderWidth); + ldata->innerRangeRect = innerRect; + shape.add(RectF(PointF(rangeRect.left(), rangeRect.top()), PointF(rangeRect.right(), innerRect.top()))); // top + shape.add(RectF(PointF(rangeRect.left(), innerRect.bottom()), PointF(rangeRect.right(), rangeRect.bottom()))); // bottom + shape.add(RectF(PointF(rangeRect.left(), innerRect.top()), PointF(innerRect.left(), innerRect.bottom()))); // left + shape.add(RectF(PointF(innerRect.right(), innerRect.top()), PointF(rangeRect.right(), innerRect.bottom()))); // right + } + + ldata->setShape(shape); + + // Ensure it goes behind notation and LayoutBreak + const_cast(item)->setZ(-100); +} + void TLayout::layoutPalmMute(PalmMute* item, LayoutContext& ctx) { LAYOUT_CALL_ITEM(item); diff --git a/src/engraving/rendering/score/tlayout.h b/src/engraving/rendering/score/tlayout.h index b4340ad3e568a..a5970ee6764fe 100644 --- a/src/engraving/rendering/score/tlayout.h +++ b/src/engraving/rendering/score/tlayout.h @@ -301,6 +301,7 @@ class TLayout static void layoutOttava(Ottava* item, LayoutContext& ctx); static void layoutOttavaSegment(OttavaSegment* item, LayoutContext& ctx); + static void layoutPageLockIndicator(const PageLockIndicator* item, PageLockIndicator::LayoutData* ldata); static void layoutPalmMute(PalmMute* item, LayoutContext& ctx); static void layoutPalmMuteSegment(PalmMuteSegment* item, LayoutContext& ctx); static void layoutParenthesis(Parenthesis* item, Parenthesis::LayoutData* ldata, const LayoutContext& ctx); diff --git a/src/engraving/types/types.h b/src/engraving/types/types.h index 8846268ffb7b9..722c34c0310f3 100644 --- a/src/engraving/types/types.h +++ b/src/engraving/types/types.h @@ -187,6 +187,7 @@ enum class ElementType : unsigned char { TAB_DURATION_SYMBOL, FSYMBOL, PAGE, + PAGE_LOCK_INDICATOR, TEXTLINE_BASE, BRACKET, SEGMENT, diff --git a/src/engraving/types/typesconv.cpp b/src/engraving/types/typesconv.cpp index 49b761be900f4..ce4e2d6b2e70a 100644 --- a/src/engraving/types/typesconv.cpp +++ b/src/engraving/types/typesconv.cpp @@ -493,6 +493,9 @@ static const std::array ELEMENT_TYPES { Item{ ElementType::PAGE, "Page", TranslatableString("engraving", "page(s)", nullptr, 1), TranslatableString("engraving", "Page(s)", nullptr, 1) }, + Item{ ElementType::PAGE_LOCK_INDICATOR, "pageLockIndicator", + TranslatableString("engraving", "page lock(s)", nullptr, 1), + TranslatableString("engraving", "Page lock(s)", nullptr, 1) }, Item{ ElementType::PARENTHESIS, "Parenthesis", TranslatableString("engraving", "parenthesis", nullptr, 1), TranslatableString("engraving", "Parenthesis", nullptr, 1) }, From 7fe9c9f4872500bbde6d111c6e167dbcddaeabd4 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Tue, 9 Jun 2026 14:12:36 +0100 Subject: [PATCH 06/10] Rewrite & rename measureAtTick The old implementation of tick2measureBase worked by returning the first MeasureBase which had a duration. As Boxes don't have durations, this always returned a measure. So, the logic has been simplified and it has been renamed The new implementation of `tick2measureBase` matches the behaviour of `tick2measure` - returns the first measurebase containing the given tick --- src/engraving/dom/score.cpp | 4 ++-- src/engraving/dom/score.h | 1 + src/engraving/dom/utils.cpp | 14 +++++++++++++- src/engraving/editing/edit.cpp | 2 +- src/notation/internal/notationinteraction.cpp | 2 +- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index c298f295a439d..7f46a439a7036 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -2040,8 +2040,8 @@ bool Score::appendMeasuresFromScore(Score* score, const Fraction& startTick, con Fraction tickOfAppend = last()->endTick(); TieMap tieMap; - MeasureBase* fmb = score->tick2measureBase(startTick); - MeasureBase* emb = score->tick2measureBase(endTick); + MeasureBase* fmb = score->measureAtTick(startTick); + MeasureBase* emb = score->measureAtTick(endTick); Fraction curTick = tickOfAppend; for (MeasureBase* cmb = fmb; cmb != emb; cmb = cmb->next()) { MeasureBase* nmb; diff --git a/src/engraving/dom/score.h b/src/engraving/dom/score.h index d28a4ebcaa800..a667d0871e27d 100644 --- a/src/engraving/dom/score.h +++ b/src/engraving/dom/score.h @@ -674,6 +674,7 @@ class Score : public EngravingObject, public muse::Contextable Fraction pos(); Measure* tick2measure(const Fraction& tick) const; Measure* tick2measureMM(const Fraction& tick) const; + Measure* measureAtTick(const Fraction& tick) const; MeasureBase* tick2measureBase(const Fraction& tick) const; Segment* tick2segment(const Fraction& tick, bool first, SegmentType st, bool useMMrest = false) const; Segment* tick2segment(const Fraction& tick) const; diff --git a/src/engraving/dom/utils.cpp b/src/engraving/dom/utils.cpp index db74071feb2bb..4ff66eee62c87 100644 --- a/src/engraving/dom/utils.cpp +++ b/src/engraving/dom/utils.cpp @@ -118,9 +118,21 @@ Measure* Score::tick2measureMM(const Fraction& t) const } //--------------------------------------------------------- -// tick2measureBase +// measureAtTick //--------------------------------------------------------- +Measure* Score::measureAtTick(const Fraction& tick) const +{ + std::vector mbList = m_measures.measureBasesAtTick(tick.ticks()); + for (MeasureBase* mb : mbList) { + if (mb->isMeasure()) { + return toMeasure(mb); + } + } + LOGD("measureAtTick %d not found", tick.ticks()); + return nullptr; +} + MeasureBase* Score::tick2measureBase(const Fraction& tick) const { if (tick == Fraction(-1, 1)) { // special number diff --git a/src/engraving/editing/edit.cpp b/src/engraving/editing/edit.cpp index 4410a50338797..d84b2e1d34e5b 100644 --- a/src/engraving/editing/edit.cpp +++ b/src/engraving/editing/edit.cpp @@ -4738,7 +4738,7 @@ MeasureBase* Score::insertMeasure(ElementType type, MeasureBase* beforeMeasure, MeasureBase* localInsertMeasureBase = nullptr; if (type == ElementType::MEASURE) { if (MeasureBase* masterInsertMeasure = masterScore()->insertMeasure(beforeMeasure, options)) { - localInsertMeasureBase = tick2measureBase(masterInsertMeasure->tick()); + localInsertMeasureBase = measureAtTick(masterInsertMeasure->tick()); } } else { localInsertMeasureBase = insertBox(type, beforeMeasure, options); diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index 9546aeecf5356..3df676fd9f0d4 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -1917,7 +1917,7 @@ bool NotationInteraction::updateDropRange(const PointF& pos, std::optional // Invalidate BSP tree of affected pages System* lastSeenSystem = nullptr; Page* lastSeenPage = nullptr; - for (MeasureBase* mb = score()->tick2measureBase(showAnchors.startTickExtendedRegion); + for (MeasureBase* mb = score()->measureAtTick(showAnchors.startTickExtendedRegion); mb && mb->tick() <= showAnchors.endTickExtendedRegion; mb = mb->next()) { System* s = mb->system(); From 4c7599f8b0d1eb7f80160bff44957595f9446081 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Tue, 9 Jun 2026 14:12:42 +0100 Subject: [PATCH 07/10] Review fixes --- src/engraving/dom/page.cpp | 2 +- src/engraving/dom/score.h | 1 - src/engraving/editing/editpagelocks.cpp | 29 ++++++++++--------- src/engraving/editing/editpagelocks.h | 2 +- .../internal/notationactioncontroller.cpp | 4 +-- .../systemlayoutsettingsmodel.cpp | 4 +++ 6 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/engraving/dom/page.cpp b/src/engraving/dom/page.cpp index f070a7bcbf8a3..ea611b54de6ca 100644 --- a/src/engraving/dom/page.cpp +++ b/src/engraving/dom/page.cpp @@ -302,5 +302,5 @@ bool Page::isLocked() const const RangeLock* Page::pageLock() const { MeasureBase* firstMeasure = firstMeasureBase(); - return firstMeasure->pageLock(); + return firstMeasure ? firstMeasure->pageLock() : nullptr; } diff --git a/src/engraving/dom/score.h b/src/engraving/dom/score.h index a667d0871e27d..0523bfa9ccc44 100644 --- a/src/engraving/dom/score.h +++ b/src/engraving/dom/score.h @@ -1060,7 +1060,6 @@ class Score : public EngravingObject, public muse::Contextable const RangeLocks* pageLocks() const { return &m_pageLocks; } void addPageLock(const RangeLock* lock); void removePageLock(const RangeLock* lock); - void clearPageLocks() { m_pageLocks.clear(); } void rebuildFretBox(); diff --git a/src/engraving/editing/editpagelocks.cpp b/src/engraving/editing/editpagelocks.cpp index 7cf97e38773a4..07cd5844b88bf 100644 --- a/src/engraving/editing/editpagelocks.cpp +++ b/src/engraving/editing/editpagelocks.cpp @@ -118,7 +118,7 @@ class RemovePageLock : public UndoCommand void EditPageLocks::undoAddPageLock(Score* score, const RangeLock* lock) { - removeLayoutBreaksOnAddPageLock(score, lock); + removeLayoutBreaksOnAddPageLock(lock); score->undo(new AddPageLock(lock)); } @@ -183,6 +183,7 @@ void EditPageLocks::toggleScoreLock(Score* score) void EditPageLocks::addRemovePageLocks(Score* score, int interval, bool lock) { + // Should only be called on range selection of whole score MeasureBase* startMeasure = score->selection().startMeasureBase(); MeasureBase* endMeasure = score->selection().endMeasureBase(); if (!endMeasure) { @@ -237,32 +238,32 @@ void EditPageLocks::addRemovePageLocks(Score* score, int interval, bool lock) void EditPageLocks::makeIntoPage(Score* score, MeasureBase* first, MeasureBase* last) { - const RangeLock* lockContainingfirst = score->pageLocks()->lockContaining(first); - const RangeLock* lockContaininglast = score->pageLocks()->lockContaining(last); + const RangeLock* lockContainingFirst = score->pageLocks()->lockContaining(first); + const RangeLock* lockContainingLast = score->pageLocks()->lockContaining(last); - if (lockContainingfirst) { - undoRemovePageLock(score, lockContainingfirst); - if (lockContainingfirst->startMB()->isBefore(first)) { + if (lockContainingFirst) { + undoRemovePageLock(score, lockContainingFirst); + if (lockContainingFirst->startMB()->isBefore(first)) { MeasureBase* oneBeforeFirst = first->prevMM(); - RangeLock* newLockBefore = new RangeLock(lockContainingfirst->startMB(), oneBeforeFirst); + RangeLock* newLockBefore = new RangeLock(lockContainingFirst->startMB(), oneBeforeFirst); undoAddPageLock(score, newLockBefore); } } - if (lockContaininglast) { - if (lockContaininglast != lockContainingfirst) { - undoRemovePageLock(score, lockContaininglast); + if (lockContainingLast) { + if (lockContainingLast != lockContainingFirst) { + undoRemovePageLock(score, lockContainingLast); } - if (last->isBefore(lockContaininglast->endMB())) { + if (last->isBefore(lockContainingLast->endMB())) { MeasureBase* oneAfterLast = last->nextMM(); - RangeLock* newLockAfter = new RangeLock(oneAfterLast, lockContaininglast->endMB()); + RangeLock* newLockAfter = new RangeLock(oneAfterLast, lockContainingLast->endMB()); undoAddPageLock(score, newLockAfter); } } std::vector locksContainedInRange = score->pageLocks()->locksContainedInRange(first, last); for (const RangeLock* lock : locksContainedInRange) { - if (lock != lockContainingfirst && lock != lockContaininglast) { + if (lock != lockContainingFirst && lock != lockContainingLast) { undoRemovePageLock(score, lock); } } @@ -388,7 +389,7 @@ void EditPageLocks::removePageLocksOnAddLayoutBreak(Score* score, LayoutBreakTyp } } -void EditPageLocks::removeLayoutBreaksOnAddPageLock(Score* score, const RangeLock* lock) +void EditPageLocks::removeLayoutBreaksOnAddPageLock(const RangeLock* lock) { for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mb->nextMM()) { mb->undoSetBreak(false, LayoutBreakType::LINE); diff --git a/src/engraving/editing/editpagelocks.h b/src/engraving/editing/editpagelocks.h index c6aab16de9854..a96c712ed92d2 100644 --- a/src/engraving/editing/editpagelocks.h +++ b/src/engraving/editing/editpagelocks.h @@ -52,7 +52,7 @@ class EditPageLocks static void applyLockToSelection(Score* score); - static void removeLayoutBreaksOnAddPageLock(Score* score, const RangeLock* lock); + static void removeLayoutBreaksOnAddPageLock(const RangeLock* lock); static void removePageLocksOnAddLayoutBreak(Score* score, LayoutBreakType breakType, const MeasureBase* measure); static void removePageLocksOnRemoveMeasures(Score* score, const MeasureBase* m1, const MeasureBase* m2); static void removePageLocksContainingMMRests(Score* score); diff --git a/src/notationscene/internal/notationactioncontroller.cpp b/src/notationscene/internal/notationactioncontroller.cpp index 872d31a1e08d5..40c36c6d6127d 100644 --- a/src/notationscene/internal/notationactioncontroller.cpp +++ b/src/notationscene/internal/notationactioncontroller.cpp @@ -282,8 +282,8 @@ void NotationActionController::init() registerAction("toggle-score-lock", &Interaction::toggleScoreLock); registerAction("make-into-system", &Interaction::makeIntoSystem); registerAction("apply-page-lock", &Interaction::applyPageLock); - registerAction("move-measure-to-prev-page", &Interaction::moveSystemToPrevPage); - registerAction("move-measure-to-next-page", &Interaction::moveSystemToNextPage); + registerAction("move-system-to-prev-page", &Interaction::moveSystemToPrevPage); + registerAction("move-system-to-next-page", &Interaction::moveSystemToNextPage); registerAction("toggle-page-lock", &Interaction::togglePageLock); registerAction("make-into-page", &Interaction::makeIntoPage); diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/systemlayoutsettingsmodel.cpp b/src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/systemlayoutsettingsmodel.cpp index 535153049968d..35c85969e7a51 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/systemlayoutsettingsmodel.cpp +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/systemlayoutsettingsmodel.cpp @@ -245,6 +245,10 @@ int SystemLayoutSettingsModel::pageCount() const void SystemLayoutSettingsModel::updateScoreIsInPageView() { + if (!currentNotation()) { + return; + } + bool isInPageView = currentNotation()->viewMode() != LayoutMode::LINE; if (m_scoreIsInPageView != isInPageView) { From 3ac7e5a5d3cd555521dbc906d1aaa2e0e263e171 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Thu, 11 Jun 2026 14:57:32 +0100 Subject: [PATCH 08/10] Disable indicators in all modes but page mode --- src/engraving/rendering/score/pagelayout.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/engraving/rendering/score/pagelayout.cpp b/src/engraving/rendering/score/pagelayout.cpp index addd8229f357b..6466c241b73d0 100644 --- a/src/engraving/rendering/score/pagelayout.cpp +++ b/src/engraving/rendering/score/pagelayout.cpp @@ -390,7 +390,9 @@ void PageLayout::collectPage(LayoutContext& ctx) MeasureLayout::layout2(m, ctx); } SystemLayout::layoutSystemLockIndicators(s, ctx); - SystemLayout::layoutPageLockIndicators(s); + if (ctx.conf().isMode(LayoutMode::PAGE)) { + SystemLayout::layoutPageLockIndicators(s); + } if (StaffVisibilityIndicator* visibilityIndicator = s->staffVisibilityIndicator()) { TLayout::layoutIndicatorIcon(visibilityIndicator, visibilityIndicator->mutldata()); From d2d9db0aca20d1cb89d790f95168e1260126739f Mon Sep 17 00:00:00 2001 From: James Mizen Date: Tue, 16 Jun 2026 15:55:24 +0100 Subject: [PATCH 09/10] System lock interaction fixes --- src/engraving/editing/editpagelocks.cpp | 7 +--- src/engraving/editing/editsystemlocks.cpp | 51 ++++++++++++++++++++++- src/engraving/editing/editsystemlocks.h | 2 +- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/engraving/editing/editpagelocks.cpp b/src/engraving/editing/editpagelocks.cpp index 07cd5844b88bf..56f26ca21e1aa 100644 --- a/src/engraving/editing/editpagelocks.cpp +++ b/src/engraving/editing/editpagelocks.cpp @@ -392,12 +392,7 @@ void EditPageLocks::removePageLocksOnAddLayoutBreak(Score* score, LayoutBreakTyp void EditPageLocks::removeLayoutBreaksOnAddPageLock(const RangeLock* lock) { for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mb->nextMM()) { - mb->undoSetBreak(false, LayoutBreakType::LINE); - mb->undoSetBreak(false, LayoutBreakType::NOBREAK); - if (mb != lock->endMB()) { - mb->undoSetBreak(false, LayoutBreakType::SECTION); - mb->undoSetBreak(false, LayoutBreakType::PAGE); - } + mb->undoSetBreak(false, LayoutBreakType::PAGE); } } diff --git a/src/engraving/editing/editsystemlocks.cpp b/src/engraving/editing/editsystemlocks.cpp index ea082c27fa742..2da7f7a231282 100644 --- a/src/engraving/editing/editsystemlocks.cpp +++ b/src/engraving/editing/editsystemlocks.cpp @@ -21,6 +21,7 @@ */ #include "editsystemlocks.h" +#include "editpagelocks.h" #include "transaction/undoablecommand.h" @@ -118,7 +119,7 @@ class RemoveSystemLock : public UndoableCommand void EditSystemLocks::undoAddSystemLock(Score* score, const RangeLock* lock) { - removeLayoutBreaksOnAddSystemLock(score, lock); + updateLayoutBreaksOnAddSystemLock(score, lock); score->undo(new AddSystemLock(lock)); } @@ -311,6 +312,7 @@ void EditSystemLocks::moveMeasureToNextSystem(Score* score, MeasureBase* m) { const System* curSystem = m->system(); MeasureBase* startMeas = curSystem->first(); + MeasureBase* systemCurEndMeasure = curSystem->last(); bool refMeasureIsStartOfSystem = m == startMeas; const RangeLock* curLock = score->systemLocks()->lockStartingAt(startMeas); @@ -322,6 +324,28 @@ void EditSystemLocks::moveMeasureToNextSystem(Score* score, MeasureBase* m) MeasureBase* prevMeas = m->prevMM(); RangeLock* sysLock = new RangeLock(startMeas, prevMeas); undoAddSystemLock(score, sysLock); + + // Move existing page breaks and section breaks to the end of the new range + if (systemCurEndMeasure->pageBreak()) { + systemCurEndMeasure->undoSetBreak(false, LayoutBreakType::PAGE); + prevMeas->undoSetBreak(true, LayoutBreakType::PAGE); + } + + if (systemCurEndMeasure->sectionBreak()) { + systemCurEndMeasure->undoSetBreak(false, LayoutBreakType::SECTION); + prevMeas->undoSetBreak(true, LayoutBreakType::SECTION); + } + + // Create an updated page lock which extends to the end of the new range + if (systemCurEndMeasure->isEndOfPageLock()) { + const RangeLock* pageLock = systemCurEndMeasure->pageLock(); + MeasureBase* pageLockStartMb = pageLock->startMB(); + + EditPageLocks::undoRemovePageLock(score, pageLock); + + RangeLock* newPageLock = new RangeLock(pageLockStartMb, sysLock->endMB()); + EditPageLocks::undoAddPageLock(score, newPageLock); + } } const System* nextSystem = m->nextNonVBoxSystem(); @@ -395,15 +419,38 @@ void EditSystemLocks::removeSystemLocksOnAddLayoutBreak(Score* score, LayoutBrea } } -void EditSystemLocks::removeLayoutBreaksOnAddSystemLock(Score* score, const RangeLock* lock) +void EditSystemLocks::updateLayoutBreaksOnAddSystemLock(Score* score, const RangeLock* lock) { + bool moveSectionBreak = false; + bool movePageBreak = false; for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mb->nextMM()) { mb->undoSetBreak(false, LayoutBreakType::LINE); mb->undoSetBreak(false, LayoutBreakType::NOBREAK); if (mb != lock->endMB()) { + // Move existing page breaks and section breaks to the end of the new range + moveSectionBreak |= mb->sectionBreak(); + movePageBreak |= mb->pageBreak(); mb->undoSetBreak(false, LayoutBreakType::SECTION); mb->undoSetBreak(false, LayoutBreakType::PAGE); } + + if (mb->isEndOfPageLock()) { + // Create an updated page lock which extends to the end of the new range + const RangeLock* pageLock = mb->pageLock(); + MeasureBase* pageLockStartMb = pageLock->startMB(); + + EditPageLocks::undoRemovePageLock(score, pageLock); + + RangeLock* newPageLock = new RangeLock(pageLockStartMb, lock->endMB()); + EditPageLocks::undoAddPageLock(score, newPageLock); + } + } + + if (moveSectionBreak) { + lock->endMB()->undoSetBreak(true, LayoutBreakType::SECTION); + } + if (movePageBreak) { + lock->endMB()->undoSetBreak(true, LayoutBreakType::PAGE); } } diff --git a/src/engraving/editing/editsystemlocks.h b/src/engraving/editing/editsystemlocks.h index 71041324f4c76..6821ff0ed744f 100644 --- a/src/engraving/editing/editsystemlocks.h +++ b/src/engraving/editing/editsystemlocks.h @@ -52,7 +52,7 @@ class EditSystemLocks static void applyLockToSelection(Score* score); static void removeSystemLocksOnAddLayoutBreak(Score* score, LayoutBreakType breakType, const MeasureBase* measure); - static void removeLayoutBreaksOnAddSystemLock(Score* score, const RangeLock* lock); + static void updateLayoutBreaksOnAddSystemLock(Score* score, const RangeLock* lock); static void removeSystemLocksOnRemoveMeasures(Score* score, const MeasureBase* m1, const MeasureBase* m2); static void removeSystemLocksContainingMMRests(Score* score); static void updateSystemLocksOnCreateMMRests(Score* score, Measure* first, Measure* last); From 200b7317ce710f5f862d341bd4b8ccbfbbac3f80 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Wed, 17 Jun 2026 08:11:52 +0100 Subject: [PATCH 10/10] Fix build --- src/engraving/editing/editpagelocks.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engraving/editing/editpagelocks.cpp b/src/engraving/editing/editpagelocks.cpp index 56f26ca21e1aa..65b44e16f77ef 100644 --- a/src/engraving/editing/editpagelocks.cpp +++ b/src/engraving/editing/editpagelocks.cpp @@ -22,7 +22,7 @@ #include "editpagelocks.h" -#include "undo.h" +#include "transaction/undoablecommand.h" #include "../dom/measurebase.h" #include "../dom/page.h" @@ -35,7 +35,7 @@ using namespace mu::engraving; // AddPageLock //--------------------------------------------------------- -class AddPageLock : public UndoCommand +class AddPageLock : public UndoableCommand { OBJECT_ALLOCATOR(engraving, AddPageLock) @@ -76,7 +76,7 @@ class AddPageLock : public UndoCommand // RemovePageLock //--------------------------------------------------------- -class RemovePageLock : public UndoCommand +class RemovePageLock : public UndoableCommand { OBJECT_ALLOCATOR(engraving, RemovePageLock)