diff --git a/src/engraving/CMakeLists.txt b/src/engraving/CMakeLists.txt index 246eb40f8767a..23d2e014685e9 100644 --- a/src/engraving/CMakeLists.txt +++ b/src/engraving/CMakeLists.txt @@ -144,6 +144,8 @@ target_sources(engraving PRIVATE editing/editmeasures.h editing/editnote.cpp editing/editnote.h + editing/editpagelocks.cpp + editing/editpagelocks.h editing/editparentheses.cpp editing/editparentheses.h editing/editpart.cpp diff --git a/src/engraving/api/v1/apitypes.h b/src/engraving/api/v1/apitypes.h index fc6e756b9842f..4b39e69b21cf5 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 ff8e83261bdd8..812153b4338f8 100644 --- a/src/engraving/api/v1/elements.cpp +++ b/src/engraving/api/v1/elements.cpp @@ -505,11 +505,11 @@ void System::setIsLocked(bool locked) return; } Transaction& tx = system()->score()->transactionManager()->currentOrDummyTransaction(); - const mu::engraving::SystemLock* currentLock = system()->systemLock(); + const mu::engraving::RangeLock* currentLock = system()->systemLock(); if (currentLock && !locked) { - EditSystemLocks::undoRemoveSystemLock(tx, currentLock); + EditSystemLocks::undoRemoveSystemLock(tx, system()->score(), currentLock); } else if (!currentLock && locked) { - EditSystemLocks::undoAddSystemLock(tx, system()->score(), new mu::engraving::SystemLock(system()->first(), system()->last())); + EditSystemLocks::undoAddSystemLock(tx, 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 93cf9a958f248..288ef9eb049c2 100644 --- a/src/engraving/dom/dom.cmake +++ b/src/engraving/dom/dom.cmake @@ -215,6 +215,8 @@ set(DOM_SRC ${CMAKE_CURRENT_LIST_DIR}/ottava.h ${CMAKE_CURRENT_LIST_DIR}/page.cpp ${CMAKE_CURRENT_LIST_DIR}/page.h + ${CMAKE_CURRENT_LIST_DIR}/pagelockindicator.cpp + ${CMAKE_CURRENT_LIST_DIR}/pagelockindicator.h ${CMAKE_CURRENT_LIST_DIR}/parenthesis.cpp ${CMAKE_CURRENT_LIST_DIR}/parenthesis.h ${CMAKE_CURRENT_LIST_DIR}/partialtie.cpp @@ -237,6 +239,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 @@ -316,12 +320,12 @@ 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 ${CMAKE_CURRENT_LIST_DIR}/systemdivider.h + ${CMAKE_CURRENT_LIST_DIR}/systemlockindicator.cpp + ${CMAKE_CURRENT_LIST_DIR}/systemlockindicator.h ${CMAKE_CURRENT_LIST_DIR}/systemtext.cpp ${CMAKE_CURRENT_LIST_DIR}/systemtext.h ${CMAKE_CURRENT_LIST_DIR}/tapping.cpp diff --git a/src/engraving/dom/engravingobject.h b/src/engraving/dom/engravingobject.h index 27baf779d7ad8..8eeab6ef27f7a 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; @@ -438,6 +439,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) @@ -587,7 +589,7 @@ class EngravingObject return isArticulationFamily() || isFermata(); } - bool isIndicatorIcon() const { return isSystemLockIndicator() || isStaffVisibilityIndicator(); } + bool isIndicatorIcon() const { return isSystemLockIndicator() || isPageLockIndicator() || isStaffVisibilityIndicator(); } }; //--------------------------------------------------- @@ -770,6 +772,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 d3148f1c9cc1e..c968fb0e9ebb1 100644 --- a/src/engraving/dom/factory.cpp +++ b/src/engraving/dom/factory.cpp @@ -75,6 +75,7 @@ #include "ornament.h" #include "ottava.h" #include "page.h" +#include "pagelockindicator.h" #include "palmmute.h" #include "parenthesis.h" #include "partialtie.h" @@ -102,7 +103,7 @@ #include "stringtunings.h" #include "system.h" #include "systemdivider.h" -#include "systemlock.h" +#include "systemlockindicator.h" #include "systemtext.h" #include "soundflag.h" #include "tapping.h" @@ -302,6 +303,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: @@ -477,6 +479,15 @@ MAKE_ITEM_IMPL(NoteLine, Note); CREATE_ITEM_IMPL(Page, RootItem, isAccessibleEnabled) +PageLockIndicator* Factory::createPageLockIndicator(System * parent, const RangeLock * lock, bool isAccessibleEnabled) +{ + PageLockIndicator* pli = new PageLockIndicator(parent, lock); + pli->setAccessibleEnabled(isAccessibleEnabled); + return pli; +} + +COPY_ITEM_IMPL(PageLockIndicator) + CREATE_ITEM_IMPL(PartialTie, Note, isAccessibleEnabled) COPY_ITEM_IMPL(PartialTie) @@ -747,7 +758,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 8ee2c7b58877a..3a6cb03e7621e 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..b16092eaf1440 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 = page(); + 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 = page(); + 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::firstMeasureBaseAtTick(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..5110a833c9240 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* firstMeasureBaseAtTick(int tick) const; std::vector measureBasesAtTick(int tick) const; private: diff --git a/src/engraving/dom/page.cpp b/src/engraving/dom/page.cpp index 2bd2085452770..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/pagelockindicator.cpp b/src/engraving/dom/pagelockindicator.cpp new file mode 100644 index 0000000000000..79a60b1f1a74a --- /dev/null +++ b/src/engraving/dom/pagelockindicator.cpp @@ -0,0 +1,57 @@ +/* + * 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 "pagelockindicator.h" + +#include "measure.h" +#include "measurebase.h" +#include "page.h" +#include "rangelock.h" +#include "system.h" + +namespace mu::engraving { +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/pagelockindicator.h b/src/engraving/dom/pagelockindicator.h new file mode 100644 index 0000000000000..957877e7ca824 --- /dev/null +++ b/src/engraving/dom/pagelockindicator.h @@ -0,0 +1,55 @@ +/* + * 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 "indicatoricon.h" + +namespace mu::engraving { +class RangeLock; + +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/rangelock.cpp b/src/engraving/dom/rangelock.cpp new file mode 100644 index 0000000000000..b0b6140d31b3b --- /dev/null +++ b/src/engraving/dom/rangelock.cpp @@ -0,0 +1,134 @@ +/* + * 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 "measure.h" +#include "measurebase.h" +#include "rangelock.h" + +#include "log.h" + +namespace mu::engraving { +bool RangeLock::contains(const MeasureBase* mb) const +{ + return m_startMB->isBeforeOrEqual(mb) && mb->isBeforeOrEqual(m_endMB); +} + +void RangeLocks::add(const RangeLock* lock) +{ + m_rangeLocks.emplace(lock->startMB(), lock); + +#ifndef NDEBUG + sanityCheck(); +#endif +} + +void RangeLocks::remove(const RangeLock* lock) +{ + m_rangeLocks.erase(lock->startMB()); + +#ifndef NDEBUG + sanityCheck(); +#endif +} + +const RangeLock* RangeLocks::lockStartingAt(const MeasureBase* mb) const +{ + auto iter = m_rangeLocks.find(mb); + return iter != m_rangeLocks.end() ? iter->second : nullptr; +} + +const RangeLock* RangeLocks::lockContaining(const MeasureBase* mb) const +{ + if (m_rangeLocks.empty()) { + return nullptr; + } + + auto iter = m_rangeLocks.lower_bound(mb); + if (iter != m_rangeLocks.begin() + && (iter == m_rangeLocks.end() || mb->isBefore(iter->second->startMB()))) { + --iter; + } + + const RangeLock* lock = iter->second; + + return lock->contains(mb) ? lock : nullptr; +} + +std::vector RangeLocks::locksContainedInRange(const MeasureBase* start, const MeasureBase* end) const +{ + std::vector result; + + for (auto& pair : m_rangeLocks) { + const RangeLock* lock = pair.second; + if (start->isBeforeOrEqual(lock->startMB()) && lock->endMB()->isBeforeOrEqual(end)) { + result.push_back(lock); + } + if (lock->startMB()->isAfter(end)) { + break; + } + } + + return result; +} + +std::vector RangeLocks::allLocks() const +{ + return muse::values(m_rangeLocks); +} + +#ifndef NDEBUG +void RangeLocks::sanityCheck() +{ + for (auto iter = m_rangeLocks.begin(); iter != m_rangeLocks.end();) { + auto curIter = iter; + auto nextIter = ++iter; + if (nextIter == m_rangeLocks.end()) { + break; + } + + const MeasureBase* curMB = curIter->first; + const RangeLock* curLock = curIter->second; + + DO_ASSERT(curLock->startMB() == curMB); + + const MeasureBase* nextMB = nextIter->first; + const RangeLock* nextLock = nextIter->second; + + DO_ASSERT(nextLock->startMB() == nextMB); + DO_ASSERT(curMB->isBefore(nextMB)); + DO_ASSERT(curLock->endMB()->isBefore(nextLock->startMB())); + } +} + +void RangeLocks::dump() const +{ + 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() << "RangeLock --- Start measure: " << (startMeasure ? startMeasure->measureNumber() : -1) + << ", End Measure: " << (endMeasure ? endMeasure->measureNumber() : -1); + } +} + +#endif +} // namespace mu::engraving diff --git a/src/engraving/dom/systemlock.h b/src/engraving/dom/rangelock.h similarity index 52% rename from src/engraving/dom/systemlock.h rename to src/engraving/dom/rangelock.h index 0d46f500a025e..8cf814eebebd7 100644 --- a/src/engraving/dom/systemlock.h +++ b/src/engraving/dom/rangelock.h @@ -24,18 +24,15 @@ #include -#include "indicatoricon.h" #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 +46,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,26 +74,6 @@ class SystemLocks } }; - std::map m_systemLocks; -}; - -class SystemLockIndicator : public IndicatorIcon -{ - OBJECT_ALLOCATOR(engraving, SystemLockIndicator) - DECLARE_CLASSOF(ElementType::SYSTEM_LOCK_INDICATOR) - -public: - SystemLockIndicator(System* parent, const SystemLock* lock); - - void setSelected(bool v) override; - - const SystemLock* systemLock() const { return m_systemLock; } - - char16_t iconCode() const override { return 0xF487; } - - String formatBarsAndBeats() const override; - -private: - const SystemLock* m_systemLock = nullptr; + std::map m_rangeLocks; }; } // namespace mu::engraving diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index a8399daeba8bd..e20ab54fd102d 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -271,6 +271,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(); @@ -1918,8 +1921,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->tick2measure(startTick); + MeasureBase* emb = score->tick2measure(endTick); Fraction curTick = tickOfAppend; for (MeasureBase* cmb = fmb; cmb != emb; cmb = cmb->next()) { MeasureBase* nmb; @@ -5184,7 +5187,7 @@ void Score::autoUpdateSpatium() createPaddingTable(); } -void Score::addSystemLock(const SystemLock* lock) +void Score::addSystemLock(const RangeLock* lock) { m_systemLocks.add(lock); @@ -5192,7 +5195,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); @@ -5200,6 +5203,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 efc3e4e210198..ba10068c9f5be 100644 --- a/src/engraving/dom/score.h +++ b/src/engraving/dom/score.h @@ -62,7 +62,7 @@ #include "select.h" #include "spannermap.h" #include "synthesizerstate.h" -#include "systemlock.h" +#include "rangelock.h" #include "tuplet.h" namespace mu::engraving { @@ -862,11 +862,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; } @@ -958,7 +962,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/segment.cpp b/src/engraving/dom/segment.cpp index ad7df7c4450f9..498ac7193ee19 100644 --- a/src/engraving/dom/segment.cpp +++ b/src/engraving/dom/segment.cpp @@ -53,6 +53,7 @@ #include "staff.h" #include "staffstate.h" #include "system.h" +#include "systemlockindicator.h" #include "timesig.h" #include "tuplet.h" #include "utils.h" diff --git a/src/engraving/dom/select.cpp b/src/engraving/dom/select.cpp index 8755d3e6c3fdc..a0640ba92ef68 100644 --- a/src/engraving/dom/select.cpp +++ b/src/engraving/dom/select.cpp @@ -55,6 +55,7 @@ #include "mscore.h" #include "note.h" #include "notedot.h" +#include "pagelockindicator.h" #include "part.h" #include "partialtie.h" #include "rest.h" @@ -66,6 +67,7 @@ #include "stem.h" #include "stemslash.h" #include "sticking.h" +#include "system.h" #include "tie.h" #include "tremolosinglechord.h" #include "tremolotwochord.h" @@ -364,7 +366,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 +379,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 +399,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 +415,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 +426,25 @@ std::vector Selection::selectedSystems() const return systems; } +std::vector mu::engraving::Selection::pagesContainingSelection() 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 33696c8b5fcf0..2977c3ff58404 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 pagesContainingSelection() const; void update(); void updateState(); void dump(); diff --git a/src/engraving/dom/system.cpp b/src/engraving/dom/system.cpp index a16e0a851a58e..e7dbe5ae14ac7 100644 --- a/src/engraving/dom/system.cpp +++ b/src/engraving/dom/system.cpp @@ -40,6 +40,7 @@ #include "measure.h" #include "mscore.h" #include "page.h" +#include "pagelockindicator.h" #include "part.h" #include "score.h" #include "segment.h" @@ -49,6 +50,7 @@ #include "staffvisibilityindicator.h" #include "system.h" #include "systemdivider.h" +#include "systemlockindicator.h" #ifndef ENGRAVING_NO_ACCESSIBILITY #include "accessibility/accessibleitem.h" @@ -161,6 +163,9 @@ System::~System() if (m_staffVisibilityIndicator) { delete m_staffVisibilityIndicator; } + if (m_pageLockIndicator) { + delete m_pageLockIndicator; + } } #ifndef ENGRAVING_NO_ACCESSIBILITY @@ -316,7 +321,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(); } @@ -333,6 +338,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 //--------------------------------------------------------- @@ -726,6 +742,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/systemlock.cpp b/src/engraving/dom/systemlock.cpp deleted file mode 100644 index 53b5fff272d06..0000000000000 --- a/src/engraving/dom/systemlock.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* - * 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 "measure.h" -#include "measurebase.h" -#include "page.h" -#include "score.h" -#include "system.h" -#include "systemlock.h" - -#include "log.h" - -using namespace muse::draw; - -namespace mu::engraving { -bool SystemLock::contains(const MeasureBase* mb) const -{ - return m_startMB->isBeforeOrEqual(mb) && mb->isBeforeOrEqual(m_endMB); -} - -void SystemLocks::add(const SystemLock* lock) -{ - m_systemLocks.emplace(lock->startMB(), lock); - -#ifndef NDEBUG - sanityCheck(); -#endif -} - -void SystemLocks::remove(const SystemLock* lock) -{ - m_systemLocks.erase(lock->startMB()); - -#ifndef NDEBUG - sanityCheck(); -#endif -} - -const SystemLock* SystemLocks::lockStartingAt(const MeasureBase* mb) const -{ - auto iter = m_systemLocks.find(mb); - return iter != m_systemLocks.end() ? iter->second : nullptr; -} - -const SystemLock* SystemLocks::lockContaining(const MeasureBase* mb) const -{ - if (m_systemLocks.empty()) { - return nullptr; - } - - auto iter = m_systemLocks.lower_bound(mb); - if (iter != m_systemLocks.begin() - && (iter == m_systemLocks.end() || mb->isBefore(iter->second->startMB()))) { - --iter; - } - - const SystemLock* lock = iter->second; - - return lock->contains(mb) ? lock : nullptr; -} - -std::vector SystemLocks::locksContainedInRange(const MeasureBase* start, const MeasureBase* end) const -{ - std::vector result; - - for (auto& pair : m_systemLocks) { - const SystemLock* lock = pair.second; - if (start->isBeforeOrEqual(lock->startMB()) && lock->endMB()->isBeforeOrEqual(end)) { - result.push_back(lock); - } - if (lock->startMB()->isAfter(end)) { - break; - } - } - - return result; -} - -std::vector SystemLocks::allLocks() const -{ - std::vector locks; - locks.reserve(m_systemLocks.size()); - for (auto& pair : m_systemLocks) { - locks.push_back(pair.second); - } - return locks; -} - -#ifndef NDEBUG -void SystemLocks::sanityCheck() -{ - for (auto iter = m_systemLocks.begin(); iter != m_systemLocks.end();) { - auto curIter = iter; - auto nextIter = ++iter; - if (nextIter == m_systemLocks.end()) { - break; - } - - const MeasureBase* curMB = curIter->first; - const SystemLock* curSysLock = curIter->second; - - DO_ASSERT(curSysLock->startMB() == curMB); - - const MeasureBase* nextMB = nextIter->first; - const SystemLock* nextSysLock = nextIter->second; - - DO_ASSERT(nextSysLock->startMB() == nextMB); - DO_ASSERT(curMB->isBefore(nextMB)); - DO_ASSERT(curSysLock->endMB()->isBefore(nextSysLock->startMB())); - } -} - -void SystemLocks::dump() -{ - for (auto& pair : m_systemLocks) { - const SystemLock* 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) - << ", End Measure: " << (endMeasure ? endMeasure->measureNumber() : -1); - } -} - -#endif - -SystemLockIndicator::SystemLockIndicator(System* parent, const SystemLock* lock) - : IndicatorIcon(ElementType::SYSTEM_LOCK_INDICATOR, parent, ElementFlag::SYSTEM | ElementFlag::GENERATED), m_systemLock(lock) {} - -void SystemLockIndicator::setSelected(bool v) -{ - EngravingItem::setSelected(v); - renderer()->layoutItem(this); - system()->page()->invalidateBspTree(); -} - -String SystemLockIndicator::formatBarsAndBeats() const -{ - const MeasureBase* startMB = m_systemLock->startMB(); - const MeasureBase* endMB = m_systemLock->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/systemlockindicator.cpp b/src/engraving/dom/systemlockindicator.cpp new file mode 100644 index 0000000000000..b28bcb39bde5e --- /dev/null +++ b/src/engraving/dom/systemlockindicator.cpp @@ -0,0 +1,52 @@ +/* + * 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 "systemlockindicator.h" + +#include "measure.h" +#include "measurebase.h" +#include "page.h" +#include "rangelock.h" +#include "system.h" + +namespace mu::engraving { +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) +{ + EngravingItem::setSelected(v); + renderer()->layoutItem(this); + system()->page()->invalidateBspTree(); +} + +String SystemLockIndicator::formatBarsAndBeats() const +{ + const MeasureBase* startMB = m_systemLock->startMB(); + const MeasureBase* endMB = m_systemLock->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/systemlockindicator.h b/src/engraving/dom/systemlockindicator.h new file mode 100644 index 0000000000000..186359339b8f0 --- /dev/null +++ b/src/engraving/dom/systemlockindicator.h @@ -0,0 +1,49 @@ +/* + * 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 "indicatoricon.h" + +namespace mu::engraving { +class RangeLock; + +class SystemLockIndicator : public IndicatorIcon +{ + OBJECT_ALLOCATOR(engraving, SystemLockIndicator) + DECLARE_CLASSOF(ElementType::SYSTEM_LOCK_INDICATOR) + +public: + SystemLockIndicator(System* parent, const RangeLock* lock); + + void setSelected(bool v) override; + + const RangeLock* systemLock() const { return m_systemLock; } + + char16_t iconCode() const override { return 0xF487; } + + String formatBarsAndBeats() const override; + +private: + const RangeLock* m_systemLock = nullptr; +}; +} // namespace mu::engraving diff --git a/src/engraving/dom/utils.cpp b/src/engraving/dom/utils.cpp index 5a82ae6ac0c55..48e90d545e57c 100644 --- a/src/engraving/dom/utils.cpp +++ b/src/engraving/dom/utils.cpp @@ -117,23 +117,16 @@ Measure* Score::tick2measureMM(const Fraction& t) const return measure->coveringMMRestOrThis(); } -//--------------------------------------------------------- -// tick2measureBase -//--------------------------------------------------------- - MeasureBase* Score::tick2measureBase(const Fraction& tick) const { - std::vector mbList = m_measures.measureBasesAtTick(tick.ticks()); - for (MeasureBase* mb : mbList) { - Fraction st = mb->tick(); - Fraction l = mb->ticks(); - if (tick >= st && tick < (st + l)) { - return mb; - } + if (tick == Fraction(-1, 1)) { // special number + return m_measures.last(); + } + if (tick <= Fraction(0, 1)) { + return m_measures.first(); } - LOGD("tick2measureBase %d not found", tick.ticks()); - return nullptr; + return m_measures.firstMeasureBaseAtTick(tick.ticks()); } //--------------------------------------------------------- diff --git a/src/engraving/editing/cmd.cpp b/src/engraving/editing/cmd.cpp index 1208a30971f05..bae85cde09f37 100644 --- a/src/engraving/editing/cmd.cpp +++ b/src/engraving/editing/cmd.cpp @@ -87,6 +87,7 @@ #include "editchord.h" #include "editnote.h" #include "noteinput.h" +#include "editpagelocks.h" #include "editproperty.h" #include "editspanner.h" #include "editstaff.h" @@ -1818,6 +1819,7 @@ void Score::cmdToggleLayoutBreak(LayoutBreakType type) val = !mb->lineBreak(); if (val) { EditSystemLocks::removeSystemLocksOnAddLayoutBreak(tx, this, type, mb); + EditPageLocks::removePageLocksOnAddLayoutBreak(tx, this, type, mb); } mb->undoSetBreak(val, type); // remove page break if appropriate @@ -1829,6 +1831,7 @@ void Score::cmdToggleLayoutBreak(LayoutBreakType type) val = !mb->pageBreak(); if (val) { EditSystemLocks::removeSystemLocksOnAddLayoutBreak(tx, this, type, mb); + EditPageLocks::removePageLocksOnAddLayoutBreak(tx, this, type, mb); } mb->undoSetBreak(val, type); // remove line break if appropriate @@ -1840,6 +1843,7 @@ void Score::cmdToggleLayoutBreak(LayoutBreakType type) val = !mb->sectionBreak(); if (val) { EditSystemLocks::removeSystemLocksOnAddLayoutBreak(tx, this, type, mb); + EditPageLocks::removePageLocksOnAddLayoutBreak(tx, this, type, mb); } mb->undoSetBreak(val, type); break; diff --git a/src/engraving/editing/edit.cpp b/src/engraving/editing/edit.cpp index bf9677b240ea4..c7b585fede152 100644 --- a/src/engraving/editing/edit.cpp +++ b/src/engraving/editing/edit.cpp @@ -64,6 +64,7 @@ #include "../dom/note.h" #include "../dom/noteline.h" #include "../dom/ottava.h" +#include "../dom/pagelockindicator.h" #include "../dom/part.h" #include "../dom/partialtie.h" #include "../dom/playcounttext.h" @@ -80,6 +81,7 @@ #include "../dom/sticking.h" #include "../dom/stringtunings.h" #include "../dom/system.h" +#include "../dom/systemlockindicator.h" #include "../dom/systemtext.h" #include "../dom/tapping.h" #include "../dom/tempotext.h" @@ -103,6 +105,7 @@ #include "editkeysig.h" #include "editmeasures.h" #include "editnote.h" +#include "editpagelocks.h" #include "editpart.h" #include "editproperty.h" #include "editrehearsalmark.h" @@ -1068,6 +1071,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: @@ -1552,8 +1556,8 @@ void Score::deleteItem(EngravingItem* el) break; case ElementType::SYSTEM_LOCK_INDICATOR: { - const SystemLock* systemLock = toSystemLockIndicator(el)->systemLock(); - EditSystemLocks::undoRemoveSystemLock(tx, systemLock); + const RangeLock* systemLock = toSystemLockIndicator(el)->systemLock(); + EditSystemLocks::undoRemoveSystemLock(tx, this, systemLock); } break; case ElementType::PARENTHESIS: { @@ -1574,6 +1578,13 @@ void Score::deleteItem(EngravingItem* el) break; } } + break; + case ElementType::PAGE_LOCK_INDICATOR: + { + const RangeLock* pageLock = toPageLockIndicator(el)->pageLock(); + EditPageLocks::undoRemovePageLock(tx, this, pageLock); + } + break; default: undoRemoveElement(el); @@ -2697,7 +2708,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 = tick2measure(masterInsertMeasure->tick()); } } else { localInsertMeasureBase = insertBox(type, beforeMeasure, options); @@ -5177,6 +5188,7 @@ void Score::undoRemoveMeasures(Measure* m1, Measure* m2, bool preserveTies, bool } EditSystemLocks::removeSystemLocksOnRemoveMeasures(tx, this, m1, m2); + EditPageLocks::removePageLocksOnRemoveMeasures(tx, 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..a90aed3ab76d4 --- /dev/null +++ b/src/engraving/editing/editpagelocks.cpp @@ -0,0 +1,528 @@ +/* + * 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 "editsystemlocks.h" + +#include "transaction/transaction.h" +#include "transaction/undoablecommand.h" + +#include "../dom/measurebase.h" +#include "../dom/page.h" +#include "../dom/pagelockindicator.h" +#include "../dom/rangelock.h" +#include "../dom/score.h" +#include "../dom/system.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() override + { + Score* score = m_pageLock->startMB()->score(); + score->removePageLock(m_pageLock); + } + + void redo() 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() override + { + Score* score = m_pageLock->startMB()->score(); + score->addPageLock(m_pageLock); + } + + void redo() 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(Transaction& tx, Score* score, const RangeLock* lock) +{ + removeLayoutBreaksOnAddPageLock(tx, lock); + score->undo(new AddPageLock(lock)); +} + +void EditPageLocks::undoRemovePageLock(Transaction& tx, Score* score, const RangeLock* lock) +{ + tx.push(new RemovePageLock(lock)); +} + +void EditPageLocks::undoRemoveAllLocks(Transaction& tx, Score* score) +{ + std::vector allLocks = score->pageLocks()->allLocks(); // copy + for (const RangeLock* lock : allLocks) { + undoRemovePageLock(tx, score, lock); + } +} + +void EditPageLocks::togglePageLock(Transaction& tx, 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(tx, score, currentLock); + continue; + } else if (!currentLock && !unlockAll) { + RangeLock* newPageLock = new RangeLock(startMeas, page->lastMeasureBase()); + undoAddPageLock(tx, score, newPageLock); + } + } +} + +void EditPageLocks::toggleScoreLock(Transaction& tx, 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(tx, score, currentLock); + continue; + } else if (!currentLock && !unlockAll) { + RangeLock* newPageLock = new RangeLock(startMeas, page->lastMeasureBase()); + undoAddPageLock(tx, score, newPageLock); + } + } +} + +void EditPageLocks::addRemovePageLocks(Transaction& tx, 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(tx, score, new RangeLock(page->firstMeasureBase(), page->lastMeasureBase())); + } + } + return; + } + + std::vector currentLocks = score->pageLocks()->locksContainedInRange(startMeasure, endMeasure); + for (const RangeLock* l : currentLocks) { + undoRemovePageLock(tx, 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(tx, score, new RangeLock(lockStart, mb)); + lockStart = nullptr; + count = 0; + } + if (mb == endMeasure) { + break; + } + } +} + +void EditPageLocks::makeIntoPage(Transaction& tx, Score* score, MeasureBase* first, MeasureBase* last) +{ + const RangeLock* lockContainingFirst = score->pageLocks()->lockContaining(first); + const RangeLock* lockContainingLast = score->pageLocks()->lockContaining(last); + + if (lockContainingFirst) { + undoRemovePageLock(tx, score, lockContainingFirst); + if (lockContainingFirst->startMB()->isBefore(first)) { + MeasureBase* oneBeforeFirst = first->prevMM(); + RangeLock* newLockBefore = new RangeLock(lockContainingFirst->startMB(), oneBeforeFirst); + undoAddPageLock(tx, score, newLockBefore); + } + } + + if (lockContainingLast) { + if (lockContainingLast != lockContainingFirst) { + undoRemovePageLock(tx, score, lockContainingLast); + } + if (last->isBefore(lockContainingLast->endMB())) { + MeasureBase* oneAfterLast = last->nextMM(); + RangeLock* newLockAfter = new RangeLock(oneAfterLast, lockContainingLast->endMB()); + undoAddPageLock(tx, score, newLockAfter); + } + } + + std::vector locksContainedInRange = score->pageLocks()->locksContainedInRange(first, last); + for (const RangeLock* lock : locksContainedInRange) { + if (lock != lockContainingFirst && lock != lockContainingLast) { + undoRemovePageLock(tx, score, lock); + } + } + + RangeLock* newLock = new RangeLock(first, last); + undoAddPageLock(tx, score, newLock); +} + +void EditPageLocks::moveMeasuresToPrevPage(Transaction& tx, Score* score, MeasureBase* first, MeasureBase* last) +{ + const Page* prevPage = last->prevPage(); + if (!prevPage) { + return; + } + + // Lock systems we are moving to the previous page + std::vector systemsToLock; + System* curSystem = nullptr; + for (MeasureBase* curMeasure = last; curMeasure && curMeasure != first->prev(); curMeasure = curMeasure->prev()) { + if (curMeasure->system() == curSystem) { + continue; + } + curSystem = curMeasure->system(); + + if (!curSystem->isLocked()) { + systemsToLock.push_back(curSystem); + } + } + EditSystemLocks::toggleSystemLock(tx, score, systemsToLock); + + MeasureBase* prevPageFirstMeas = prevPage->firstMeasureBase(); + + const RangeLock* prevPageLock = score->pageLocks()->lockStartingAt(prevPageFirstMeas); + if (prevPageLock) { + undoRemovePageLock(tx, score, prevPageLock); + } + + const Page* curPage = last->page(); + const RangeLock* curPageLock = score->pageLocks()->lockStartingAt(curPage->firstMeasureBase()); + if (curPageLock) { + undoRemovePageLock(tx, score, curPageLock); + if (curPageLock->endMB() != last) { + MeasureBase* nextMB = last->nextMM(); + RangeLock* newLockOnCurPage = new RangeLock(nextMB, curPageLock->endMB()); + undoAddPageLock(tx, score, newLockOnCurPage); + } + } + + RangeLock* pageLock = new RangeLock(prevPageFirstMeas, last); + undoAddPageLock(tx, score, pageLock); +} + +void EditPageLocks::moveMeasuresToNextPage(Transaction& tx, Score* score, MeasureBase* first, MeasureBase* last) +{ + const Page* curPage = first->page(); + MeasureBase* startMeas = curPage->firstMeasureBase(); + bool refMeasureIsStartOfPage = first == startMeas; + + const Page* nextPage = first->nextPage(); + if (!nextPage) { + return; + } + + // Lock systems we are moving to the next page + std::vector systemsToLock; + System* curSystem = nullptr; + for (MeasureBase* curMeasure = first; curMeasure && curMeasure != last->next(); + curMeasure = curMeasure->next()) { + if (curMeasure->system() == curSystem) { + continue; + } + curSystem = curMeasure->system(); + + if (!curSystem->isLocked()) { + systemsToLock.push_back(curSystem); + } + } + EditSystemLocks::toggleSystemLock(tx, score, systemsToLock); + + const RangeLock* curLock = score->pageLocks()->lockStartingAt(startMeas); + if (curLock) { + undoRemovePageLock(tx, score, curLock); + } + + if (!refMeasureIsStartOfPage) { + MeasureBase* prevMeas = first->prevMM(); + if (!curLock) { + prevMeas->undoSetBreak(true, LayoutBreakType::PAGE); + } else { + RangeLock* pageLock = new RangeLock(startMeas, prevMeas); + undoAddPageLock(tx, score, pageLock); + } + } + + const RangeLock* nextPageLock = score->pageLocks()->lockStartingAt(nextPage->firstMeasureBase()); + if (nextPageLock) { + undoRemovePageLock(tx, score, nextPageLock); + } + + if (nextPageLock || refMeasureIsStartOfPage) { + RangeLock* newNextPageLock = new RangeLock(first, nextPage->lastMeasureBase()); + undoAddPageLock(tx, score, newNextPageLock); + } +} + +void EditPageLocks::applyLockToSelection(Transaction& tx, 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(tx, score, lockOnLast); + } else if (first != last) { + makeIntoPage(tx, score, first, last); + } else { + makeIntoPage(tx, score, first->page()->firstMeasureBase(), last); + } +} + +void EditPageLocks::removePageLocksOnAddLayoutBreak(Transaction& tx, 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); + MeasureBase* lockEndMeasure = lock ? lock->endMB() : nullptr; + if (lock && breakType == LayoutBreakType::PAGE) { + undoRemovePageLock(tx, score, lock); + + if (measure != lockEndMeasure && measure->next()) { + // Make sure the resultant page is locked + undoAddPageLock(tx, score, new RangeLock(measure->next(), lockEndMeasure)); + } + } +} + +void EditPageLocks::removeLayoutBreaksOnAddPageLock(Transaction&, const RangeLock* lock) +{ + for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mb->nextMM()) { + mb->undoSetBreak(false, LayoutBreakType::PAGE); + } +} + +void EditPageLocks::removePageLocksOnRemoveMeasures(Transaction& tx, 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(tx, score, lock); + } + if (lockStartIsInRange && !lockEndIsInRange) { + MeasureBase* newLockStart = m2->nextMeasure(); + if (newLockStart) { + undoAddPageLock(tx, score, new RangeLock(newLockStart, lockEnd)); + } + } else if (!lockStartIsInRange && lockEndIsInRange) { + MeasureBase* newLockEnd = m1->prevMeasure(); + if (newLockEnd) { + undoAddPageLock(tx, score, new RangeLock(lockStart, newLockEnd)); + } + } + } +} + +void EditPageLocks::removePageLocksContainingMMRests(Transaction& tx, 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(tx, score, lock); + break; + } + if (mb->isAfter(lock->endMB())) { + break; + } + } + } +} + +void EditPageLocks::updatePageLocksOnCreateMMRests(Transaction& tx, 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(tx, 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(tx, score, lockOnFirst); + undoAddPageLock(tx, 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(tx, score, lockOnLast); + startMB = last->nextMM(); + undoAddPageLock(tx, 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..f41e64ab5632b --- /dev/null +++ b/src/engraving/editing/editpagelocks.h @@ -0,0 +1,62 @@ +/* + * 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 Transaction; +class RangeLock; + +enum class LayoutBreakType : unsigned char; + +class EditPageLocks +{ +public: + static void undoAddPageLock(Transaction& tx, Score* score, const RangeLock* lock); + static void undoRemovePageLock(Transaction& tx, Score* score, const RangeLock* lock); + static void undoRemoveAllLocks(Transaction& tx, Score* score); + + static void togglePageLock(Transaction& tx, Score* score, const std::vector& pages); + static void toggleScoreLock(Transaction& tx, Score* score); + + static void addRemovePageLocks(Transaction& tx, Score* score, int interval, bool lock); + + static void makeIntoPage(Transaction& tx, Score* score, MeasureBase* first, MeasureBase* last); + static void moveMeasuresToPrevPage(Transaction& tx, Score* score, MeasureBase* first, MeasureBase* last); + static void moveMeasuresToNextPage(Transaction& tx, Score* score, MeasureBase* first, MeasureBase* last); + + static void applyLockToSelection(Transaction& tx, Score* score); + + static void removeLayoutBreaksOnAddPageLock(Transaction& tx, const RangeLock* lock); + static void removePageLocksOnAddLayoutBreak(Transaction& tx, Score* score, LayoutBreakType breakType, const MeasureBase* measure); + static void removePageLocksOnRemoveMeasures(Transaction& tx, Score* score, const MeasureBase* m1, const MeasureBase* m2); + static void removePageLocksContainingMMRests(Transaction& tx, Score* score); + static void updatePageLocksOnCreateMMRests(Transaction& tx, Score* score, Measure* first, Measure* last); +}; +} diff --git a/src/engraving/editing/editstyle.cpp b/src/engraving/editing/editstyle.cpp index f391122ae3b4a..6fc0591270422 100644 --- a/src/engraving/editing/editstyle.cpp +++ b/src/engraving/editing/editstyle.cpp @@ -24,7 +24,7 @@ #include "../dom/chordlist.h" #include "../dom/score.h" - +#include "editing/editpagelocks.h" #include "editing/editsystemlocks.h" #include "editing/transaction/transaction.h" @@ -130,6 +130,7 @@ static void changeStyleValue(Score* score, Sid idx, const PropertyValue& oldValu if (oldValue.toBool() == true && newValue.toBool() == false) { Transaction& tx = score->transactionManager()->currentOrDummyTransaction(); EditSystemLocks::removeSystemLocksContainingMMRests(tx, score); + EditPageLocks::removePageLocksContainingMMRests(tx, score); } break; default: diff --git a/src/engraving/editing/editsystemlocks.cpp b/src/engraving/editing/editsystemlocks.cpp index 89c21422820f0..258119b9ef5a6 100644 --- a/src/engraving/editing/editsystemlocks.cpp +++ b/src/engraving/editing/editsystemlocks.cpp @@ -21,14 +21,16 @@ */ #include "editsystemlocks.h" +#include "editpagelocks.h" #include "transaction/transaction.h" #include "transaction/undoablecommand.h" #include "../dom/measurebase.h" +#include "../dom/rangelock.h" #include "../dom/score.h" #include "../dom/system.h" -#include "../dom/systemlock.h" +#include "../dom/systemlockindicator.h" using namespace mu::engraving; @@ -40,9 +42,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() override @@ -81,9 +83,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() override @@ -117,22 +119,22 @@ class RemoveSystemLock : public UndoableCommand // EditSystemLocks //--------------------------------------------------------- -void EditSystemLocks::undoAddSystemLock(Transaction& tx, Score* score, const SystemLock* lock) +void EditSystemLocks::undoAddSystemLock(Transaction& tx, Score* score, const RangeLock* lock) { - removeLayoutBreaksOnAddSystemLock(tx, score, lock); + updateLayoutBreaksOnAddSystemLock(tx, score, lock); tx.push(new AddSystemLock(lock)); } -void EditSystemLocks::undoRemoveSystemLock(Transaction& tx, const SystemLock* lock) +void EditSystemLocks::undoRemoveSystemLock(Transaction& tx, Score* score, const RangeLock* lock) { tx.push(new RemoveSystemLock(lock)); } void EditSystemLocks::undoRemoveAllLocks(Transaction& tx, Score* score) { - std::vector allLocks = score->systemLocks()->allLocks(); // copy - for (const SystemLock* lock : allLocks) { - undoRemoveSystemLock(tx, lock); + std::vector allLocks = score->systemLocks()->allLocks(); // copy + for (const RangeLock* lock : allLocks) { + undoRemoveSystemLock(tx, score, lock); } } @@ -148,12 +150,12 @@ void EditSystemLocks::toggleSystemLock(Transaction& tx, Score* score, const std: 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(tx, currentLock); + undoRemoveSystemLock(tx, score, currentLock); continue; } else if (!currentLock && !unlockAll) { - SystemLock* newSystemLock = new SystemLock(startMeas, system->last()); + RangeLock* newSystemLock = new RangeLock(startMeas, system->last()); undoAddSystemLock(tx, score, newSystemLock); } } @@ -178,12 +180,12 @@ void EditSystemLocks::toggleScoreLock(Transaction& tx, 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(tx, currentLock); + undoRemoveSystemLock(tx, score, currentLock); continue; } else if (!currentLock && !unlockAll) { - SystemLock* newSystemLock = new SystemLock(startMeas, system->last()); + RangeLock* newSystemLock = new RangeLock(startMeas, system->last()); undoAddSystemLock(tx, score, newSystemLock); } } @@ -191,12 +193,10 @@ void EditSystemLocks::toggleScoreLock(Transaction& tx, Score* score) void EditSystemLocks::addRemoveSystemLocks(Transaction& tx, 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) { @@ -212,15 +212,15 @@ void EditSystemLocks::addRemoveSystemLocks(Transaction& tx, Score* score, int in break; } if (!system->isLocked()) { - undoAddSystemLock(tx, score, new SystemLock(system->first(), system->last())); + undoAddSystemLock(tx, score, new RangeLock(system->first(), system->last())); } } return; } - std::vector currentLocks = score->systemLocks()->locksContainedInRange(startMeasure, endMeasure); - for (const SystemLock* l : currentLocks) { - undoRemoveSystemLock(tx, l); + std::vector currentLocks = score->systemLocks()->locksContainedInRange(startMeasure, endMeasure); + for (const RangeLock* l : currentLocks) { + undoRemoveSystemLock(tx, score, l); } if (interval == 0) { @@ -229,13 +229,13 @@ void EditSystemLocks::addRemoveSystemLocks(Transaction& tx, Score* score, int in 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(tx, score, new SystemLock(lockStart, mb)); + undoAddSystemLock(tx, score, new RangeLock(lockStart, mb)); lockStart = nullptr; count = 0; } @@ -247,39 +247,56 @@ void EditSystemLocks::addRemoveSystemLocks(Transaction& tx, Score* score, int in void EditSystemLocks::makeIntoSystem(Transaction& tx, 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(tx, lockContainingfirst); + undoRemoveSystemLock(tx, 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(tx, score, newLockBefore); } } if (lockContaininglast) { if (lockContaininglast != lockContainingfirst) { - undoRemoveSystemLock(tx, lockContaininglast); + undoRemoveSystemLock(tx, 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(tx, 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(tx, lock); + undoRemoveSystemLock(tx, score, lock); + } + } + + MeasureBase* firstMB = first; + MeasureBase* lastMB = last; + + // If a section break is within this range, create a new system lock after it + for (MeasureBase* mb = first; mb && mb->isBeforeOrEqual(last); mb = mb->nextMM()) { + if (!firstMB) { + firstMB = mb; + } + lastMB = mb; + + if (mb->sectionBreak()) { + RangeLock* newLock = new RangeLock(firstMB, lastMB); + undoAddSystemLock(tx, score, newLock); + + firstMB = nullptr; + lastMB = nullptr; } } - SystemLock* newLock = new SystemLock(first, last); + RangeLock* newLock = new RangeLock(firstMB, lastMB); undoAddSystemLock(tx, score, newLock); } @@ -292,59 +309,49 @@ void EditSystemLocks::moveMeasureToPrevSystem(Transaction& tx, Score* score, Mea MeasureBase* prevSystemFirstMeas = prevSystem->first(); - const SystemLock* prevSystemLock = score->systemLocks()->lockStartingAt(prevSystemFirstMeas); - if (prevSystemLock) { - undoRemoveSystemLock(tx, prevSystemLock); - } - - const System* curSystem = m->system(); - const SystemLock* curSystemLock = score->systemLocks()->lockStartingAt(curSystem->first()); - if (curSystemLock) { - undoRemoveSystemLock(tx, 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()); - undoAddSystemLock(tx, score, newLockOnCurSystem); - } - } - - SystemLock* sysLock = new SystemLock(prevSystemFirstMeas, m); - undoAddSystemLock(tx, score, sysLock); + makeIntoSystem(tx, score, prevSystemFirstMeas, m); } void EditSystemLocks::moveMeasureToNextSystem(Transaction& tx, Score* score, MeasureBase* m) { const System* curSystem = m->system(); MeasureBase* startMeas = curSystem->first(); - bool refMeasureIsStartOfSystem = m == startMeas; - - const SystemLock* curLock = score->systemLocks()->lockStartingAt(startMeas); - if (curLock) { - undoRemoveSystemLock(tx, curLock); - } - - if (!refMeasureIsStartOfSystem) { - bool mmrests = score->style().styleB(Sid::createMultiMeasureRests); - MeasureBase* prevMeas = mmrests ? m->prevMM() : m->prev(); - SystemLock* sysLock = new SystemLock(startMeas, prevMeas); - undoAddSystemLock(tx, score, sysLock); - } + MeasureBase* systemCurEndMeasure = curSystem->last(); const System* nextSystem = m->nextNonVBoxSystem(); if (!nextSystem) { return; } - const SystemLock* nextSysLock = score->systemLocks()->lockStartingAt(nextSystem->first()); - if (nextSysLock) { - undoRemoveSystemLock(tx, nextSysLock); + const RangeLock* curLock = score->systemLocks()->lockStartingAt(startMeas); + const RangeLock* nextSysLock = score->systemLocks()->lockStartingAt(nextSystem->first()); + + MeasureBase* prevMeas = m->prevMM(); + + if (!curLock && !nextSysLock) { + if (systemCurEndMeasure->pageBreak()) { + // Move page break to measure preceding selection + systemCurEndMeasure->undoSetBreak(false, LayoutBreakType::PAGE); + prevMeas->undoSetBreak(true, LayoutBreakType::PAGE); + } else { + // Add a system break + prevMeas->undoSetBreak(true, LayoutBreakType::LINE); + } + return; } - if (nextSysLock || refMeasureIsStartOfSystem) { - SystemLock* newNextSysLock = new SystemLock(m, nextSystem->last()); - undoAddSystemLock(tx, score, newNextSysLock); + if (curLock && !nextSysLock) { + undoRemoveSystemLock(tx, score, curLock); + + MeasureBase* firstMeasure = curSystem->first(); + MeasureBase* lastMeasure = m->prev(); + + undoAddSystemLock(tx, score, new RangeLock(firstMeasure, lastMeasure)); + return; } + + MeasureBase* lastMeasure = nextSystem->last(); + makeIntoSystem(tx, score, m, lastMeasure); } void EditSystemLocks::applyLockToSelection(Transaction& tx, Score* score) @@ -358,7 +365,7 @@ void EditSystemLocks::applyLockToSelection(Transaction& tx, 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; @@ -380,9 +387,9 @@ void EditSystemLocks::applyLockToSelection(Transaction& tx, Score* score) return; } - const SystemLock* lockOnLast = score->systemLocks()->lockContaining(last); + const RangeLock* lockOnLast = score->systemLocks()->lockContaining(last); if (lockOnLast && lockOnLast->endMB() == last) { - undoRemoveSystemLock(tx, lockOnLast); + undoRemoveSystemLock(tx, score, lockOnLast); } else if (first != last) { makeIntoSystem(tx, score, first, last); } else { @@ -397,45 +404,70 @@ void EditSystemLocks::removeSystemLocksOnAddLayoutBreak(Transaction& tx, Score* 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(tx, lock); + undoRemoveSystemLock(tx, score, lock); } } -void EditSystemLocks::removeLayoutBreaksOnAddSystemLock(Transaction&, Score* score, const SystemLock* lock) +void EditSystemLocks::updateLayoutBreaksOnAddSystemLock(Transaction& tx, 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); + } else { + // End of system lock + // System lock and no-break would conflict + mb->undoSetBreak(false, LayoutBreakType::NOBREAK); } + + 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(tx, score, pageLock); + + RangeLock* newPageLock = new RangeLock(pageLockStartMb, lock->endMB()); + EditPageLocks::undoAddPageLock(tx, score, newPageLock); + } + } + + if (moveSectionBreak) { + lock->endMB()->undoSetBreak(true, LayoutBreakType::SECTION); + } + if (movePageBreak) { + lock->endMB()->undoSetBreak(true, LayoutBreakType::PAGE); } } void EditSystemLocks::removeSystemLocksOnRemoveMeasures(Transaction& tx, 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); bool lockEndIsInRange = lockEnd->isAfterOrEqual(m1) && lockEnd->isBeforeOrEqual(m2); if (lockStartIsInRange || lockEndIsInRange) { - undoRemoveSystemLock(tx, lock); + undoRemoveSystemLock(tx, score, lock); } if (lockStartIsInRange && !lockEndIsInRange) { MeasureBase* newLockStart = m2->nextMeasure(); if (newLockStart) { - undoAddSystemLock(tx, score, new SystemLock(newLockStart, lockEnd)); + undoAddSystemLock(tx, score, new RangeLock(newLockStart, lockEnd)); } } else if (!lockStartIsInRange && lockEndIsInRange) { MeasureBase* newLockEnd = m1->prevMeasure(); if (newLockEnd) { - undoAddSystemLock(tx, score, new SystemLock(lockStart, newLockEnd)); + undoAddSystemLock(tx, score, new RangeLock(lockStart, newLockEnd)); } } } @@ -443,11 +475,11 @@ void EditSystemLocks::removeSystemLocksOnRemoveMeasures(Transaction& tx, Score* void EditSystemLocks::removeSystemLocksContainingMMRests(Transaction& tx, 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(tx, lock); + undoRemoveSystemLock(tx, score, lock); break; } if (mb->isAfter(lock->endMB())) { @@ -461,13 +493,13 @@ void EditSystemLocks::updateSystemLocksOnCreateMMRests(Transaction& tx, Score* s { // 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(tx, lock); + undoRemoveSystemLock(tx, 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(); @@ -484,8 +516,8 @@ void EditSystemLocks::updateSystemLocksOnCreateMMRests(Transaction& tx, Score* s } if (startMB != lockOnFirst->startMB() || endMB != lockOnFirst->endMB()) { - undoRemoveSystemLock(tx, lockOnFirst); - undoAddSystemLock(tx, score, new SystemLock(startMB, endMB)); + undoRemoveSystemLock(tx, score, lockOnFirst); + undoAddSystemLock(tx, score, new RangeLock(startMB, endMB)); } } @@ -497,7 +529,7 @@ void EditSystemLocks::updateSystemLocksOnCreateMMRests(Transaction& tx, Score* s MeasureBase* endMB = lockOnLast->endMB(); assert(startMB->isAfter(first) && endMB->isAfter(last)); - undoRemoveSystemLock(tx, lockOnLast); + undoRemoveSystemLock(tx, score, lockOnLast); startMB = last->nextMM(); - undoAddSystemLock(tx, score, new SystemLock(startMB, endMB)); + undoAddSystemLock(tx, score, new RangeLock(startMB, endMB)); } diff --git a/src/engraving/editing/editsystemlocks.h b/src/engraving/editing/editsystemlocks.h index 92e15e20693a5..262b132a75231 100644 --- a/src/engraving/editing/editsystemlocks.h +++ b/src/engraving/editing/editsystemlocks.h @@ -29,16 +29,16 @@ class Measure; class MeasureBase; class Score; class System; -class SystemLock; class Transaction; +class RangeLock; enum class LayoutBreakType : unsigned char; class EditSystemLocks { public: - static void undoAddSystemLock(Transaction& tx, Score* score, const SystemLock* lock); - static void undoRemoveSystemLock(Transaction& tx, const SystemLock* lock); + static void undoAddSystemLock(Transaction& tx, Score* score, const RangeLock* lock); + static void undoRemoveSystemLock(Transaction& tx, Score* score, const RangeLock* lock); static void undoRemoveAllLocks(Transaction& tx, Score* score); static void toggleSystemLock(Transaction& tx, Score* score, const std::vector& systems); @@ -53,7 +53,7 @@ class EditSystemLocks static void applyLockToSelection(Transaction& tx, Score* score); static void removeSystemLocksOnAddLayoutBreak(Transaction& tx, Score* score, LayoutBreakType breakType, const MeasureBase* measure); - static void removeLayoutBreaksOnAddSystemLock(Transaction& tx, Score* score, const SystemLock* lock); + static void updateLayoutBreaksOnAddSystemLock(Transaction& tx, Score* score, const RangeLock* lock); static void removeSystemLocksOnRemoveMeasures(Transaction& tx, Score* score, const MeasureBase* m1, const MeasureBase* m2); static void removeSystemLocksContainingMMRests(Transaction& tx, Score* score); static void updateSystemLocksOnCreateMMRests(Transaction& tx, Score* score, Measure* first, Measure* last); diff --git a/src/engraving/editing/navigation.cpp b/src/engraving/editing/navigation.cpp index a4e6927411955..cbebaa29151c7 100644 --- a/src/engraving/editing/navigation.cpp +++ b/src/engraving/editing/navigation.cpp @@ -34,6 +34,7 @@ #include "../dom/measurerepeat.h" #include "../dom/mscore.h" #include "../dom/note.h" +#include "../dom/pagelockindicator.h" #include "../dom/rest.h" #include "../dom/score.h" #include "../dom/segment.h" @@ -41,6 +42,7 @@ #include "../dom/spanner.h" #include "../dom/staff.h" #include "../dom/system.h" +#include "../dom/systemlockindicator.h" #include "../dom/tapping.h" #include "../style/style.h" @@ -925,6 +927,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; @@ -1174,6 +1182,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/iengravingconfiguration.h b/src/engraving/iengravingconfiguration.h index d0ed2435087f8..75449c19f68bf 100644 --- a/src/engraving/iengravingconfiguration.h +++ b/src/engraving/iengravingconfiguration.h @@ -54,6 +54,7 @@ class IEngravingConfiguration : MODULE_GLOBAL_INTERFACE virtual Color defaultColor() const = 0; virtual Color scoreInversionColor() const = 0; + virtual Color indicatorIconInvertedSelectionColor() const = 0; virtual Color lassoColor() const = 0; virtual Color warningColor() const = 0; virtual Color warningSelectedColor() const = 0; diff --git a/src/engraving/internal/engravingconfiguration.cpp b/src/engraving/internal/engravingconfiguration.cpp index 8641b39736a59..d187d9d8b621c 100644 --- a/src/engraving/internal/engravingconfiguration.cpp +++ b/src/engraving/internal/engravingconfiguration.cpp @@ -253,6 +253,11 @@ Color EngravingConfiguration::scoreInversionColor() const return Color(220, 220, 220); } +Color EngravingConfiguration::indicatorIconInvertedSelectionColor() const +{ + return Color::fromQColor(voiceColor(0).toQColor().lighter(135)); +} + Color EngravingConfiguration::lassoColor() const { return "#00323200"; diff --git a/src/engraving/internal/engravingconfiguration.h b/src/engraving/internal/engravingconfiguration.h index 488c4883461c1..9a806ca78e158 100644 --- a/src/engraving/internal/engravingconfiguration.h +++ b/src/engraving/internal/engravingconfiguration.h @@ -60,6 +60,7 @@ class EngravingConfiguration : public IEngravingConfiguration, public muse::asyn Color defaultColor() const override; Color scoreInversionColor() const override; + Color indicatorIconInvertedSelectionColor() const override; Color lassoColor() const override; Color warningColor() const override; Color warningSelectedColor() const override; diff --git a/src/engraving/rendering/score/layoutcontext.cpp b/src/engraving/rendering/score/layoutcontext.cpp index 77c549ff16360..892a2ce206b4f 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 "editing/transaction/transaction.h" @@ -376,7 +377,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; @@ -384,6 +385,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(); @@ -480,12 +489,13 @@ 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; } Transaction& tx = score()->transactionManager()->currentOrDummyTransaction(); + EditPageLocks::updatePageLocksOnCreateMMRests(tx, score(), first, last); EditSystemLocks::updateSystemLocksOnCreateMMRests(tx, score(), first, last); } diff --git a/src/engraving/rendering/score/layoutcontext.h b/src/engraving/rendering/score/layoutcontext.h index 4ba84d6b96dad..968953e44f36b 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) 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/mmrestlayout.cpp b/src/engraving/rendering/score/mmrestlayout.cpp index 35aee28b6b5ab..ed61f91c22b0b 100644 --- a/src/engraving/rendering/score/mmrestlayout.cpp +++ b/src/engraving/rendering/score/mmrestlayout.cpp @@ -120,7 +120,7 @@ void MMRestLayout::reuseExistingMMRest(LayoutContext& ctx, Measure* mmrMeasure, nextMMRMeasure->setMMRestCount(numMeasuresInNewMMRest); nextMMRMeasure->setMeasureNumber(nextFirstMeasure->measureNumber()); - ctx.mutDom().updateSystemLocksOnCreateMMRest(nextFirstMeasure, nextLastMeasure); + ctx.mutDom().updateLocksOnCreateMMRest(nextFirstMeasure, nextLastMeasure); nextMMRMeasure->setRepeatStart(nextFirstMeasure->repeatStart() || nextLastMeasure->repeatStart()); nextMMRMeasure->setRepeatEnd(nextFirstMeasure->repeatEnd() || nextLastMeasure->repeatEnd()); @@ -193,7 +193,7 @@ void MMRestLayout::createMMRest(LayoutContext& ctx, Measure* firstMeasure, Measu 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 a0d307fa28e18..baceec2d84cf5 100644 --- a/src/engraving/rendering/score/scorehorizontalviewlayout.cpp +++ b/src/engraving/rendering/score/scorehorizontalviewlayout.cpp @@ -28,6 +28,7 @@ #include "dom/score.h" #include "dom/masterscore.h" #include "dom/system.h" +#include "dom/systemlockindicator.h" #include "dom/spanner.h" #include "dom/page.h" #include "dom/durationelement.h" @@ -259,8 +260,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 34d8f712ee74e..3aa861acc4443 100644 --- a/src/engraving/rendering/score/systemlayout.cpp +++ b/src/engraving/rendering/score/systemlayout.cpp @@ -45,6 +45,8 @@ #include "dom/mmrestrange.h" #include "dom/note.h" #include "dom/ornament.h" +#include "dom/page.h" +#include "dom/pagelockindicator.h" #include "dom/part.h" #include "dom/parenthesis.h" #include "dom/pedal.h" @@ -56,6 +58,7 @@ #include "dom/staff.h" #include "dom/stafflines.h" #include "dom/system.h" +#include "dom/systemlockindicator.h" #include "dom/tie.h" #include "dom/timesig.h" #include "dom/tremolosinglechord.h" @@ -144,8 +147,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); @@ -263,8 +266,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: @@ -506,7 +509,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; } @@ -518,6 +521,27 @@ void SystemLayout::layoutSystemLockIndicators(System* system, LayoutContext& ctx TLayout::layoutIndicatorIcon(lockIndicator, lockIndicator->mutldata()); } +void SystemLayout::layoutPageLockIndicators(System* system) +{ + Page* page = system->page(); + system->deletePageLockIndicator(); + + IF_ASSERT_FAILED(!page->systems().empty()) { + return; + } + + 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 a5342c778fd06..3a8102836b91d 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 1a5df772dc401..4e2f12a4f065c 100644 --- a/src/engraving/rendering/score/tdraw.cpp +++ b/src/engraving/rendering/score/tdraw.cpp @@ -100,6 +100,7 @@ #include "dom/ottava.h" #include "dom/page.h" +#include "dom/pagelockindicator.h" #include "dom/parenthesis.h" #include "dom/partialtie.h" #include "dom/palmmute.h" @@ -131,8 +132,8 @@ #include "dom/stringtunings.h" #include "dom/symbol.h" #include "dom/systemdivider.h" +#include "dom/systemlockindicator.h" #include "dom/systemtext.h" -#include "dom/systemlock.h" #include "dom/soundflag.h" #include "dom/tapping.h" @@ -317,7 +318,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); @@ -2161,7 +2163,10 @@ void TDraw::draw(const LayoutBreak* item, Painter* painter, const PaintOptions& return; } - Pen pen(item->selected() ? item->configuration()->selectionColor() : item->configuration()->formattingColor()); + Color selectionColor = opt.invertColors ? item->configuration()->indicatorIconInvertedSelectionColor() + : item->configuration()->selectionColor(); + Color color = item->selected() ? selectionColor : item->configuration()->formattingColor(); + Pen pen(color); painter->setPen(pen); painter->setFont(item->font()); painter->drawSymbol(PointF(), item->iconCode()); @@ -2436,6 +2441,38 @@ 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; + } + + Color selectionColor = opt.invertColors ? item->configuration()->indicatorIconInvertedSelectionColor() + : item->configuration()->selectionColor(); + Color color = item->selected() ? selectionColor : item->configuration()->formattingColor(); + Pen pen(color); + painter->setPen(pen); + painter->setFont(item->font()); + painter->drawSymbol(PointF(), item->iconCode()); + + if (item->selected()) { + Color lockedAreaColor = selectionColor; + lockedAreaColor.setAlpha(opt.invertColors ? 90 : 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; @@ -2968,7 +3005,10 @@ void TDraw::draw(const IndicatorIcon* item, muse::draw::Painter* painter, const return; } - Pen pen(item->selected() ? item->configuration()->selectionColor() : item->configuration()->formattingColor()); + Color selectionColor = opt.invertColors ? item->configuration()->indicatorIconInvertedSelectionColor() + : item->configuration()->selectionColor(); + Color color = item->selected() ? selectionColor : item->configuration()->formattingColor(); + Pen pen(color); painter->setPen(pen); painter->setFont(item->font()); painter->drawSymbol(PointF(), item->iconCode()); @@ -2976,8 +3016,8 @@ void TDraw::draw(const IndicatorIcon* item, muse::draw::Painter* painter, const if (item->isSystemLockIndicator() && item->selected()) { const SystemLockIndicator* sli = toSystemLockIndicator(item); - Color lockedAreaColor = sli->configuration()->selectionColor(); - lockedAreaColor.setAlpha(38); + Color lockedAreaColor = selectionColor; + lockedAreaColor.setAlpha(opt.invertColors ? 90 : 38); Brush brush(lockedAreaColor); painter->setBrush(brush); painter->setNoPen(); diff --git a/src/engraving/rendering/score/tdraw.h b/src/engraving/rendering/score/tdraw.h index 0ae4fd25612bf..0a11fa90542c6 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 7eb4d2c0b8904..51dee7a28afbb 100644 --- a/src/engraving/rendering/score/tlayout.cpp +++ b/src/engraving/rendering/score/tlayout.cpp @@ -132,6 +132,7 @@ #include "dom/symbol.h" #include "dom/system.h" #include "dom/systemdivider.h" +#include "dom/systemlockindicator.h" #include "dom/systemtext.h" #include "dom/soundflag.h" @@ -384,6 +385,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); @@ -3749,7 +3753,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(); @@ -4378,6 +4382,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 7643ff24c257c..b9f88653e5bd1 100644 --- a/src/engraving/rendering/score/tlayout.h +++ b/src/engraving/rendering/score/tlayout.h @@ -80,6 +80,7 @@ #include "../../dom/note.h" #include "../../dom/notedot.h" +#include "../../dom/pagelockindicator.h" #include "../../dom/parenthesis.h" #include "../../dom/playcounttext.h" #include "../../dom/playtechannotation.h" @@ -302,6 +303,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 413c751b57fac..b96fba3f47eff 100644 --- a/src/engraving/rw/read410/tread.cpp +++ b/src/engraving/rw/read410/tread.cpp @@ -4533,5 +4533,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 7a3885e150bda..d8b832f918e3a 100644 --- a/src/engraving/rw/read460/tread.cpp +++ b/src/engraving/rw/read460/tread.cpp @@ -4677,7 +4677,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 c6806dac839fb..b6e20fccc6f87 100644 --- a/src/engraving/rw/read500/read500.cpp +++ b/src/engraving/rw/read500/read500.cpp @@ -186,6 +186,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 53ea334f12555..0b7e5e8d16639 100644 --- a/src/engraving/rw/read500/tread.cpp +++ b/src/engraving/rw/read500/tread.cpp @@ -4777,6 +4777,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()) { @@ -4788,6 +4799,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; @@ -4811,7 +4848,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 623e80694d313..a5d75b74336ae 100644 --- a/src/engraving/rw/read500/tread.h +++ b/src/engraving/rw/read500/tread.h @@ -393,6 +393,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); @@ -411,6 +412,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 f0672f2567ae2..ee90ed9fdac93 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 1c6e09b48b444..c9da9e0163743 100644 --- a/src/engraving/rw/write/twrite.h +++ b/src/engraving/rw/write/twrite.h @@ -317,6 +317,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); @@ -368,7 +369,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 b14da929efc8f..d161e5fea4bc3 100644 --- a/src/engraving/rw/write/writer.cpp +++ b/src/engraving/rw/write/writer.cpp @@ -261,6 +261,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 e29fcba0abc4b..7e11705cc2ab6 100644 --- a/src/engraving/tests/CMakeLists.txt +++ b/src/engraving/tests/CMakeLists.txt @@ -102,6 +102,7 @@ set(MODULE_TEST_SRC ${CMAKE_CURRENT_LIST_DIR}/parentheses_tests.cpp ${CMAKE_CURRENT_LIST_DIR}/lyrics_tests.cpp ${CMAKE_CURRENT_LIST_DIR}/automation/utils/automationtestutils.h + ${CMAKE_CURRENT_LIST_DIR}/page_locks_tests.cpp ${CMAKE_CURRENT_LIST_DIR}/automation/automation_tests.cpp ${CMAKE_CURRENT_LIST_DIR}/automation/automationrw_tests.cpp ${CMAKE_CURRENT_LIST_DIR}/automation/scoreautomationcontroller_tests.cpp diff --git a/src/engraving/tests/mocks/engravingconfigurationmock.h b/src/engraving/tests/mocks/engravingconfigurationmock.h index 0510a49dca8bc..b78d4f83bbc2d 100644 --- a/src/engraving/tests/mocks/engravingconfigurationmock.h +++ b/src/engraving/tests/mocks/engravingconfigurationmock.h @@ -47,6 +47,7 @@ class EngravingConfigurationMock : public IEngravingConfiguration MOCK_METHOD(Color, defaultColor, (), (const, override)); MOCK_METHOD(Color, scoreInversionColor, (), (const, override)); + MOCK_METHOD(Color, indicatorIconInvertedSelectionColor, (), (const, override)); MOCK_METHOD(Color, lassoColor, (), (const, override)); MOCK_METHOD(Color, warningColor, (), (const, override)); MOCK_METHOD(Color, warningSelectedColor, (), (const, override)); 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..b1cab71609e73 --- /dev/null +++ b/src/engraving/tests/page_locks_tests.cpp @@ -0,0 +1,215 @@ +/* + * 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 "engraving/editing/transaction/transaction.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->transactionManager()->transaction(TranslatableString::untranslatable("Engraving system locks tests"), [&](auto& tx) { + EditPageLocks::addRemovePageLocks(tx, score, 0, false); // Remove all locks + }); + + 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->transactionManager()->transaction(TranslatableString::untranslatable("Engraving system locks tests"), [&](auto& tx) { + EditPageLocks::addRemovePageLocks(tx, score, 0, true); // Lock current layout + }); + + for (MeasureBase* mb : measuresAtPageStart) { + EXPECT_TRUE(mb->isStartOfPageLock()); + } + for (MeasureBase* mb : measuresAtPageEnd) { + EXPECT_TRUE(mb->isEndOfPageLock()); + } + + score->transactionManager()->transaction(TranslatableString::untranslatable("Engraving system locks tests"), [&](auto& tx) { + EditPageLocks::addRemovePageLocks(tx, score, 4, false); // Add locks every 4 measures + }); + + 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->transactionManager()->transaction(TranslatableString::untranslatable("Engraving system locks tests"), [&](auto& tx) { + EditPageLocks::makeIntoPage(tx, score, thirdMeasure, sixthMeasure); + }); + + 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->transactionManager()->transaction(TranslatableString::untranslatable("Engraving system locks tests"), [&](auto& tx) { + EditPageLocks::moveMeasuresToPrevPage(tx, score, sixthMeasure, sixthMeasure); + }); + + EXPECT_TRUE(sixthMeasure->isEndOfPageLock()); + EXPECT_TRUE(sixthMeasure->next()->isStartOfPageLock()); + + score->transactionManager()->transaction(TranslatableString::untranslatable("Engraving system locks tests"), [&](auto& tx) { + EditPageLocks::moveMeasuresToNextPage(tx, score, thirdMeasure, thirdMeasure); + }); + + 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->transactionManager()->transaction(TranslatableString::untranslatable("Engraving system locks tests"), [&](auto& tx) { + EditPageLocks::togglePageLock(tx, score, score->selection().pagesContainingSelection()); + }); + + EXPECT_FALSE(score->pages().front()->isLocked()); + + score->transactionManager()->transaction(TranslatableString::untranslatable("Engraving system locks tests"), [&](auto& tx) { + EditPageLocks::togglePageLock(tx, score, score->selection().pagesContainingSelection()); + }); + + EXPECT_TRUE(score->pages().front()->isLocked()); + + score->transactionManager()->transaction(TranslatableString::untranslatable("Engraving system locks tests"), [&](auto& tx) { + EditPageLocks::toggleScoreLock(tx, score); + }); + + for (Page* page : score->pages()) { + EXPECT_FALSE(page->isLocked()); + } + + score->transactionManager()->transaction(TranslatableString::untranslatable("Engraving system locks tests"), [&](auto& tx) { + EditPageLocks::toggleScoreLock(tx, score); + }); + + for (Page* page : score->pages()) { + EXPECT_TRUE(page->isLocked()); + } + + delete score; +} diff --git a/src/engraving/tests/spannermigration_tests.cpp b/src/engraving/tests/spannermigration_tests.cpp new file mode 100644 index 0000000000000..fa89f2102e34a --- /dev/null +++ b/src/engraving/tests/spannermigration_tests.cpp @@ -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 + * + * 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 . + */ + +// ONE-OFF MIGRATION TOOL — not part of the permanent test suite. +// Converts a single fixture that is marked version="5.00" but still uses the +// pre-EID inline spanner format. read500's reading code has been temporarily +// reverted (see git commit 3f8a08bbfd) to the pre-EID connector-based reader +// so that it can still parse these files; the writer is unmodified +// (current/new EID format), so a plain load+resave upgrades the fixture in +// place. +// +// Driven one file at a time (via the MIGRATE_FILE env var, an absolute path) +// from an external shell loop so that a hard abort() on one problem fixture +// doesn't take the rest of the batch down with it. + +#include + +#include "utils/scorerw.h" + +#include "log.h" + +using namespace mu; +using namespace mu::engraving; + +TEST(Engraving_SpannerMigration, ConvertOldFormatFixture) +{ + const char* envPath = std::getenv("MIGRATE_FILE"); + if (!envPath || !*envPath) { + GTEST_SKIP() << "MIGRATE_FILE not set"; + } + + String absPath = String::fromUtf8(envPath); + LOGI() << "=== migrating: " << absPath << " ==="; + + MasterScore* score = ScoreRW::readScore(absPath, /*isAbsolutePath*/ true); + ASSERT_TRUE(score) << absPath.toStdString(); + + bool saveOk = ScoreRW::saveScore(score, absPath); + EXPECT_TRUE(saveOk) << absPath.toStdString(); + + delete score; +} diff --git a/src/engraving/tests/system_locks_tests.cpp b/src/engraving/tests/system_locks_tests.cpp index 44d184c050283..9341e71983092 100644 --- a/src/engraving/tests/system_locks_tests.cpp +++ b/src/engraving/tests/system_locks_tests.cpp @@ -43,7 +43,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()) { @@ -54,7 +54,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; @@ -70,8 +70,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")); @@ -108,7 +108,7 @@ TEST_F(Engraving_SystemLocksTests, lockMeasuresPerSystem) }); 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 0e252f63a2c83..f320ef8d2df63 100644 --- a/src/engraving/types/types.h +++ b/src/engraving/types/types.h @@ -188,6 +188,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 3e6376f8b2542..2a5daebfb6ea5 100644 --- a/src/engraving/types/typesconv.cpp +++ b/src/engraving/types/typesconv.cpp @@ -496,6 +496,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 ecd9568d4adec..72177d46dcbce 100644 --- a/src/notation/inotationinteraction.h +++ b/src/notation/inotationinteraction.h @@ -242,6 +242,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 e1c1aeaaac160..e2575647798a1 100644 --- a/src/notation/inotationselection.h +++ b/src/notation/inotationselection.h @@ -35,6 +35,7 @@ namespace mu::engraving { class EngravingItem; class MeasureBase; class Note; +class Page; class System; enum class SelState : char; } @@ -76,6 +77,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 pagesContainingSelection() 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 4dfef306f2f84..4e0604bf55572 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -117,6 +117,7 @@ #include "engraving/editing/editstretch.h" #include "engraving/editing/edittie.h" #include "engraving/editing/edittimesig.h" +#include "engraving/editing/editpagelocks.h" #include "engraving/editing/editsystemlocks.h" #include "engraving/editing/flip.h" #include "engraving/editing/exchangevoices.h" @@ -1962,7 +1963,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()->tick2measure(showAnchors.startTickExtendedRegion); mb && mb->tick() <= showAnchors.endTickExtendedRegion; mb = mb->next()) { System* s = mb->system(); @@ -2512,7 +2513,8 @@ void NotationInteraction::applyPaletteElementToList(EngravingItem* element, mu:: const ActionIcon* icon = toActionIcon(element); switch (icon->actionType()) { case ActionIconType::SYSTEM_LOCK: { - EditSystemLocks::applyLockToSelection(tx, score); + engraving::Transaction& tx = score->transactionManager()->currentOrDummyTransaction(); + EditSystemLocks::toggleSystemLock(tx, score, score->selection().selectedSystems()); return; } case ActionIconType::PARENTHESES: { @@ -2760,7 +2762,7 @@ void NotationInteraction::applyPaletteElementToRange(EngravingItem* element, mu: const ActionIconType actionType = toActionIcon(element)->actionType(); switch (actionType) { case ActionIconType::SYSTEM_LOCK: { - EditSystemLocks::applyLockToSelection(tx, score); + EditSystemLocks::toggleSystemLock(tx, score, score->selection().selectedSystems()); return; } case ActionIconType::PARENTHESES: { @@ -6194,6 +6196,65 @@ void NotationInteraction::applySystemLock() }); } +void NotationInteraction::moveSystemToPrevPage() +{ + MeasureBase* firstMeas = score()->selection().startMeasureBase(); + MeasureBase* endMeas = score()->selection().endMeasureBase(); + System* startSys = firstMeas ? firstMeas->system() : nullptr; + System* endSys = endMeas ? endMeas->system() : nullptr; + if (!startSys || !endSys) { + return; + } + MeasureBase* firstMb = startSys->first(); + MeasureBase* lastMb = endSys->last(); + transaction(TranslatableString("undoableAction", "Move system to previous page"), [&](auto& tx) { + EditPageLocks::moveMeasuresToPrevPage(tx, score(), firstMb, lastMb); + }); +} + +void NotationInteraction::moveSystemToNextPage() +{ + MeasureBase* firstMeas = score()->selection().startMeasureBase(); + MeasureBase* endMeas = score()->selection().endMeasureBase(); + System* startSys = firstMeas ? firstMeas->system() : nullptr; + System* endSys = endMeas ? endMeas->system() : nullptr; + if (!startSys || !endSys) { + return; + } + MeasureBase* firstMb = startSys->first(); + MeasureBase* lastMb = endSys->last(); + transaction(TranslatableString("undoableAction", "Move system to next page"), [&](auto& tx) { + EditPageLocks::moveMeasuresToNextPage(tx, score(), firstMb, lastMb); + }); +} + +void NotationInteraction::togglePageLock() +{ + transaction(TranslatableString("undoableAction", "Lock/unlock selected page(s)"), [&](auto& tx) { + EditPageLocks::togglePageLock(tx, score(), selection()->pagesContainingSelection()); + }); +} + +void NotationInteraction::makeIntoPage() +{ + MeasureBase* first = score()->selection().startMeasureBase(); + MeasureBase* last = score()->selection().endMeasureBase(); + if (!first || !last) { + return; + } + + transaction(TranslatableString("undoableAction", "Create page from selection"), [&](auto& tx) { + EditPageLocks::makeIntoPage(tx, score(), first, last); + }); +} + +void NotationInteraction::applyPageLock() +{ + transaction(TranslatableString("undoableAction", "Apply page lock to selection"), [&](auto& tx) { + EditPageLocks::applyLockToSelection(tx, score()); + }); +} + 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 cbed8a5ecb582..3a6765584c768 100644 --- a/src/notation/internal/notationinteraction.h +++ b/src/notation/internal/notationinteraction.h @@ -252,6 +252,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 8f517478d8f57..43140c111dfb9 100644 --- a/src/notation/internal/notationparts.cpp +++ b/src/notation/internal/notationparts.cpp @@ -30,6 +30,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/editstaff.h" #include "engraving/editing/editstavesharing.h" @@ -247,6 +248,7 @@ void NotationParts::setPartVisible(const ID& partId, bool visible) if (visible) { engraving::Transaction& tx = score()->transactionManager()->currentOrDummyTransaction(); + EditPageLocks::removePageLocksContainingMMRests(tx, score()); EditSystemLocks::removeSystemLocksContainingMMRests(tx, score()); } @@ -572,6 +574,7 @@ void NotationParts::setStaffVisible(const ID& staffId, bool visible) if (visible) { engraving::Transaction& tx = score()->transactionManager()->currentOrDummyTransaction(); + EditPageLocks::removePageLocksContainingMMRests(tx, score()); EditSystemLocks::removeSystemLocksContainingMMRests(tx, score()); } @@ -733,6 +736,7 @@ void NotationParts::insertPart(Part* part, size_t index) startEdit(TranslatableString("undoableAction", "Add instrument")); engraving::Transaction& tx = score()->transactionManager()->currentOrDummyTransaction(); + EditPageLocks::removePageLocksContainingMMRests(tx, score()); EditSystemLocks::removeSystemLocksContainingMMRests(tx, score()); doInsertPart(part, index); diff --git a/src/notation/internal/notationselection.cpp b/src/notation/internal/notationselection.cpp index bd27b479c2133..b41e28b3fa08f 100644 --- a/src/notation/internal/notationselection.cpp +++ b/src/notation/internal/notationselection.cpp @@ -163,6 +163,11 @@ std::vector NotationSelection::selectedSystems() const return score()->selection().selectedSystems(); } +std::vector NotationSelection::pagesContainingSelection() const +{ + return score()->selection().pagesContainingSelection(); +} + EngravingItem* NotationSelection::lastElementHit() const { return m_lastElementHit; diff --git a/src/notation/internal/notationselection.h b/src/notation/internal/notationselection.h index 147fa14b3bb93..7506ad72b76e3 100644 --- a/src/notation/internal/notationselection.h +++ b/src/notation/internal/notationselection.h @@ -56,6 +56,7 @@ class NotationSelection : public INotationSelection mu::engraving::MeasureBase* startMeasureBase() const override; mu::engraving::MeasureBase* endMeasureBase() const override; std::vector selectedSystems() const override; + std::vector pagesContainingSelection() 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 c6d92c423595f..3055063b94c0f 100644 --- a/src/notation/tests/mocks/notationinteractionmock.h +++ b/src/notation/tests/mocks/notationinteractionmock.h @@ -193,6 +193,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..a28e57bd59514 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, pagesContainingSelection, (), (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 945dc71b9dd8b..cabe1f5604593 100644 --- a/src/notationscene/internal/notationactioncontroller.cpp +++ b/src/notationscene/internal/notationactioncontroller.cpp @@ -312,6 +312,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 e4764a13ca294..1466f170d745a 100644 --- a/src/notationscene/internal/notationuiactions.cpp +++ b/src/notationscene/internal/notationuiactions.cpp @@ -725,6 +725,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..59a8feed305a9 --- /dev/null +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/MeasuresFlowSection.qml @@ -0,0 +1,168 @@ +/* + * 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 lockLabel: "" + property string unlockLabel: "" + + 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.unlockLabel : root.unlockLabel) + : (root.itemCount > 1 ? root.lockLabel : root.lockLabel) + + 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 c9c92b73005da..78415e0d8dbda 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.cpp +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/measures/measuressettingsmodel.cpp @@ -21,9 +21,7 @@ */ #include "measuressettingsmodel.h" -#include "engraving/dom/score.h" - -#include "notation/inotationinteraction.h" // IWYU pragma: keep +#include "notation/inotationinteraction.h" #include "notation/inotationselection.h" #include "translation.h" @@ -43,10 +41,6 @@ MeasuresSettingsModel::MeasuresSettingsModel(QObject* parent, const muse::modula void MeasuresSettingsModel::loadProperties() { - updateAllSystemsAreLocked(); - updateScoreIsInPageView(); - updateIsMakeIntoSystemAvailable(); - updateSystemCount(); } bool MeasuresSettingsModel::shouldUpdateOnEmptyPropertyAndStyleIdSets() const @@ -56,7 +50,6 @@ bool MeasuresSettingsModel::shouldUpdateOnEmptyPropertyAndStyleIdSets() const void MeasuresSettingsModel::onNotationChanged(const engraving::PropertyIdSet&, const engraving::StyleIdSet&) { - loadProperties(); } bool MeasuresSettingsModel::isEmpty() const @@ -93,147 +86,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 ee5bf67689328..99275f28c1d33 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanelabstractmodel.cpp +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanelabstractmodel.cpp @@ -405,6 +405,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 27d9211af57a2..f8f7f080beec6 100644 --- a/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanellistmodel.cpp +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/propertiespanellistmodel.cpp @@ -30,6 +30,7 @@ #include "engraving/dom/score.h" #include "general/generalsettingsmodel.h" +#include "systemlayout/systemlayoutsettingsmodel.h" #include "measures/measuressettingsmodel.h" #include "emptystaves/emptystavesvisiblitysettingsmodel.h" #include "notation/notationsettingsproxymodel.h" @@ -233,6 +234,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..bd58e0bf0a1da --- /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("propertiespanel", "System") + + navigation.name: "SystemTab" + navigation.panel: root.navigationPanel + navigation.row: root.navigationRowStart + 1 + + maxWidth: tabBar.truncatedItemWidth + } + + PropertiesPanelTabButton { + text: qsTrc("propertiespanel", "Page") + + navigation.name: "PageTab" + navigation.panel: root.navigationPanel + navigation.row: root.navigationRowStart + 2 + + maxWidth: tabBar.truncatedItemWidth + } + } + + 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 + lockLabel: qsTrc("propertiespanel", "Lock selected system(s)", "", root.model ? root.model.systemCount : 0) + unlockLabel: qsTrc("propertiespanel", "Unlock selected system(s)", "", root.model ? root.model.systemCount : 0) + lockToolTipTitle: qsTrc("propertiespanel", "Lock/unlock selected system(s)", "", root.model ? root.model.systemCount : 0) + lockToolTipDescription: qsTrc("propertiespanel", "Keep measures on the selected system(s) together and prevent them from reflowing to the next system", root.model ? root.model.systemCount : 0) + lockToolTipShortcut: root.model ? root.model.shortcutToggleSystemLock : "" + lockNavigationName: "SystemLock" + + moveSectionTitle: qsTrc("propertiespanel", "Move measures across systems") + movePrevText: qsTrc("propertiespanel", "Previous") + moveNextText: qsTrc("propertiespanel", "Next") + movePreviousToolTipTitle: qsTrc("propertiespanel", "Move measure(s) to previous system") + moveNextToolTipTitle: qsTrc("propertiespanel", "Move measure(s) to next system") + movePreviousShortcut: root.model?.shortcutMoveMeasureUpSystem ?? "" + moveNextShortcut: root.model ? root.model.shortcutMoveMeasureDownSystem : "" + previousNavigationName: "SystemUp" + nextNavigationName: "SystemDown" + + makeIntoText: qsTrc("propertiespanel", "New system from selection") + makeIntoToolTipTitle: qsTrc("propertiespanel", "New system from selection") + makeIntoToolTipDescription: qsTrc("propertiespanel", "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 + lockLabel: qsTrc("propertiespanel", "Lock selected page(s)", "", root.model ? root.model.pageCount : 0) + unlockLabel: qsTrc("propertiespanel", "Unlock selected page(s)", "", root.model ? root.model.pageCount : 0) + lockToolTipTitle: qsTrc("propertiespanel", "Lock/unlock selected page(s)", root.model ? root.model.pageCount : 0) + lockToolTipDescription: qsTrc("propertiespanel", "Keep measures on the selected page(s) together and prevent them from reflowing to the next page", root.model ? root.model.pageCount : 0) + lockToolTipShortcut: root.model ? root.model.shortcutTogglePageLock : "" + lockNavigationName: "PageLock" + + moveSectionTitle: qsTrc("propertiespanel", "Move systems across pages") + movePrevText: qsTrc("propertiespanel", "Previous") + moveNextText: qsTrc("propertiespanel", "Next") + movePreviousToolTipTitle: qsTrc("propertiespanel", "Move system(s) to previous page") + moveNextToolTipTitle: qsTrc("propertiespanel", "Move system(s) to next page") + movePreviousShortcut: root.model?.shortcutMoveSystemUpPage ?? "" + moveNextShortcut: root.model ? root.model.shortcutMoveSystemDownPage : "" + previousNavigationName: "PageUp" + nextNavigationName: "PageDown" + + makeIntoText: qsTrc("propertiespanel", "New page from selection") + makeIntoToolTipTitle: qsTrc("propertiespanel", "New page from selection") + makeIntoToolTipDescription: qsTrc("propertiespanel", "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..1a7d4378b12c8 --- /dev/null +++ b/src/propertiespanel/qml/MuseScore/PropertiesPanel/systemlayout/systemlayoutsettingsmodel.cpp @@ -0,0 +1,334 @@ +/* + * 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 "notation/inotationinteraction.h" +#include "notation/inotationselection.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("propertiespanel", "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()->pagesContainingSelection(); + + 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()->pagesContainingSelection().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; +}; +}