Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2023 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include "scorepageviewlayout.h"
23 :
24 : #include "containers.h"
25 :
26 : #include "dom/score.h"
27 : #include "dom/system.h"
28 : #include "dom/bracket.h"
29 : #include "dom/layoutbreak.h"
30 : #include "dom/page.h"
31 :
32 : #include "passresetlayoutdata.h"
33 : #include "passlayoutindependentitems.h"
34 :
35 : #include "headerfooterlayout.h"
36 : #include "measurelayout.h"
37 : #include "systemlayout.h"
38 : #include "pagelayout.h"
39 :
40 : #include "log.h"
41 :
42 : using namespace mu::engraving;
43 : using namespace mu::engraving::rendering::score;
44 :
45 3964 : void ScorePageViewLayout::initLayoutContext(const Score* score, LayoutContext& ctx, const Fraction& stick, const Fraction& etick)
46 : {
47 : LAYOUT_CALL();
48 :
49 3964 : LayoutState& state = ctx.mutState();
50 :
51 3964 : state.setEndTick(etick);
52 :
53 3964 : MeasureBase* m = nullptr;
54 : {
55 3964 : m = score->tick2measure(stick);
56 3964 : if (m == nullptr) {
57 1 : m = score->first();
58 : }
59 :
60 : // start layout one measure earlier to handle clefs and cautionary elements
61 3964 : if (m->prevMeasureMM()) {
62 180 : m = m->prevMeasureMM();
63 3784 : } else if (m->prev()) {
64 2139 : m = m->prev();
65 : }
66 :
67 4087 : while (!m->isMeasure() && m->prev()) {
68 123 : m = m->prev();
69 : }
70 :
71 : // if the first measure of the score is part of a multi measure rest
72 : // m->system() will return a nullptr. We need to find the multi measure
73 : // rest which replaces the measure range
74 3964 : if (!m->system() && m->isMeasure() && toMeasure(m)->hasMMRest()) {
75 3 : LOGD(" don’t start with mmrest");
76 3 : m = toMeasure(m)->mmRest();
77 : }
78 : }
79 :
80 3964 : if (!state.isLayoutAll() && m->system()) {
81 591 : System* system = m->system();
82 591 : system_idx_t systemIndex = muse::indexOf(score->systems(), system);
83 :
84 : // set current system
85 591 : state.setStartTick(system->measures().front()->tick());
86 591 : state.setCurSystem(system);
87 591 : state.setSystemList(muse::mid(score->systems(), systemIndex));
88 :
89 : // set current page
90 591 : state.setPage(system->page());
91 591 : page_idx_t pageIdx = score->pageIdx(state.page());
92 591 : if (pageIdx == muse::nidx) {
93 0 : pageIdx = 0;
94 : }
95 591 : state.setPageIdx(pageIdx);
96 :
97 591 : MeasureBase* nextMeasure = nullptr;
98 591 : if (systemIndex == 0) {
99 476 : nextMeasure = ctx.conf().isShowVBox() ? score->first() : score->firstMeasure();
100 : } else {
101 115 : System* prevSystem = score->systems().at(systemIndex - 1);
102 115 : nextMeasure = prevSystem->measures().back()->next();
103 : }
104 591 : state.setNextMeasure(nextMeasure);
105 :
106 591 : if (!state.nextMeasure()->prevMeasure()) {
107 552 : state.setMeasureNumber(0);
108 552 : state.setTick(Fraction(0, 1));
109 : } else {
110 39 : const MeasureBase* mb = state.nextMeasure()->prev();
111 39 : if (mb) {
112 39 : mb = mb->mbWithPrecedingSectionBreak();
113 : }
114 :
115 39 : const LayoutBreak* layoutBreak = mb->sectionBreakElement();
116 : // TODO: also use mb in else clause here?
117 : // probably not, only actual measures have meaningful numbers
118 39 : if (layoutBreak && layoutBreak->startWithMeasureOne()) {
119 5 : state.setMeasureNumber(0);
120 : } else {
121 68 : state.setMeasureNumber(state.nextMeasure()->prevMeasure()->measureNumber() // will be adjusted later with respect
122 34 : + (state.nextMeasure()->prevMeasure()->excludeFromNumbering() ? 0 : 1)); // to the user-defined offset.
123 : }
124 39 : state.setTick(ctx.state().nextMeasure()->tick());
125 : }
126 : } else {
127 3373 : state.setNextMeasure(ctx.conf().isShowVBox() ? score->first() : score->firstMeasure());
128 : }
129 :
130 3964 : state.setPrevMeasure(nullptr);
131 3964 : }
132 :
133 3964 : void ScorePageViewLayout::prepareScore(Score* score, const LayoutContext& ctx)
134 : {
135 : LAYOUT_CALL();
136 :
137 3964 : if (!ctx.state().isLayoutAll() && ctx.state().curSystem()) {
138 591 : system_idx_t systemIndex = muse::indexOf(score->systems(), ctx.state().curSystem());
139 591 : score->systems().erase(score->systems().begin() + systemIndex, score->systems().end());
140 : } else {
141 6293 : for (System* s : score->systems()) {
142 3279 : for (Bracket* b : s->brackets()) {
143 359 : if (b->selected()) {
144 0 : bool selected = b->selected();
145 0 : score->selection().remove(b);
146 0 : BracketItem* item = b->bracketItem();
147 0 : item->setSelected(selected);
148 0 : score->setSelectionChanged(true);
149 : }
150 : }
151 : // no need to clear the page placement: the systems are deleted below,
152 : // and ~System unlinks itself from its page
153 : }
154 :
155 32799 : for (MeasureBase* mb = score->first(); mb; mb = mb->next()) {
156 29426 : mb->setSystem(nullptr);
157 29426 : if (mb->isMeasure() && toMeasure(mb)->mmRest()) {
158 111 : toMeasure(mb)->mmRest()->setSystem(nullptr);
159 : }
160 : }
161 3373 : muse::DeleteAll(score->systems());
162 3373 : score->systems().clear();
163 :
164 3373 : muse::DeleteAll(score->pages());
165 3373 : score->pages().clear();
166 : }
167 3964 : }
168 :
169 3964 : void ScorePageViewLayout::layoutPageView(Score* score, LayoutContext& ctx, const Fraction& stick, const Fraction& etick)
170 : {
171 3979 : TRACEFUNC;
172 :
173 : LAYOUT_CALL_CLEAR();
174 : LAYOUT_CALL();
175 :
176 3964 : initLayoutContext(score, ctx, stick, etick);
177 :
178 3964 : prepareScore(score, ctx);
179 :
180 : //! NOTE Reset pass need anyway
181 : //#ifdef MUE_ENABLE_ENGRAVING_LD_PASSES
182 3964 : PassResetLayoutData resetPass;
183 3964 : resetPass.run(score, ctx);
184 : //#endif
185 :
186 : #ifdef MUE_ENABLE_ENGRAVING_LD_PASSES
187 : if (ctx.state().isLayoutAll()) {
188 : PassLayoutIndependentItems independentPass;
189 : independentPass.run(score, ctx);
190 : }
191 : #endif
192 :
193 3964 : doLayout(ctx);
194 :
195 3964 : layoutFinished(score, ctx);
196 :
197 : LAYOUT_CALL_PRINT();
198 3964 : }
199 :
200 3964 : void ScorePageViewLayout::doLayout(LayoutContext& ctx)
201 : {
202 : LAYOUT_CALL();
203 :
204 3964 : LayoutState& state = ctx.mutState();
205 3964 : MeasureLayout::getNextMeasure(ctx);
206 3964 : state.setCurSystem(SystemLayout::collectSystem(ctx));
207 :
208 3964 : const MeasureBase* lmb = nullptr;
209 : do {
210 4125 : PageLayout::getNextPage(ctx);
211 4125 : PageLayout::collectPage(ctx);
212 :
213 4125 : if (state.page() && !state.page()->systems().empty()) {
214 4125 : lmb = state.page()->systems().back()->measures().back();
215 : } else {
216 0 : lmb = nullptr;
217 : }
218 :
219 : // we can stop collecting pages when:
220 : // 1) we reach the end of score (curSystem is nullptr)
221 : // or
222 : // 2) we have fully processed the range and reached a point of stability:
223 : // a) we have completed layout for the range (rangeDone is true)
224 : // b) we haven't collected a system that will need to go on the next page
225 : // c) this page ends with the same measure as the previous layout
226 : // pageOldMeasure will be last measure from previous layout if range was completed on or before this page
227 : // it will be nullptr if this page was never laid out or if we collected a system for next page
228 4125 : } while (state.curSystem() && !(state.rangeDone() && lmb == state.pageOldMeasure()));
229 : // && page->system(0)->measures().back()->tick() > endTick // FIXME: perhaps the first measure was meant? Or last system?
230 :
231 3964 : SystemLayout::centerPendingSystems(ctx);
232 3964 : }
233 :
234 3964 : void ScorePageViewLayout::layoutFinished(Score* score, LayoutContext& ctx)
235 : {
236 : LAYOUT_CALL();
237 :
238 3964 : LayoutState& state = ctx.mutState();
239 :
240 3964 : if (!state.curSystem()) {
241 : // The end of the score. The remaining systems are not needed...
242 3954 : muse::DeleteAll(state.systemList());
243 3954 : state.systemList().clear();
244 :
245 : // ...and the remaining pages too
246 7918 : while (score->npages() > state.pageIdx()) {
247 10 : Page* p = score->pages().back();
248 10 : score->pages().pop_back();
249 10 : delete p;
250 : }
251 : } else {
252 10 : Page* p = state.curSystem()->page();
253 10 : if (p && (p != state.page())) {
254 10 : p->invalidateBspTree();
255 : }
256 : }
257 :
258 3964 : score->systems().insert(score->systems().end(), state.systemList().begin(), state.systemList().end());
259 :
260 : /* If the headers/footers involve page count, we need to re-calculate them. This needs to
261 : * be done after laying out all pages, so that the total number of pages will be known: */
262 3964 : if (state.mustRecomputeHeadersFooters()) {
263 4 : doLayoutHeadersFooters(ctx);
264 4 : state.setMustRecomputeHeadersFooters(false);
265 : }
266 3964 : }
267 :
268 0 : void ScorePageViewLayout::layoutHeadersFooters(LayoutContext& ctx)
269 : {
270 0 : TRACEFUNC;
271 :
272 : LAYOUT_CALL_CLEAR();
273 : LAYOUT_CALL();
274 :
275 0 : doLayoutHeadersFooters(ctx);
276 :
277 : LAYOUT_CALL_PRINT();
278 0 : }
279 :
280 4 : void ScorePageViewLayout::doLayoutHeadersFooters(LayoutContext& ctx)
281 : {
282 10 : for (const auto& page : ctx.dom().pages()) {
283 6 : HeaderFooterLayout::layoutHeaderFooter(ctx, page);
284 : }
285 4 : }
|