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 "editchord.h"
24 :
25 : #include "../dom/arpeggio.h"
26 : #include "../dom/articulation.h"
27 : #include "../dom/chord.h"
28 : #include "../dom/chordrest.h"
29 : #include "../dom/factory.h"
30 : #include "../dom/note.h"
31 : #include "../dom/score.h"
32 : #include "../dom/segment.h"
33 : #include "../dom/tapping.h"
34 : #include "../dom/tremolotwochord.h"
35 : #include "../dom/utils.h"
36 :
37 : using namespace mu::engraving;
38 :
39 50788 : void EditChord::addChordParentheses(Chord* chord, std::vector<Note*> notes, bool addToLinked, bool generated)
40 : {
41 50788 : if (notes.empty()) {
42 50610 : return;
43 : }
44 178 : undoAddParenthesesToNotes(chord, notes, addToLinked, generated);
45 : }
46 :
47 : //---------------------------------------------------------
48 : // toggleArticulation
49 : /// Toggle attribute \a attr for all selected notes/rests.
50 : //---------------------------------------------------------
51 :
52 0 : void EditChord::toggleArticulation(Score* score, SymId attr)
53 : {
54 0 : std::set<Chord*> set;
55 0 : for (EngravingItem* el : score->selection().elements()) {
56 0 : if (el->isNote() || el->isChord()) {
57 0 : Chord* cr = nullptr;
58 0 : if (el->isNote()) {
59 0 : cr = toNote(el)->chord();
60 0 : if (muse::contains(set, cr)) {
61 0 : continue;
62 : }
63 : }
64 0 : Articulation* na = Factory::createArticulation(score->dummy());
65 0 : na->setSymId(attr);
66 0 : if (!EditChord::toggleArticulation(score, el, na)) {
67 0 : delete na;
68 : }
69 0 : if (cr) {
70 0 : set.insert(cr);
71 : }
72 : }
73 : }
74 0 : }
75 :
76 24 : bool EditChord::toggleArticulation(Score* score, EngravingItem* el, Articulation* a)
77 : {
78 : Chord* c;
79 24 : if (el->isNote()) {
80 24 : c = toNote(el)->chord();
81 0 : } else if (el->isChord()) {
82 0 : c = toChord(el);
83 : } else {
84 0 : return false;
85 : }
86 24 : Articulation* oa = c->hasArticulation(a);
87 24 : if (oa) {
88 0 : score->undoRemoveElement(oa);
89 0 : return false;
90 : }
91 :
92 24 : Tapping* tap = c->tapping();
93 24 : if (tap) {
94 0 : score->undoRemoveElement(tap);
95 : }
96 :
97 24 : if (!a->isDouble()) {
98 24 : a->setOwnershipParent(c);
99 24 : a->setTrack(c->track());
100 24 : score->undoAddElement(a);
101 24 : return true;
102 : }
103 :
104 0 : std::set<SymId> newSubComponentIds = splitArticulations({ a->symId() });
105 0 : for (const SymId& id : newSubComponentIds) {
106 0 : Articulation* articCopy = a->clone();
107 0 : articCopy->setSymId(id);
108 :
109 0 : if (!c->hasArticulation(articCopy)) {
110 0 : articCopy->setOwnershipParent(c);
111 0 : articCopy->setTrack(c->track());
112 0 : score->undoAddElement(articCopy);
113 0 : continue;
114 : }
115 0 : delete articCopy;
116 : }
117 0 : return true;
118 0 : }
119 :
120 51075 : void EditChord::removeChordParentheses(Chord* chord, std::vector<Note*> notes, bool addToLinked, bool generated)
121 : {
122 51075 : if (notes.empty()) {
123 50677 : return;
124 : }
125 : // Split based on parentheses group
126 398 : std::map<const NoteParenthesisInfo*, std::vector<Note*> > notesByGroup;
127 :
128 821 : for (Note* n : notes) {
129 423 : const NoteParenthesisInfo* noteParenInfo = chord->findNoteParenthesisInfo(n);
130 423 : if (!noteParenInfo) {
131 419 : continue;
132 : }
133 :
134 13 : auto groupIt = notesByGroup.find(noteParenInfo);
135 :
136 13 : if (groupIt == notesByGroup.end()) {
137 18 : std::vector<Note*> noteVec { n };
138 9 : notesByGroup.insert(std::make_pair(noteParenInfo, noteVec));
139 9 : continue;
140 9 : }
141 :
142 4 : groupIt->second.push_back(n);
143 : }
144 :
145 416 : for (auto pairIterator = notesByGroup.rbegin(); pairIterator != notesByGroup.rend(); pairIterator = std::next(pairIterator)) {
146 9 : auto groupNotesPair = *pairIterator;
147 9 : const NoteParenthesisInfo* parenInfo = groupNotesPair.first;
148 9 : if (parenInfo->notes().size() == groupNotesPair.second.size()) {
149 : // All notes marked for paren removal, clear parentheses group
150 4 : undoClearParenthesisGroup(chord, parenInfo->notes(), parenInfo->leftParen(), parenInfo->rightParen(), addToLinked);
151 4 : continue;
152 : }
153 :
154 : // Only some notes marked for removal. Remove from lowest note up. This keeps the paren group splitting logic safe
155 10 : for (auto noteIterator = groupNotesPair.second.begin(); noteIterator != groupNotesPair.second.end();
156 5 : noteIterator = std::next(noteIterator)) {
157 : // Remove paren from single note and create new paren group for all notes below
158 5 : Note* note = *noteIterator;
159 5 : const NoteParenthesisInfo* curParenInfo = note->parenthesisInfo();
160 5 : const std::vector<Note*> notesList = curParenInfo->notes();
161 5 : Parenthesis* leftParen = curParenInfo->leftParen();
162 5 : Parenthesis* rightParen = curParenInfo->rightParen();
163 5 : auto notePos = std::find(notesList.begin(), notesList.end(), note);
164 :
165 10 : if (notePos != notesList.end() && std::next(notePos) != notesList.end() && notePos != notesList.begin()) {
166 : // Create new group
167 2 : std::vector<Note*> newNoteGroup(notePos + 1, notesList.end());
168 :
169 6 : for (Note* noteToRemove : newNoteGroup) {
170 4 : if (curParenInfo->notes().size() == 1) {
171 0 : undoClearParenthesisGroup(chord, { noteToRemove }, leftParen, rightParen, addToLinked);
172 : } else {
173 4 : undoRemoveParenthesesFromNote(chord, noteToRemove, leftParen, addToLinked);
174 : }
175 : }
176 :
177 2 : undoAddParenthesesToNotes(chord, newNoteGroup, addToLinked, generated);
178 2 : }
179 5 : if (curParenInfo->notes().size() == 1) {
180 0 : undoClearParenthesisGroup(chord, { note }, leftParen, rightParen, addToLinked);
181 : } else {
182 5 : undoRemoveParenthesesFromNote(chord, note, leftParen, addToLinked);
183 : }
184 5 : }
185 9 : }
186 398 : }
187 :
188 180 : void EditChord::undoAddParenthesesToNotes(Chord* chord, std::vector<Note*> notes, bool addToLinked, bool generated)
189 : {
190 180 : track_idx_t track = chord->track();
191 180 : Parenthesis* leftParen = Factory::createParenthesis(chord);
192 180 : leftParen->setTrack(track);
193 180 : leftParen->setDirection(DirectionH::LEFT);
194 180 : leftParen->setGenerated(generated);
195 180 : Parenthesis* rightParen = Factory::createParenthesis(chord);
196 180 : rightParen->setTrack(track);
197 180 : rightParen->setDirection(DirectionH::RIGHT);
198 180 : rightParen->setGenerated(generated);
199 :
200 180 : if (!addToLinked) {
201 67 : doAddNoteParentheses(chord, notes, leftParen, rightParen);
202 67 : return;
203 : }
204 :
205 231 : for (EngravingObject* linkedObject : chord->linkList()) {
206 118 : if (linkedObject == chord) {
207 113 : doAddNoteParentheses(chord, notes, leftParen, rightParen);
208 113 : continue;
209 : }
210 :
211 5 : Chord* linkedChord = toChord(linkedObject);
212 5 : Score* linkedScore = linkedChord->score();
213 5 : Staff* linkedStaff = linkedChord->staff();
214 :
215 5 : std::vector<Note*> linkedNotes;
216 5 : linkedNotes.reserve(notes.size());
217 19 : for (Note* note : notes) {
218 14 : Note* linkedNote = toNote(note->findLinkedInStaff(linkedStaff));
219 14 : if (!linkedNote) {
220 0 : continue;
221 : }
222 14 : linkedNotes.push_back(linkedNote);
223 : }
224 5 : if (linkedNotes.empty()) {
225 0 : continue;
226 : }
227 :
228 5 : Parenthesis* linkedParenLeft = toParenthesis(leftParen->linkedClone());
229 5 : linkedParenLeft->setScore(linkedScore);
230 5 : linkedParenLeft->setOwnershipParent(linkedChord);
231 5 : linkedParenLeft->setTrack(linkedChord->track());
232 5 : Parenthesis* linkedParenRight = toParenthesis(rightParen->linkedClone());
233 5 : linkedParenRight->setScore(linkedScore);
234 5 : linkedParenRight->setOwnershipParent(linkedChord);
235 5 : linkedParenRight->setTrack(linkedChord->track());
236 :
237 5 : doAddNoteParentheses(linkedChord, linkedNotes, linkedParenLeft, linkedParenRight);
238 118 : }
239 : }
240 :
241 9 : void EditChord::undoRemoveParenthesesFromNote(Chord* chord, Note* note, Parenthesis* leftParen, bool removeFromLinked)
242 : {
243 9 : if (!removeFromLinked) {
244 3 : doRemoveSingleNoteParenthesis(chord, note, leftParen);
245 3 : return;
246 : }
247 :
248 15 : for (EngravingObject* linkedObject : chord->linkList()) {
249 9 : if (linkedObject == chord) {
250 6 : doRemoveSingleNoteParenthesis(chord, note, leftParen);
251 6 : continue;
252 : }
253 :
254 3 : Chord* linkedChord = toChord(linkedObject);
255 3 : Staff* linkedStaff = linkedChord->staff();
256 3 : Parenthesis* linkedLeftParen = toParenthesis(leftParen->findLinkedInStaff(linkedStaff));
257 3 : if (!linkedLeftParen) {
258 0 : continue;
259 : }
260 :
261 3 : Note* linkedNote = toNote(note->findLinkedInStaff(linkedStaff));
262 3 : if (!linkedNote) {
263 0 : continue;
264 : }
265 :
266 3 : const NoteParenthesisInfo* noteParenInfo = linkedChord->findNoteParenthesisInfo(linkedNote);
267 3 : if (noteParenInfo) {
268 3 : doRemoveSingleNoteParenthesis(linkedChord, linkedNote, linkedLeftParen);
269 : }
270 6 : }
271 : }
272 :
273 4 : void EditChord::undoClearParenthesisGroup(Chord* chord, std::vector<Note*> notes, Parenthesis* leftParen, Parenthesis* rightParen,
274 : bool removeFromLinked)
275 : {
276 4 : if (!removeFromLinked) {
277 3 : doRemoveAllNoteParentheses(chord, leftParen);
278 3 : return;
279 : }
280 :
281 2 : for (EngravingObject* linkedObject : chord->linkList()) {
282 1 : if (linkedObject == chord) {
283 1 : doRemoveAllNoteParentheses(chord, leftParen);
284 1 : continue;
285 : }
286 :
287 0 : Chord* linkedChord = toChord(linkedObject);
288 0 : Staff* linkedStaff = linkedChord->staff();
289 0 : Parenthesis* linkedLeftParen = toParenthesis(leftParen->findLinkedInStaff(linkedStaff));
290 0 : Parenthesis* linkedRightParen = toParenthesis(rightParen->findLinkedInStaff(linkedStaff));
291 :
292 0 : if (!linkedLeftParen && !linkedRightParen) {
293 0 : continue;
294 : }
295 :
296 0 : std::vector<Note*> linkedNotes;
297 0 : linkedNotes.reserve(notes.size());
298 0 : for (Note* note : notes) {
299 0 : Note* linkedNote = toNote(note->findLinkedInStaff(linkedStaff));
300 0 : if (!linkedNote) {
301 0 : continue;
302 : }
303 0 : linkedNotes.push_back(linkedNote);
304 : }
305 :
306 0 : doRemoveAllNoteParentheses(linkedChord, linkedLeftParen);
307 1 : }
308 : }
309 :
310 185 : void EditChord::doAddNoteParentheses(Chord* chord, std::vector<Note*> notes, Parenthesis* leftParen, Parenthesis* rightParen)
311 : {
312 185 : NoteParenthesisInfo* parenInfo = new NoteParenthesisInfo(leftParen, rightParen, notes);
313 185 : if (leftParen->generated()) {
314 67 : chord->addNoteParenthesisInfo(parenInfo);
315 : } else {
316 118 : Score* score = chord->score();
317 118 : score->undo(new AddNoteParenthesisInfo(chord, parenInfo));
318 : }
319 185 : }
320 :
321 12 : void EditChord::doRemoveSingleNoteParenthesis(Chord* chord, Note* note, Parenthesis* leftParen)
322 : {
323 12 : if (leftParen->generated()) {
324 3 : chord->removeNoteFromParenthesisInfo(note, leftParen);
325 :
326 3 : chord->triggerLayout();
327 : } else {
328 9 : Score* score = chord->score();
329 9 : score->undo(new RemoveSingleNoteParentheses(chord, note, leftParen));
330 : }
331 12 : }
332 :
333 4 : void EditChord::doRemoveAllNoteParentheses(Chord* chord, Parenthesis* leftParen)
334 : {
335 4 : if (leftParen->generated()) {
336 3 : const NoteParenthesisInfo* noteParenInfo = chord->findNoteParenthesisInfo(leftParen);
337 :
338 3 : chord->removeNoteParenthesisInfo(noteParenInfo);
339 : //! HACK: don't delete as it may still be used in PropertiesPanel - see Score::doUndoRemoveElement
340 : // delete noteParenInfo
341 :
342 3 : chord->triggerLayout();
343 : } else {
344 1 : Score* score = chord->score();
345 1 : NoteParenthesisInfo* noteParenInfo = chord->findNoteParenthesisInfo(leftParen);
346 :
347 1 : score->undo(new RemoveNoteParenthesisInfo(chord, noteParenInfo));
348 : }
349 4 : }
350 :
351 : //---------------------------------------------------------
352 : // ChangeChordStaffMove
353 : //---------------------------------------------------------
354 :
355 4 : void ChangeChordStaffMove::flip()
356 : {
357 4 : int v = chordRest->staffMove();
358 4 : staff_idx_t oldStaff = chordRest->vStaffIdx();
359 :
360 4 : chordRest->setStaffMove(staffMove);
361 4 : chordRest->checkStaffMoveValidity();
362 4 : chordRest->triggerLayout();
363 4 : if (chordRest->vStaffIdx() == oldStaff) {
364 0 : return;
365 : }
366 :
367 8 : for (EngravingObject* e : chordRest->linkList()) {
368 4 : ChordRest* cr = toChordRest(e);
369 4 : if (cr == chordRest) {
370 4 : continue;
371 : }
372 0 : cr->setStaffMove(staffMove);
373 0 : cr->checkStaffMoveValidity();
374 0 : cr->triggerLayout();
375 4 : }
376 4 : staffMove = v;
377 : }
378 :
379 : //---------------------------------------------------------
380 : // SwapCR
381 : //---------------------------------------------------------
382 :
383 0 : void SwapCR::flip()
384 : {
385 0 : Segment* s1 = cr1->segment();
386 0 : Segment* s2 = cr2->segment();
387 0 : track_idx_t track = cr1->track();
388 :
389 0 : if (cr1->isChord() && cr2->isChord() && toChord(cr1)->tremoloTwoChord()
390 0 : && (toChord(cr1)->tremoloTwoChord() == toChord(cr2)->tremoloTwoChord())) {
391 0 : TremoloTwoChord* t = toChord(cr1)->tremoloTwoChord();
392 0 : Chord* c1 = t->chord1();
393 0 : Chord* c2 = t->chord2();
394 0 : t->setOwnershipParent(toChord(c2));
395 0 : t->setChords(toChord(c2), toChord(c1));
396 : }
397 :
398 0 : EngravingItem* cr = s1->element(track);
399 0 : s1->setElement(track, s2->element(track));
400 0 : s2->setElement(track, cr);
401 0 : cr1->score()->setLayout(s1->tick(), cr1->staffIdx(), cr1);
402 0 : cr1->score()->setLayout(s2->tick(), cr1->staffIdx(), cr1);
403 0 : }
404 :
405 : //---------------------------------------------------------
406 : // ChangeSpanArpeggio
407 : //---------------------------------------------------------
408 :
409 81 : void ChangeSpanArpeggio::flip()
410 : {
411 81 : Arpeggio* f_spanArp = m_chord->spanArpeggio();
412 :
413 81 : m_chord->setSpanArpeggio(m_spanArpeggio);
414 81 : m_spanArpeggio = f_spanArp;
415 81 : }
416 :
417 118 : void AddNoteParenthesisInfo::redo()
418 : {
419 118 : m_chord->addNoteParenthesisInfo(m_noteParenInfo);
420 :
421 118 : m_chord->triggerLayout();
422 118 : }
423 :
424 9 : void mu::engraving::AddNoteParenthesisInfo::undo()
425 : {
426 9 : m_chord->removeNoteParenthesisInfo(m_noteParenInfo);
427 :
428 9 : m_chord->triggerLayout();
429 9 : }
430 :
431 0 : void AddNoteParenthesisInfo::cleanup(bool wasDone)
432 : {
433 0 : if (!wasDone) {
434 0 : delete m_noteParenInfo;
435 0 : m_noteParenInfo = nullptr;
436 : }
437 0 : }
438 :
439 1 : void RemoveNoteParenthesisInfo::redo()
440 : {
441 1 : m_chord->removeNoteParenthesisInfo(m_noteParenInfo);
442 :
443 1 : m_chord->triggerLayout();
444 1 : }
445 :
446 0 : void RemoveNoteParenthesisInfo::undo()
447 : {
448 0 : m_chord->addNoteParenthesisInfo(m_noteParenInfo);
449 :
450 0 : m_chord->triggerLayout();
451 0 : }
452 :
453 0 : void RemoveNoteParenthesisInfo::cleanup(bool wasDone)
454 : {
455 0 : if (wasDone) {
456 0 : delete m_noteParenInfo;
457 0 : m_noteParenInfo = nullptr;
458 : }
459 0 : }
460 :
461 9 : void RemoveSingleNoteParentheses::redo()
462 : {
463 9 : m_chord->removeNoteFromParenthesisInfo(m_note, m_paren);
464 :
465 9 : m_chord->triggerLayout();
466 9 : }
467 :
468 9 : void RemoveSingleNoteParentheses::undo()
469 : {
470 9 : m_chord->addNoteToParenthesisInfo(m_note, m_paren);
471 :
472 9 : m_chord->triggerLayout();
473 9 : }
|