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) 2026 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 :
23 : #include "editpagelocks.h"
24 : #include "editsystemlocks.h"
25 :
26 : #include "transaction/transaction.h"
27 : #include "transaction/undoablecommand.h"
28 :
29 : #include "../dom/measure.h"
30 : #include "../dom/measurebase.h"
31 : #include "../dom/page.h"
32 : #include "../dom/pagelockindicator.h"
33 : #include "../dom/rangelock.h"
34 : #include "../dom/score.h"
35 : #include "../dom/system.h"
36 :
37 : using namespace mu::engraving;
38 :
39 : //---------------------------------------------------------
40 : // AddPageLock
41 : //---------------------------------------------------------
42 :
43 : class AddPageLock : public UndoableCommand
44 : {
45 36 : OBJECT_ALLOCATOR(engraving, AddPageLock)
46 :
47 : const RangeLock* m_pageLock;
48 : public:
49 18 : AddPageLock(const RangeLock* pageLock)
50 18 : : m_pageLock(pageLock) {}
51 :
52 0 : void undo() override
53 : {
54 0 : Score* score = m_pageLock->startMB()->score();
55 0 : score->removePageLock(m_pageLock);
56 0 : }
57 :
58 18 : void redo() override
59 : {
60 18 : Score* score = m_pageLock->startMB()->score();
61 18 : score->addPageLock(m_pageLock);
62 18 : }
63 :
64 18 : void cleanup(bool undo) override
65 : {
66 18 : if (!undo) {
67 0 : delete m_pageLock;
68 0 : m_pageLock = nullptr;
69 : }
70 18 : }
71 :
72 0 : UNDO_NAME("AddPageLock")
73 :
74 18 : std::vector<EngravingObject*> objectItems() const override
75 : {
76 54 : return { m_pageLock->startMB(), m_pageLock->endMB() };
77 : }
78 : };
79 :
80 : //---------------------------------------------------------
81 : // RemovePageLock
82 : //---------------------------------------------------------
83 :
84 : class RemovePageLock : public UndoableCommand
85 : {
86 46 : OBJECT_ALLOCATOR(engraving, RemovePageLock)
87 :
88 : const RangeLock* m_pageLock;
89 : public:
90 23 : RemovePageLock(const RangeLock* pageLock)
91 23 : : m_pageLock(pageLock) {}
92 :
93 0 : void undo() override
94 : {
95 0 : Score* score = m_pageLock->startMB()->score();
96 0 : score->addPageLock(m_pageLock);
97 0 : }
98 :
99 23 : void redo() override
100 : {
101 23 : Score* score = m_pageLock->startMB()->score();
102 23 : score->removePageLock(m_pageLock);
103 23 : }
104 :
105 23 : void cleanup(bool undo) override
106 : {
107 23 : if (undo) {
108 23 : delete m_pageLock;
109 23 : m_pageLock = nullptr;
110 : }
111 23 : }
112 :
113 0 : UNDO_NAME("RemovePageLock")
114 23 : std::vector<EngravingObject*> objectItems() const override
115 : {
116 69 : return { m_pageLock->startMB(), m_pageLock->endMB() };
117 : }
118 : };
119 :
120 : //---------------------------------------------------------
121 : // EditPageLocks
122 : //---------------------------------------------------------
123 :
124 18 : void EditPageLocks::undoAddPageLock(Transaction& tx, const RangeLock* lock)
125 : {
126 18 : removeLayoutBreaksOnAddPageLock(tx, lock);
127 18 : tx.push(new AddPageLock(lock));
128 18 : }
129 :
130 23 : void EditPageLocks::undoRemovePageLock(Transaction& tx, const RangeLock* lock)
131 : {
132 23 : tx.push(new RemovePageLock(lock));
133 23 : }
134 :
135 1 : void EditPageLocks::undoRemoveAllLocks(Transaction& tx, Score* score)
136 : {
137 1 : std::vector<const RangeLock*> allLocks = score->pageLocks()->allLocks(); // copy
138 2 : for (const RangeLock* lock : allLocks) {
139 1 : undoRemovePageLock(tx, lock);
140 : }
141 1 : }
142 :
143 2 : void EditPageLocks::togglePageLock(Transaction& tx, Score* score, const std::vector<Page*>& pages)
144 : {
145 2 : bool unlockAll = true;
146 3 : for (const Page* page : pages) {
147 2 : if (!page->isLocked()) {
148 1 : unlockAll = false;
149 1 : break;
150 : }
151 : }
152 :
153 4 : for (Page* page : pages) {
154 2 : MeasureBase* startMeas = page->firstMeasureBase();
155 2 : const RangeLock* currentLock = score->pageLocks()->lockStartingAt(startMeas);
156 2 : if (currentLock && unlockAll) {
157 1 : undoRemovePageLock(tx, currentLock);
158 1 : continue;
159 1 : } else if (!currentLock && !unlockAll) {
160 1 : RangeLock* newPageLock = new RangeLock(startMeas, page->lastMeasureBase());
161 1 : undoAddPageLock(tx, newPageLock);
162 : }
163 : }
164 2 : }
165 :
166 3 : void EditPageLocks::toggleScoreLock(Transaction& tx, Score* score)
167 : {
168 3 : bool unlockAll = true;
169 7 : for (const Page* page : score->pages()) {
170 6 : if (!page->isLocked()) {
171 2 : unlockAll = false;
172 2 : break;
173 : }
174 : }
175 :
176 9 : for (Page* page : score->pages()) {
177 6 : MeasureBase* startMeas = page->firstMeasureBase();
178 6 : const RangeLock* currentLock = score->pageLocks()->lockStartingAt(startMeas);
179 6 : if (currentLock && unlockAll) {
180 4 : undoRemovePageLock(tx, currentLock);
181 4 : continue;
182 2 : } else if (!currentLock && !unlockAll) {
183 2 : RangeLock* newPageLock = new RangeLock(startMeas, page->lastMeasureBase());
184 2 : undoAddPageLock(tx, newPageLock);
185 : }
186 : }
187 3 : }
188 :
189 3 : void EditPageLocks::addRemovePageLocks(Transaction& tx, Score* score, int interval, bool lock)
190 : {
191 : // Should only be called on range selection of whole score
192 3 : MeasureBase* startMeasure = score->selection().startMeasureBase();
193 3 : MeasureBase* endMeasure = score->selection().endMeasureBase();
194 3 : if (!endMeasure) {
195 0 : endMeasure = score->lastMeasureMM();
196 : }
197 :
198 3 : if (!startMeasure || !endMeasure) {
199 2 : return;
200 : }
201 :
202 3 : if (lock) {
203 2 : for (const Page* page : score->pages()) {
204 1 : if (page->lastMeasureBase()->isBefore(startMeasure)) {
205 0 : continue;
206 : }
207 1 : if (page->firstMeasureBase()->isAfter(endMeasure)) {
208 0 : break;
209 : }
210 1 : if (!page->isLocked()) {
211 1 : undoAddPageLock(tx, new RangeLock(page->firstMeasureBase(), page->lastMeasureBase()));
212 : }
213 : }
214 1 : return;
215 : }
216 :
217 2 : std::vector<const RangeLock*> currentLocks = score->pageLocks()->locksContainedInRange(startMeasure, endMeasure);
218 7 : for (const RangeLock* l : currentLocks) {
219 5 : undoRemovePageLock(tx, l);
220 : }
221 :
222 2 : if (interval == 0) {
223 1 : return;
224 : }
225 :
226 1 : int count = 0;
227 1 : MeasureBase* lockStart = nullptr;
228 16 : for (MeasureBase* mb = startMeasure; mb; mb = mb->nextMM()) {
229 16 : if (count == 0) {
230 4 : lockStart = mb;
231 : }
232 16 : count++;
233 16 : if (count == interval || mb == endMeasure) {
234 4 : undoAddPageLock(tx, new RangeLock(lockStart, mb));
235 4 : lockStart = nullptr;
236 4 : count = 0;
237 : }
238 16 : if (mb == endMeasure) {
239 1 : break;
240 : }
241 : }
242 2 : }
243 :
244 2 : void EditPageLocks::makeIntoPage(Transaction& tx, Score* score, MeasureBase* first, MeasureBase* last)
245 : {
246 2 : const RangeLock* lockContainingFirst = score->pageLocks()->lockContaining(first);
247 2 : const RangeLock* lockContainingLast = score->pageLocks()->lockContaining(last);
248 :
249 2 : if (lockContainingFirst) {
250 1 : undoRemovePageLock(tx, lockContainingFirst);
251 1 : if (lockContainingFirst->startMB()->isBefore(first)) {
252 1 : MeasureBase* oneBeforeFirst = first->prevMM();
253 1 : RangeLock* newLockBefore = new RangeLock(lockContainingFirst->startMB(), oneBeforeFirst);
254 1 : undoAddPageLock(tx, newLockBefore);
255 : }
256 : }
257 :
258 2 : if (lockContainingLast) {
259 1 : if (lockContainingLast != lockContainingFirst) {
260 1 : undoRemovePageLock(tx, lockContainingLast);
261 : }
262 1 : if (last->isBefore(lockContainingLast->endMB())) {
263 1 : MeasureBase* oneAfterLast = last->nextMM();
264 1 : RangeLock* newLockAfter = new RangeLock(oneAfterLast, lockContainingLast->endMB());
265 1 : undoAddPageLock(tx, newLockAfter);
266 : }
267 : }
268 :
269 2 : std::vector<const RangeLock*> locksContainedInRange = score->pageLocks()->locksContainedInRange(first, last);
270 2 : for (const RangeLock* lock : locksContainedInRange) {
271 0 : if (lock != lockContainingFirst && lock != lockContainingLast) {
272 0 : undoRemovePageLock(tx, lock);
273 : }
274 : }
275 :
276 2 : RangeLock* newLock = new RangeLock(first, last);
277 2 : undoAddPageLock(tx, newLock);
278 2 : }
279 :
280 1 : void EditPageLocks::moveMeasuresToPrevPage(Transaction& tx, Score* score, MeasureBase* first, MeasureBase* last)
281 : {
282 1 : const Page* prevPage = last->prevPage();
283 1 : if (!prevPage) {
284 0 : return;
285 : }
286 :
287 : // Lock systems we are moving to the previous page
288 1 : std::vector<System*> systemsToLock;
289 1 : System* curSystem = nullptr;
290 2 : for (MeasureBase* curMeasure = last; curMeasure && curMeasure != first->prev(); curMeasure = curMeasure->prev()) {
291 1 : if (curMeasure->system() == curSystem) {
292 0 : continue;
293 : }
294 1 : curSystem = curMeasure->system();
295 :
296 1 : if (!curSystem->isLocked()) {
297 1 : systemsToLock.push_back(curSystem);
298 : }
299 : }
300 1 : EditSystemLocks::toggleSystemLock(tx, score, systemsToLock);
301 :
302 1 : MeasureBase* prevPageFirstMeas = prevPage->firstMeasureBase();
303 :
304 1 : const RangeLock* prevPageLock = score->pageLocks()->lockStartingAt(prevPageFirstMeas);
305 1 : if (prevPageLock) {
306 1 : undoRemovePageLock(tx, prevPageLock);
307 : }
308 :
309 1 : const Page* curPage = last->page();
310 1 : const RangeLock* curPageLock = score->pageLocks()->lockStartingAt(curPage->firstMeasureBase());
311 1 : if (curPageLock) {
312 1 : undoRemovePageLock(tx, curPageLock);
313 1 : if (curPageLock->endMB() != last) {
314 1 : MeasureBase* nextMB = last->nextMM();
315 1 : RangeLock* newLockOnCurPage = new RangeLock(nextMB, curPageLock->endMB());
316 1 : undoAddPageLock(tx, newLockOnCurPage);
317 : }
318 : }
319 :
320 1 : RangeLock* pageLock = new RangeLock(prevPageFirstMeas, last);
321 1 : undoAddPageLock(tx, pageLock);
322 1 : }
323 :
324 1 : void EditPageLocks::moveMeasuresToNextPage(Transaction& tx, Score* score, MeasureBase* first, MeasureBase* last)
325 : {
326 1 : const Page* curPage = first->page();
327 1 : MeasureBase* startMeas = curPage->firstMeasureBase();
328 1 : bool refMeasureIsStartOfPage = first == startMeas;
329 :
330 1 : const Page* nextPage = first->nextPage();
331 1 : if (!nextPage) {
332 0 : return;
333 : }
334 :
335 : // Lock systems we are moving to the next page
336 1 : std::vector<System*> systemsToLock;
337 1 : System* curSystem = nullptr;
338 2 : for (MeasureBase* curMeasure = first; curMeasure && curMeasure != last->next();
339 1 : curMeasure = curMeasure->next()) {
340 1 : if (curMeasure->system() == curSystem) {
341 0 : continue;
342 : }
343 1 : curSystem = curMeasure->system();
344 :
345 1 : if (!curSystem->isLocked()) {
346 1 : systemsToLock.push_back(curSystem);
347 : }
348 : }
349 1 : EditSystemLocks::toggleSystemLock(tx, score, systemsToLock);
350 :
351 1 : const RangeLock* curLock = score->pageLocks()->lockStartingAt(startMeas);
352 1 : if (curLock) {
353 1 : undoRemovePageLock(tx, curLock);
354 : }
355 :
356 1 : if (!refMeasureIsStartOfPage) {
357 1 : MeasureBase* prevMeas = first->prevMM();
358 1 : if (!curLock) {
359 0 : prevMeas->undoSetBreak(true, LayoutBreakType::PAGE);
360 : } else {
361 1 : RangeLock* pageLock = new RangeLock(startMeas, prevMeas);
362 1 : undoAddPageLock(tx, pageLock);
363 : }
364 : }
365 :
366 1 : const RangeLock* nextPageLock = score->pageLocks()->lockStartingAt(nextPage->firstMeasureBase());
367 1 : if (nextPageLock) {
368 1 : undoRemovePageLock(tx, nextPageLock);
369 : }
370 :
371 1 : if (nextPageLock || refMeasureIsStartOfPage) {
372 1 : RangeLock* newNextPageLock = new RangeLock(first, nextPage->lastMeasureBase());
373 1 : undoAddPageLock(tx, newNextPageLock);
374 : }
375 1 : }
376 :
377 1 : void EditPageLocks::applyLockToSelection(Transaction& tx, Score* score)
378 : {
379 1 : MeasureBase* first = nullptr;
380 1 : MeasureBase* last = nullptr;
381 :
382 1 : if (score->selection().isRange()) {
383 1 : first = score->selection().startMeasureBase();
384 1 : last = score->selection().endMeasureBase();
385 : } else {
386 0 : for (EngravingItem* el : score->selection().elements()) {
387 0 : if (el->isPageLockIndicator()) {
388 0 : const RangeLock* lock = toPageLockIndicator(el)->pageLock();
389 0 : first = lock->startMB();
390 0 : last = lock->endMB();
391 0 : break;
392 : }
393 0 : MeasureBase* mb = el->findMeasureBase();
394 0 : if (!mb) {
395 0 : continue;
396 : }
397 0 : if (!first || mb->isBefore(first)) {
398 0 : first = mb;
399 : }
400 0 : if (!last || mb->isAfter(last)) {
401 0 : last = mb;
402 : }
403 : }
404 : }
405 :
406 1 : if (!first || !last) {
407 0 : return;
408 : }
409 :
410 1 : const RangeLock* lockOnLast = score->pageLocks()->lockContaining(last);
411 1 : if (lockOnLast && lockOnLast->endMB() == last) {
412 0 : undoRemovePageLock(tx, lockOnLast);
413 1 : } else if (first != last) {
414 1 : makeIntoPage(tx, score, first, last);
415 : } else {
416 0 : makeIntoPage(tx, score, first->page()->firstMeasureBase(), last);
417 : }
418 : }
419 :
420 0 : void EditPageLocks::removePageLocksOnAddLayoutBreak(Transaction& tx, Score* score, LayoutBreakType breakType, const MeasureBase* measure)
421 : {
422 0 : IF_ASSERT_FAILED(breakType != LayoutBreakType::NOBREAK) {
423 0 : return; // NOBREAK not allowed on locked measures
424 : }
425 :
426 0 : const RangeLock* lock = score->pageLocks()->lockContaining(measure);
427 0 : MeasureBase* lockEndMeasure = lock ? lock->endMB() : nullptr;
428 0 : if (lock && breakType == LayoutBreakType::PAGE) {
429 0 : undoRemovePageLock(tx, lock);
430 :
431 0 : if (measure != lockEndMeasure && measure->next()) {
432 : // Make sure the resultant page is locked
433 0 : undoAddPageLock(tx, new RangeLock(measure->next(), lockEndMeasure));
434 : }
435 : }
436 : }
437 :
438 18 : void EditPageLocks::removeLayoutBreaksOnAddPageLock(Transaction&, const RangeLock* lock)
439 : {
440 112 : for (MeasureBase* mb = lock->startMB(); mb && mb->isBeforeOrEqual(lock->endMB()); mb = mb->nextMM()) {
441 94 : mb->undoSetBreak(false, LayoutBreakType::PAGE);
442 : }
443 18 : }
444 :
445 62 : void EditPageLocks::removePageLocksOnRemoveMeasures(Transaction& tx, Score* score, const MeasureBase* m1, const MeasureBase* m2)
446 : {
447 62 : std::vector<const RangeLock*> allPageLocks = score->pageLocks()->allLocks();
448 62 : for (const RangeLock* lock : allPageLocks) {
449 0 : MeasureBase* lockStart = lock->startMB();
450 0 : MeasureBase* lockEnd = lock->endMB();
451 0 : bool lockStartIsInRange = lockStart->isAfterOrEqual(m1) && lockStart->isBeforeOrEqual(m2);
452 0 : bool lockEndIsInRange = lockEnd->isAfterOrEqual(m1) && lockEnd->isBeforeOrEqual(m2);
453 0 : if (lockStartIsInRange || lockEndIsInRange) {
454 0 : undoRemovePageLock(tx, lock);
455 : }
456 0 : if (lockStartIsInRange && !lockEndIsInRange) {
457 0 : MeasureBase* newLockStart = m2->nextMeasure();
458 0 : if (newLockStart) {
459 0 : undoAddPageLock(tx, new RangeLock(newLockStart, lockEnd));
460 : }
461 0 : } else if (!lockStartIsInRange && lockEndIsInRange) {
462 0 : MeasureBase* newLockEnd = m1->prevMeasure();
463 0 : if (newLockEnd) {
464 0 : undoAddPageLock(tx, new RangeLock(lockStart, newLockEnd));
465 : }
466 : }
467 : }
468 62 : }
469 :
470 2 : void EditPageLocks::removePageLocksContainingMMRests(Transaction& tx, Score* score)
471 : {
472 2 : std::vector<const RangeLock*> allLocks = score->pageLocks()->allLocks(); // copy
473 3 : for (const RangeLock* lock : allLocks) {
474 1 : if ((lock->startMB()->isMeasure() && toMeasure(lock->startMB())->isMMRest())
475 1 : || (lock->endMB()->isMeasure() && toMeasure(lock->endMB())->isMMRest())) {
476 1 : undoRemovePageLock(tx, lock);
477 1 : continue;
478 : }
479 0 : for (MeasureBase* mb = lock->startMB(); mb; mb = mb->next()) {
480 0 : if (mb->isMeasure() && toMeasure(mb)->mmRest()) {
481 0 : undoRemovePageLock(tx, lock);
482 0 : break;
483 : }
484 0 : if (mb->isAfter(lock->endMB())) {
485 0 : break;
486 : }
487 : }
488 : }
489 2 : }
490 :
491 166 : void EditPageLocks::updatePageLocksOnCreateMMRests(Transaction& tx, Score* score, Measure* first, Measure* last)
492 : {
493 : // NOTE: this must be done during layout as the mmRests get created.
494 :
495 169 : for (const RangeLock* lock : score->pageLocks()->locksContainedInRange(first, last)) {
496 : // These locks are inside the range of the mmRest so remove them
497 3 : undoRemovePageLock(tx, lock);
498 166 : }
499 :
500 166 : const RangeLock* lockOnFirst = score->pageLocks()->lockContaining(first);
501 166 : const RangeLock* lockOnLast = score->pageLocks()->lockContaining(last);
502 :
503 166 : if (lockOnFirst) {
504 1 : MeasureBase* startMB = lockOnFirst->startMB();
505 1 : MeasureBase* endMB = lockOnFirst->endMB();
506 :
507 1 : if (startMB->isBefore(first)) {
508 1 : if (endMB->isBeforeOrEqual(last)) {
509 1 : endMB = first->mmRest();
510 : } else {
511 0 : return;
512 : }
513 : } else {
514 0 : startMB = first->mmRest();
515 : }
516 :
517 1 : if (startMB != lockOnFirst->startMB() || endMB != lockOnFirst->endMB()) {
518 1 : undoRemovePageLock(tx, lockOnFirst);
519 1 : undoAddPageLock(tx, new RangeLock(startMB, endMB));
520 : }
521 : }
522 :
523 166 : if (!lockOnLast || lockOnLast == lockOnFirst) {
524 166 : return;
525 : }
526 :
527 0 : MeasureBase* startMB = lockOnLast->startMB();
528 0 : MeasureBase* endMB = lockOnLast->endMB();
529 0 : assert(startMB->isAfter(first) && endMB->isAfter(last));
530 :
531 0 : undoRemovePageLock(tx, lockOnLast);
532 0 : startMB = last->nextMM();
533 0 : undoAddPageLock(tx, new RangeLock(startMB, endMB));
534 : }
|