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) 2025 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 "addremoveelement.h"
24 :
25 : #include "containers.h"
26 :
27 : #include "../dom/arpeggio.h"
28 : #include "../dom/chord.h"
29 : #include "../dom/engravingitem.h"
30 : #include "../dom/keysig.h"
31 : #include "../dom/linkedobjects.h"
32 : #include "../dom/measure.h"
33 : #include "../dom/note.h"
34 : #include "../dom/score.h"
35 : #include "../dom/segment.h"
36 : #include "../dom/spanner.h"
37 : #include "../dom/staff.h"
38 : #include "../dom/system.h"
39 : #include "../dom/tie.h"
40 : #include "../dom/tremolotwochord.h"
41 : #include "../dom/tuplet.h"
42 :
43 : using namespace mu::engraving;
44 :
45 : //---------------------------------------------------------
46 : // undoRemoveTuplet
47 : //---------------------------------------------------------
48 :
49 5664 : static void undoRemoveTuplet(DurationElement* cr)
50 : {
51 5664 : if (cr->tuplet()) {
52 495 : cr->tuplet()->remove(cr);
53 495 : if (cr->tuplet()->elements().empty()) {
54 91 : undoRemoveTuplet(cr->tuplet());
55 : }
56 : }
57 5664 : }
58 :
59 : //---------------------------------------------------------
60 : // undoAddTuplet
61 : //---------------------------------------------------------
62 :
63 6785 : static void undoAddTuplet(DurationElement* cr)
64 : {
65 6785 : if (cr->tuplet()) {
66 560 : cr->tuplet()->add(cr);
67 560 : if (cr->tuplet()->elements().size() == 1) {
68 134 : undoAddTuplet(cr->tuplet());
69 : }
70 : }
71 6785 : }
72 :
73 : //---------------------------------------------------------
74 : // AddElement
75 : //---------------------------------------------------------
76 :
77 14309 : AddElement::AddElement(EngravingItem* e)
78 : {
79 14309 : DO_ASSERT_X(!e->generated(), String(u"Generated item %1 passed to AddElement").arg(String::fromAscii(e->typeName())));
80 14309 : DO_ASSERT_X(e->parent()->score() == e->score(),
81 : String(u"Item %1 is in a different score to its parent %2").arg(String::fromAscii(e->typeName()),
82 : String::fromAscii(e->parent()->typeName())));
83 14309 : element = e;
84 14309 : }
85 :
86 7495 : void AddElement::cleanup(bool wasDone)
87 : {
88 7495 : if (!wasDone) {
89 689 : delete element;
90 689 : element = nullptr;
91 : }
92 7495 : }
93 :
94 15273 : void AddElement::endUndoRedo(bool isUndo) const
95 : {
96 15273 : if (element->isChordRest()) {
97 6801 : if (isUndo) {
98 508 : undoRemoveTuplet(toChordRest(element));
99 : } else {
100 6293 : undoAddTuplet(toChordRest(element));
101 : }
102 8472 : } else if (element->isClef()) {
103 38 : element->triggerLayout();
104 38 : element->score()->setLayout(element->staff()->nextClefTick(element->tick()), element->staffIdx());
105 8434 : } else if (element->isKeySig()) {
106 235 : element->triggerLayout();
107 235 : element->score()->setLayout(element->staff()->nextKeyTick(element->tick()), element->staffIdx());
108 : }
109 15273 : }
110 :
111 831 : void AddElement::undo()
112 : {
113 831 : Score* score = element->score();
114 831 : EngravingItem::disconnectSharedTree(element);
115 :
116 831 : if (!element->isTuplet()) {
117 831 : score->removeElement(element);
118 : }
119 :
120 831 : endUndoRedo(true);
121 831 : }
122 :
123 14442 : void AddElement::redo()
124 : {
125 14442 : Score* score = element->score();
126 :
127 14442 : if (!element->isTuplet()) {
128 14439 : score->addElement(element);
129 : }
130 :
131 14442 : endUndoRedo(false);
132 14442 : }
133 :
134 0 : const char* AddElement::name() const
135 : {
136 : static char buffer[64];
137 0 : if (element->isTextBase()) {
138 0 : snprintf(buffer, 64, "Add: %s <%s> %p", element->typeName(),
139 0 : muPrintable(toTextBase(element)->plainText()), element);
140 0 : } else if (element->isSegment()) {
141 0 : snprintf(buffer, 64, "Add: <%s-%s> %p", element->typeName(), toSegment(element)->subTypeName(), element);
142 : } else {
143 0 : snprintf(buffer, 64, "Add: <%s> %p", element->typeName(), element);
144 : }
145 0 : return buffer;
146 : }
147 :
148 0 : bool AddElement::matchesFilter(UndoableCommandFilter f, const EngravingItem* target) const
149 : {
150 0 : switch (f) {
151 0 : case UndoableCommandFilter::AddElement:
152 0 : return target == element;
153 0 : case UndoableCommandFilter::AddElementLinked:
154 0 : return muse::contains(target->linkList(), static_cast<EngravingObject*>(element));
155 0 : default:
156 0 : break;
157 : }
158 0 : return false;
159 : }
160 :
161 9348 : std::vector<EngravingObject*> AddElement::objectItems() const
162 : {
163 9348 : return compoundObjects(element);
164 : }
165 :
166 : //---------------------------------------------------------
167 : // removeNote
168 : // Helper function for RemoveElement class
169 : //---------------------------------------------------------
170 :
171 2662 : static void removeNote(const Note* note)
172 : {
173 2662 : Score* score = note->score();
174 2662 : Tie* tieFor = note->tieFor();
175 2662 : Tie* tieBack = note->tieBack();
176 2662 : if (tieFor && tieFor->endNote()) {
177 822 : score->doUndoRemoveElement(tieFor);
178 : }
179 2662 : if (tieBack) {
180 92 : if (tieBack->tieJumpPoints() && tieBack->tieJumpPoints()->size() > 1) {
181 1 : Tie::changeTieType(tieBack);
182 : } else {
183 91 : score->doUndoRemoveElement(tieBack);
184 : }
185 : }
186 2662 : for (Spanner* s : note->spannerBack()) {
187 0 : score->doUndoRemoveElement(s);
188 : }
189 2662 : for (Spanner* s : note->spannerFor()) {
190 0 : score->doUndoRemoveElement(s);
191 : }
192 2662 : }
193 :
194 : //---------------------------------------------------------
195 : // RemoveElement
196 : //---------------------------------------------------------
197 :
198 11016 : RemoveElement::RemoveElement(EngravingItem* e)
199 : {
200 11016 : DO_ASSERT_X(!e->generated(), String(u"Generated item %1 passed to RemoveElement").arg(String::fromAscii(e->typeName())));
201 11016 : element = e;
202 :
203 11016 : Score* score = element->score();
204 11016 : if (element->isChordRest()) {
205 5025 : ChordRest* cr = toChordRest(element);
206 5025 : if (cr->tuplet() && cr->tuplet()->elements().size() <= 1) {
207 61 : score->doUndoRemoveElement(cr->tuplet());
208 : }
209 5025 : if (e->isChord()) {
210 2224 : Chord* chord = toChord(e);
211 : // remove tremolo between 2 notes
212 2224 : if (chord->tremoloTwoChord()) {
213 0 : TremoloTwoChord* tremolo = chord->tremoloTwoChord();
214 0 : score->doUndoRemoveElement(tremolo);
215 : }
216 : // Move arpeggio down to next available note
217 2224 : if (chord->arpeggio()) {
218 1 : chord->arpeggio()->rebaseStartAnchor(AnchorRebaseDirection::DOWN);
219 : } else {
220 : // If this chord is the end of an arpeggio, move the end of the arpeggio upwards to the next available chord
221 2223 : Arpeggio* spanArp = chord->spanArpeggio();
222 2223 : if (spanArp && chord->track() == spanArp->endTrack()) {
223 0 : spanArp->rebaseEndAnchor(AnchorRebaseDirection::UP);
224 : }
225 : }
226 4549 : for (const Note* note : chord->notes()) {
227 2325 : removeNote(note);
228 : }
229 : }
230 5991 : } else if (element->isNote()) {
231 : // Removing an individual note within a chord
232 337 : const Note* note = toNote(element);
233 337 : removeNote(note);
234 : }
235 11016 : }
236 :
237 5762 : void RemoveElement::cleanup(bool wasDone)
238 : {
239 5762 : if (wasDone) {
240 5115 : delete element;
241 5115 : element = nullptr;
242 : }
243 5762 : }
244 :
245 738 : void RemoveElement::undo()
246 : {
247 738 : Score* score = element->score();
248 :
249 738 : if (!element->isTuplet()) {
250 729 : score->addElement(element);
251 : }
252 :
253 738 : if (element->isChordRest()) {
254 358 : if (element->isChord()) {
255 118 : Chord* chord = toChord(element);
256 237 : for (Note* note : chord->notes()) {
257 119 : note->connectTiedNotes();
258 : }
259 : }
260 358 : undoAddTuplet(toChordRest(element));
261 380 : } else if (element->isClef()) {
262 4 : score->setLayout(element->staff()->nextClefTick(element->tick()), element->staffIdx());
263 376 : } else if (element->isKeySig()) {
264 9 : score->setLayout(element->staff()->nextKeyTick(element->tick()), element->staffIdx());
265 : }
266 738 : }
267 :
268 11105 : void RemoveElement::redo()
269 : {
270 11105 : Score* score = element->score();
271 :
272 11105 : EngravingItem::disconnectSharedTree(element);
273 11105 : if (!element->isTuplet()) {
274 11044 : score->removeElement(element);
275 : }
276 :
277 11105 : if (element->isChordRest()) {
278 5065 : undoRemoveTuplet(toChordRest(element));
279 5065 : if (element->isChord()) {
280 2226 : Chord* chord = toChord(element);
281 4553 : for (Note* note : chord->notes()) {
282 2327 : note->disconnectTiedNotes();
283 : }
284 : }
285 6040 : } else if (element->isClef()) {
286 13 : score->setLayout(element->staff()->nextClefTick(element->tick()), element->staffIdx());
287 6027 : } else if (element->isKeySig()) {
288 41 : score->setLayout(element->staff()->nextKeyTick(element->tick()), element->staffIdx());
289 : }
290 11105 : }
291 :
292 0 : const char* RemoveElement::name() const
293 : {
294 : static char buffer[64];
295 0 : if (element->isTextBase()) {
296 0 : snprintf(buffer, 64, "Remove: %s <%s> %p", element->typeName(),
297 0 : muPrintable(toTextBase(element)->plainText()), element);
298 0 : } else if (element->isSegment()) {
299 0 : snprintf(buffer, 64, "Remove: <%s-%s> %p", element->typeName(), toSegment(element)->subTypeName(), element);
300 : } else {
301 0 : snprintf(buffer, 64, "Remove: %s %p", element->typeName(), element);
302 : }
303 0 : return buffer;
304 : }
305 :
306 0 : bool RemoveElement::matchesFilter(UndoableCommandFilter f, const EngravingItem* target) const
307 : {
308 0 : switch (f) {
309 0 : case UndoableCommandFilter::RemoveElement:
310 0 : return target == element;
311 0 : case UndoableCommandFilter::RemoveElementLinked:
312 0 : return muse::contains(target->linkList(), static_cast<EngravingObject*>(element));
313 0 : default:
314 0 : break;
315 : }
316 0 : return false;
317 : }
318 :
319 7374 : std::vector<EngravingObject*> RemoveElement::objectItems() const
320 : {
321 7374 : return compoundObjects(element);
322 : }
323 :
324 : //---------------------------------------------------------
325 : // ChangeElement
326 : //---------------------------------------------------------
327 :
328 115 : ChangeElement::ChangeElement(EngravingItem* oe, EngravingItem* ne)
329 : {
330 115 : oldElement = oe;
331 115 : newElement = ne;
332 115 : }
333 :
334 115 : void ChangeElement::flip()
335 : {
336 115 : const LinkedObjects* links = oldElement->links();
337 115 : if (links) {
338 0 : newElement->linkTo(oldElement);
339 0 : oldElement->unlink();
340 : }
341 :
342 115 : newElement->setTrack(oldElement->track());
343 115 : if (newElement->isSpanner()) {
344 0 : toSpanner(newElement)->setTrack2(toSpanner(oldElement)->track2());
345 : }
346 :
347 115 : Score* score = oldElement->score();
348 115 : if (!score->selection().isRange()) {
349 14 : if (oldElement->selected()) {
350 0 : score->deselect(oldElement);
351 : }
352 14 : if (newElement->selected()) {
353 0 : score->select(newElement, SelectType::ADD);
354 : }
355 : }
356 :
357 115 : if (oldElement->ownershipParent() == nullptr) {
358 0 : newElement->setScore(score);
359 0 : score->removeElement(oldElement);
360 0 : score->addElement(newElement);
361 : } else {
362 115 : oldElement->ownershipParentItem()->change(oldElement, newElement);
363 : }
364 :
365 115 : if (newElement->isKeySig()) {
366 0 : KeySig* ks = toKeySig(newElement);
367 0 : if (!ks->generated()) {
368 0 : ks->staff()->setKey(ks->tick(), ks->keySigEvent());
369 : }
370 : }
371 :
372 115 : if (newElement->isSpannerSegment()) {
373 0 : SpannerSegment* os = toSpannerSegment(oldElement);
374 0 : SpannerSegment* ns = toSpannerSegment(newElement);
375 0 : if (os->system()) {
376 0 : os->system()->remove(os);
377 : }
378 0 : if (ns->system()) {
379 0 : ns->system()->add(ns);
380 : }
381 : }
382 :
383 115 : std::swap(oldElement, newElement);
384 115 : oldElement->triggerLayout();
385 115 : newElement->triggerLayout();
386 115 : }
387 :
388 : //---------------------------------------------------------
389 : // ChangeParent
390 : //---------------------------------------------------------
391 :
392 187 : void ChangeParent::flip()
393 : {
394 187 : EngravingItem* p = element->ownershipParentItem();
395 187 : staff_idx_t si = element->staffIdx();
396 187 : p->remove(element);
397 187 : element->setOwnershipParent(parent);
398 187 : element->setTrack(staffIdx * VOICES + element->voice());
399 187 : parent->add(element);
400 187 : staffIdx = si;
401 187 : parent = p;
402 :
403 187 : element->triggerLayout();
404 187 : }
405 :
406 : //---------------------------------------------------------
407 : // LinkUnlink
408 : //---------------------------------------------------------
409 :
410 5363 : LinkUnlink::~LinkUnlink()
411 : {
412 5363 : if (le && mustDelete) {
413 381 : assert(le->size() <= 1);
414 381 : delete le;
415 : }
416 5363 : }
417 :
418 4967 : void LinkUnlink::link()
419 : {
420 4967 : if (le->size() == 1) {
421 4186 : le->front()->setLinks(le);
422 : }
423 4967 : mustDelete = false;
424 4967 : le->push_back(e);
425 4967 : e->setLinks(le);
426 4967 : }
427 :
428 610 : void LinkUnlink::unlink()
429 : {
430 610 : assert(le->contains(e));
431 610 : le->remove(e);
432 610 : if (le->size() == 1) {
433 409 : le->front()->setLinks(nullptr);
434 409 : mustDelete = true;
435 : }
436 :
437 610 : e->setLinks(nullptr);
438 610 : }
439 :
440 : //---------------------------------------------------------
441 : // Link
442 : // link e1 to e2
443 : //---------------------------------------------------------
444 :
445 4941 : Link::Link(EngravingObject* e1, EngravingObject* e2)
446 : {
447 4941 : assert(e1->links() == nullptr);
448 4941 : le = e2->links();
449 4941 : if (!le) {
450 4067 : le = new LinkedObjects();
451 4067 : le->push_back(e2);
452 : }
453 4941 : e = e1;
454 4941 : }
455 :
456 0 : bool Link::matchesFilter(UndoableCommandFilter f, const EngravingItem* target) const
457 : {
458 0 : if (f == UndoableCommandFilter::Link) {
459 0 : return e == target || le->contains(const_cast<EngravingItem*>(target));
460 : }
461 0 : return false;
462 : }
463 :
464 : //---------------------------------------------------------
465 : // Unlink
466 : //---------------------------------------------------------
467 :
468 536 : Unlink::Unlink(EngravingObject* _e)
469 : {
470 536 : e = _e;
471 536 : le = e->links();
472 536 : assert(le);
473 536 : }
474 :
475 8 : void ChangeSegmentParent::flip()
476 : {
477 8 : Measure* p = segment->measure();
478 8 : Fraction oldTick = segment->rtick();
479 8 : p->remove(segment);
480 8 : segment->setOwnershipParent(parent);
481 8 : segment->setRtick(tick);
482 8 : parent->add(segment);
483 :
484 8 : parent = p;
485 8 : tick = oldTick;
486 :
487 8 : segment->triggerLayout();
488 8 : }
|