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/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/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/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 bca4c75dd0327..d26ae23ee5ed4 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" @@ -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) @@ -738,7 +748,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..76d199bed96b6 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); @@ -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/measurebase.cpp b/src/engraving/dom/measurebase.cpp index 3452311c315ff..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 { @@ -138,6 +136,41 @@ System* MeasureBase::nextNonVBoxSystem() const return nextSystem != curSystem ? nextSystem : nullptr; } +Page* MeasureBase::page() const +{ + return system() ? system()->page() : nullptr; +} + +Page* MeasureBase::prevPage() const +{ + Page* curPage = system() ? system()->page() : nullptr; + IF_ASSERT_FAILED(curPage) { + return nullptr; + } + + Page* prevPage = curPage; + for (const MeasureBase* mb = this; mb && prevPage == curPage; mb = mb->prevMM()) { + prevPage = mb->system()->page(); + } + + return prevPage != curPage ? prevPage : nullptr; +} + +Page* MeasureBase::nextPage() const +{ + Page* curPage = system() ? system()->page() : nullptr; + IF_ASSERT_FAILED(curPage) { + return nullptr; + } + + Page* nextPage = curPage; + for (const MeasureBase* mb = this; mb && nextPage == curPage; mb = mb->nextMM()) { + nextPage = mb->system()->page(); + } + + return nextPage != curPage ? nextPage : nullptr; +} + //--------------------------------------------------------- // add /// Add new EngravingItem \a el to MeasureBase @@ -638,20 +671,37 @@ 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; +} + +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; } @@ -902,6 +952,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 5c5dc32981e75..e52057c318cd1 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 @@ -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; @@ -160,10 +163,14 @@ 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; + 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/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/page.cpp b/src/engraving/dom/page.cpp index 2bd2085452770..ea611b54de6ca 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 ? firstMeasure->pageLock() : nullptr; +} 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/systemlock.cpp b/src/engraving/dom/rangelock.cpp similarity index 51% rename from src/engraving/dom/systemlock.cpp rename to src/engraving/dom/rangelock.cpp index 53b5fff272d06..38bfbf3ae156d 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,54 +95,54 @@ 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* curLock = curIter->second; - DO_ASSERT(curSysLock->startMB() == curMB); + DO_ASSERT(curLock->startMB() == curMB); const MeasureBase* nextMB = nextIter->first; - const SystemLock* 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 SystemLocks::dump() +void RangeLocks::dump() const { - 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) @@ -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/systemlock.h b/src/engraving/dom/rangelock.h similarity index 53% rename from src/engraving/dom/systemlock.h rename to src/engraving/dom/rangelock.h index 0d46f500a025e..05ba9fe1b273b 100644 --- a/src/engraving/dom/systemlock.h +++ b/src/engraving/dom/rangelock.h @@ -28,14 +28,12 @@ #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()); - assert(m_endMB->isMeasure() || m_endMB->isHBox()); assert(m_startMB->isBefore(m_endMB) || m_startMB == m_endMB); } @@ -49,24 +47,24 @@ 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 void sanityCheck(); - void dump(); + void dump() const; #endif struct Ordering @@ -77,7 +75,7 @@ class SystemLocks } }; - std::map m_systemLocks; + std::map m_rangeLocks; }; class SystemLockIndicator : public IndicatorIcon @@ -86,17 +84,43 @@ 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; +}; + +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/score.cpp b/src/engraving/dom/score.cpp index 99effd01ff0dd..7f46a439a7036 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(); @@ -2037,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; @@ -6032,7 +6035,7 @@ void Score::autoUpdateSpatium() createPaddingTable(); } -void Score::addSystemLock(const SystemLock* lock) +void Score::addSystemLock(const RangeLock* lock) { m_systemLocks.add(lock); @@ -6040,7 +6043,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); @@ -6048,6 +6051,22 @@ void Score::removeSystemLock(const SystemLock* 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 bb40e7586eb9f..0523bfa9ccc44 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 { @@ -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; @@ -1051,11 +1052,15 @@ 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(); } + const RangeLocks* pageLocks() const { return &m_pageLocks; } + void addPageLock(const RangeLock* lock); + void removePageLock(const RangeLock* lock); + void rebuildFretBox(); const std::map > systemDividers() const { return m_systemDividers; } @@ -1170,7 +1175,9 @@ 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; + + 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..79dc48bde2a23 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,16 +398,13 @@ 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 { 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()) }; } @@ -419,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); @@ -431,6 +425,25 @@ std::vector Selection::selectedSystems() const return systems; } +std::vector mu::engraving::Selection::selectedPages() const +{ + EngravingItem* el = element(); + if (el && (el->isPageLockIndicator())) { + return { const_cast(toPageLockIndicator(el)->page()) }; + } + + 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/system.cpp b/src/engraving/dom/system.cpp index 757724b8dbdf4..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 @@ -314,7 +317,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(); } @@ -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 554f201bd7719..7c79ddcd2486b 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,12 +214,15 @@ 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); 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/dom/utils.cpp b/src/engraving/dom/utils.cpp index f6098627bad2c..4ff66eee62c87 100644 --- a/src/engraving/dom/utils.cpp +++ b/src/engraving/dom/utils.cpp @@ -118,24 +118,33 @@ Measure* Score::tick2measureMM(const Fraction& t) const } //--------------------------------------------------------- -// tick2measureBase +// measureAtTick //--------------------------------------------------------- -MeasureBase* Score::tick2measureBase(const Fraction& tick) const +Measure* Score::measureAtTick(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 (mb->isMeasure()) { + return toMeasure(mb); } } - - LOGD("tick2measureBase %d not found", tick.ticks()); + LOGD("measureAtTick %d not found", tick.ticks()); return nullptr; } +MeasureBase* Score::tick2measureBase(const Fraction& tick) const +{ + if (tick == Fraction(-1, 1)) { // special number + return m_measures.last(); + } + if (tick <= Fraction(0, 1)) { + return m_measures.first(); + } + + return m_measures.measureBaseByTick(tick.ticks()); +} + //--------------------------------------------------------- // tick2segment //--------------------------------------------------------- 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 702cc7654d3cb..d84b2e1d34e5b 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" @@ -2799,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: @@ -3390,10 +3392,16 @@ 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; + case ElementType::PAGE_LOCK_INDICATOR: + { + const RangeLock* pageLock = toPageLockIndicator(el)->pageLock(); + EditPageLocks::undoRemovePageLock(this, pageLock); + } + break; default: undoRemoveElement(el); @@ -4730,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); @@ -7541,6 +7549,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..65b44e16f77ef --- /dev/null +++ b/src/engraving/editing/editpagelocks.cpp @@ -0,0 +1,483 @@ +/* + * 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 "transaction/undoablecommand.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 UndoableCommand +{ + 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 UndoableCommand +{ + 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(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) +{ + // Should only be called on range selection of whole score + MeasureBase* startMeasure = score->selection().startMeasureBase(); + MeasureBase* endMeasure = score->selection().endMeasureBase(); + if (!endMeasure) { + endMeasure = score->lastMeasureMM(); + } + + 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 = mb->nextMM()) { + 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) +{ + 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 = first->prevMM(); + 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 = last->nextMM(); + 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) { + MeasureBase* nextMB = m->nextMM(); + 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) { + MeasureBase* prevMeas = m->prevMM(); + 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()) { + 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(const RangeLock* lock) +{ + for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mb->nextMM()) { + 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..a96c712ed92d2 --- /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(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/editing/editsystemlocks.cpp b/src/engraving/editing/editsystemlocks.cpp index 266926fbf6f1a..2da7f7a231282 100644 --- a/src/engraving/editing/editsystemlocks.cpp +++ b/src/engraving/editing/editsystemlocks.cpp @@ -21,13 +21,14 @@ */ #include "editsystemlocks.h" +#include "editpagelocks.h" #include "transaction/undoablecommand.h" #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 +40,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 +81,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 +117,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); + updateLayoutBreaksOnAddSystemLock(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 +148,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 +178,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); } } @@ -190,12 +191,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) { @@ -211,14 +210,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); } @@ -228,13 +227,13 @@ 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; } count++; if (count == interval || mb == endMeasure) { - undoAddSystemLock(score, new SystemLock(lockStart, mb)); + undoAddSystemLock(score, new RangeLock(lockStart, mb)); lockStart = nullptr; count = 0; } @@ -246,16 +245,14 @@ 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 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); + MeasureBase* oneBeforeFirst = first->prevMM(); + RangeLock* newLockBefore = new RangeLock(lockContainingfirst->startMB(), oneBeforeFirst); undoAddSystemLock(score, newLockBefore); } } @@ -265,20 +262,20 @@ void EditSystemLocks::makeIntoSystem(Score* score, MeasureBase* first, MeasureBa undoRemoveSystemLock(score, lockContaininglast); } if (last->isBefore(lockContaininglast->endMB())) { - MeasureBase* oneAfterLast = mmrests ? last->nextMM() : last->next(); - SystemLock* newLockAfter = new SystemLock(oneAfterLast, lockContaininglast->endMB()); + MeasureBase* oneAfterLast = last->nextMM(); + 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 +288,23 @@ 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()); + MeasureBase* nextMB = m->nextMM(); + 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); } @@ -316,18 +312,40 @@ 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 SystemLock* curLock = score->systemLocks()->lockStartingAt(startMeas); + const RangeLock* curLock = score->systemLocks()->lockStartingAt(startMeas); if (curLock) { undoRemoveSystemLock(score, curLock); } if (!refMeasureIsStartOfSystem) { - bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); - MeasureBase* prevMeas = mmrests ? m->prevMM() : m->prev(); - SystemLock* sysLock = new SystemLock(startMeas, prevMeas); + 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(); @@ -335,13 +353,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 +375,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 +397,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,29 +413,51 @@ 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::updateLayoutBreaksOnAddSystemLock(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()) { + 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); } } 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 +468,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 +481,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 +499,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 +523,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 +537,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..6821ff0ed744f 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 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); diff --git a/src/engraving/rendering/score/layoutcontext.cpp b/src/engraving/rendering/score/layoutcontext.cpp index 25c2239e29f1f..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" @@ -375,7 +376,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; @@ -383,6 +384,14 @@ const SystemLocks* 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 8fbeaa96a7eb3..6fff8cb1edeb9 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,8 @@ class DomAccessor const ChordRest* findCR(Fraction tick, track_idx_t track) const; - const SystemLocks* systemLocks() 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..6466c241b73d0 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(); + + bool isPageBreak = !ctx.state().curSystem() + || (breakPages && (ctx.state().prevSystem()->pageBreak() || endOfPageLock || pageLockStart)); - if (!isPageBreak) { + 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) { @@ -372,6 +390,9 @@ void PageLayout::collectPage(LayoutContext& ctx) MeasureLayout::layout2(m, ctx); } SystemLayout::layoutSystemLockIndicators(s, ctx); + if (ctx.conf().isMode(LayoutMode::PAGE)) { + SystemLayout::layoutPageLockIndicators(s); + } if (StaffVisibilityIndicator* visibilityIndicator = s->staffVisibilityIndicator()) { TLayout::layoutIndicatorIcon(visibilityIndicator, visibilityIndicator->mutldata()); @@ -527,10 +548,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 +579,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 +615,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 +643,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 +738,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 +778,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/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..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" @@ -143,8 +144,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); @@ -262,8 +263,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: @@ -505,7 +506,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; } @@ -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 63db73a5cc7fc..74e58ae3471ae 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" @@ -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 2c2b40f34f951..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); @@ -3711,7 +3714,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(); @@ -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/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/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 303bf72ce7753..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; @@ -4795,7 +4832,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.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 b697b4101f493..31c6d3df379e8 100644 --- a/src/engraving/rw/write/twrite.cpp +++ b/src/engraving/rw/write/twrite.cpp @@ -448,15 +448,29 @@ 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(); + 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 +539,17 @@ void TWrite::writeItemLink(const EngravingObject* item, XmlWriter& xml, WriteCon } } -void TWrite::writeSystemLock(const SystemLock* systemLock, XmlWriter& xml) +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 2693d014d638d..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,7 +367,8 @@ 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 writePageLock(const RangeLock* pageLock, 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/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 0000000000000..9688bb83c400f Binary files /dev/null and b/src/engraving/tests/page_locks_data/page_locks-1.mscz differ 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/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; 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) }, diff --git a/src/notation/inotationinteraction.h b/src/notation/inotationinteraction.h index a8a33b6bbd578..751e08484399a 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 moveSystemToPrevPage() = 0; + virtual void moveSystemToNextPage() = 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..3df676fd9f0d4 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" @@ -1916,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(); @@ -6119,6 +6120,59 @@ void NotationInteraction::applySystemLock() apply(); } +void NotationInteraction::moveSystemToPrevPage() +{ + MeasureBase* m = score()->selection().endMeasureBase(); + System* sys = m ? m->system() : nullptr; + if (!sys) { + return; + } + MeasureBase* lastMb = sys->last(); + startEdit(TranslatableString("undoableAction", "Move system to previous page")); + EditPageLocks::moveMeasureToPrevPage(score(), lastMb); + apply(); +} + +void NotationInteraction::moveSystemToNextPage() +{ + MeasureBase* m = score()->selection().startMeasureBase(); + System* sys = m ? m->system() : nullptr; + if (!sys) { + return; + } + MeasureBase* firstMb = sys->first(); + startEdit(TranslatableString("undoableAction", "Move system to next page")); + EditPageLocks::moveMeasureToNextPage(score(), firstMb); + 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..357700cf9c18a 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 moveSystemToPrevPage() override; + void moveSystemToNextPage() 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..c54afa5e37396 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, moveSystemToPrevPage, (), (override)); + MOCK_METHOD(void, moveSystemToNextPage, (), (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..40c36c6d6127d 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-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); 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/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 1d0db146976f3..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,121 +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?.shortcutMoveMeasureUp ?? "" - - onClicked: { - root.model?.moveMeasureUp?.() - } - } - - FlatButton { - id: downSystem - - width: (moveSystemLayout.width - moveSystemLayout.spacing) / 2 - - navigation.panel: root.navigationPanel - navigation.name: "SystemDown" - navigation.row: upSystem.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 : "" - - onClicked: { - root.model?.moveMeasureDown?.() - } - } - } - } - - 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?.() - } - } } } diff --git a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.cpp b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.cpp index 426facecc7f45..667df969580be 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.cpp +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.cpp @@ -21,8 +21,6 @@ */ #include "measuressettingsmodel.h" -#include "engraving/dom/score.h" - #include "translation.h" using namespace mu::propertiespanel; @@ -40,10 +38,6 @@ MeasuresSettingsModel::MeasuresSettingsModel(QObject* parent, const muse::modula void MeasuresSettingsModel::loadProperties() { - updateAllSystemsAreLocked(); - updateScoreIsInPageView(); - updateIsMakeIntoSystemAvailable(); - updateSystemCount(); } bool MeasuresSettingsModel::shouldUpdateOnEmptyPropertyAndStyleIdSets() const @@ -53,7 +47,6 @@ bool MeasuresSettingsModel::shouldUpdateOnEmptyPropertyAndStyleIdSets() const void MeasuresSettingsModel::onNotationChanged(const engraving::PropertyIdSet&, const engraving::StyleIdSet&) { - loadProperties(); } bool MeasuresSettingsModel::isEmpty() const @@ -90,147 +83,3 @@ void MeasuresSettingsModel::deleteSelectedMeasures() currentNotation()->interaction()->removeSelectedMeasures(); } - -void MeasuresSettingsModel::moveMeasureUp() -{ - if (!currentNotation()) { - return; - } - - currentNotation()->interaction()->moveMeasureToPrevSystem(); -} - -QString MeasuresSettingsModel::shortcutMoveMeasureUp() const -{ - return shortcutsForActionCode("move-measure-to-prev-system"); -} - -void MeasuresSettingsModel::moveMeasureDown() -{ - if (!currentNotation()) { - return; - } - - currentNotation()->interaction()->moveMeasureToNextSystem(); -} - -QString MeasuresSettingsModel::shortcutMoveMeasureDown() 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"); -} - -bool MeasuresSettingsModel::allSystemsAreLocked() const -{ - return m_allSystemsAreLocked; -} - -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); - } -} - -bool MeasuresSettingsModel::scoreIsInPageView() const -{ - return m_scoreIsInPageView; -} - -bool MeasuresSettingsModel::isMakeIntoSystemAvailable() const -{ - return m_isMakeIntoSystemAvailable; -} - -int MeasuresSettingsModel::systemCount() const -{ - return static_cast(m_systemCount); -} - -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::updateSystemCount() -{ - if (isEmpty()) { - return; - } - - size_t count = selection()->selectedSystems().size(); - if (count != m_systemCount) { - m_systemCount = count; - emit systemCountChanged(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 b72684c10a430..6c239df0150c8 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.h +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.h @@ -33,15 +33,6 @@ 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 shortcutToggleSystemLock READ shortcutToggleSystemLock CONSTANT) - Q_PROPERTY(QString shortcutMakeIntoSystem READ shortcutMakeIntoSystem CONSTANT) - Q_PROPERTY(bool allSystemsAreLocked READ allSystemsAreLocked NOTIFY allSystemsAreLockedChanged) - Q_PROPERTY(bool scoreIsInPageView READ scoreIsInPageView NOTIFY scoreIsInPageViewChanged) - Q_PROPERTY(bool isMakeIntoSystemAvailable READ isMakeIntoSystemAvailable NOTIFY isMakeIntoSystemAvailableChanged) - Q_PROPERTY(int systemCount READ systemCount NOTIFY systemCountChanged) - public: explicit MeasuresSettingsModel(QObject* parent, const muse::modularity::ContextPtr& iocCtx, IElementRepositoryService* repository); @@ -63,43 +54,7 @@ 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 moveMeasureDown(); - QString shortcutMoveMeasureDown() const; - - Q_INVOKABLE void toggleSystemLock(); - QString shortcutToggleSystemLock() const; - bool allSystemsAreLocked() const; - - Q_INVOKABLE void makeIntoSystem(); - QString shortcutMakeIntoSystem() const; - - bool scoreIsInPageView() const; - bool isMakeIntoSystemAvailable() const; - - int systemCount() const; - protected: void onNotationChanged(const mu::engraving::PropertyIdSet&, const mu::engraving::StyleIdSet&) override; - -private: - void updateAllSystemsAreLocked(); - void updateScoreIsInPageView(); - void updateIsMakeIntoSystemAvailable(); - void updateSystemCount(); - -signals: - void allSystemsAreLockedChanged(bool allLocked); - void scoreIsInPageViewChanged(bool isInPageView); - void isMakeIntoSystemAvailableChanged(bool isMakeIntoSystemAvailable); - void systemCountChanged(int count); - -private: - bool m_allSystemsAreLocked = false; - bool m_scoreIsInPageView = false; - bool m_isMakeIntoSystemAvailable = false; - size_t m_systemCount = 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..35c85969e7a51 --- /dev/null +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/systemlayoutsettingsmodel.cpp @@ -0,0 +1,331 @@ +/* + * 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() +{ + if (!currentNotation()) { + return; + } + + 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; +}; +}