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
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 "editparentheses.h"
24 :
25 : #include <algorithm>
26 : #include <map>
27 : #include <set>
28 :
29 : #include "editchord.h"
30 :
31 : #include "../dom/accidental.h"
32 : #include "../dom/chord.h"
33 : #include "../dom/engravingitem.h"
34 : #include "../dom/note.h"
35 : #include "../dom/score.h"
36 : #include "../dom/select.h"
37 : #include "../dom/timesig.h"
38 : #include "../dom/utils.h"
39 :
40 : #include "log.h"
41 :
42 : using namespace mu::engraving;
43 :
44 : namespace {
45 : struct NoteComparator {
46 173 : bool operator()(const Note* n1, const Note* n2) const
47 : {
48 173 : if (noteIsBefore(n1, n2)) {
49 25 : return true;
50 : }
51 148 : if (noteIsBefore(n2, n1)) {
52 98 : return false;
53 : }
54 : // tiebreak by pointer address so we can have unison notes in the set
55 50 : return n1 < n2;
56 : }
57 : };
58 :
59 14 : static std::map<Chord*, std::set<Note*, NoteComparator> > getNotesByChord(const std::vector<Note*>& notes)
60 : {
61 : // Return map of notes by chord
62 : // Include all notes between highest and lowest in each chord
63 14 : std::map<Chord*, std::set<Note*, NoteComparator> > notesByChord;
64 55 : for (Note* noteToAdd : notes) {
65 41 : Chord* chord = noteToAdd->chord();
66 41 : auto notesByChordIt = notesByChord.find(chord);
67 :
68 41 : if (notesByChordIt == notesByChord.end()) {
69 32 : notesByChord.emplace(chord, std::set<Note*, NoteComparator> { noteToAdd });
70 16 : continue;
71 : }
72 :
73 : // Add all notes between last note and this one
74 25 : const Note* prevNote = *notesByChordIt->second.rbegin();
75 :
76 25 : const Note* firstNote = noteIsBefore(noteToAdd, prevNote) ? noteToAdd : prevNote;
77 25 : const Note* secondNote = firstNote == noteToAdd ? prevNote : noteToAdd;
78 :
79 25 : const std::vector<Note*>& chordNotes = chord->notes();
80 :
81 25 : std::vector<Note*>::const_iterator firstNoteIt = std::find(chordNotes.begin(), chordNotes.end(), firstNote);
82 25 : std::vector<Note*>::const_iterator secondNoteIt = std::find(chordNotes.begin(), chordNotes.end(), secondNote);
83 :
84 25 : assert(firstNoteIt != chordNotes.end() && secondNoteIt != chordNotes.end());
85 :
86 100 : for (std::vector<Note*>::const_iterator chordNoteIt = firstNoteIt; chordNoteIt != std::next(secondNoteIt);
87 50 : chordNoteIt = std::next(chordNoteIt)) {
88 50 : Note* note = *chordNoteIt;
89 50 : notesByChordIt->second.insert(note);
90 : }
91 : }
92 :
93 14 : return notesByChord;
94 0 : }
95 : }
96 :
97 9 : void EditParentheses::toggleParenthesesOnNotes(Transaction& tx, Score* score)
98 : {
99 9 : std::vector<Note*> notes = score->selection().uniqueNotes(muse::nidx, false);
100 :
101 9 : bool add = false;
102 :
103 11 : for (Note* note : notes) {
104 9 : if (note->getProperty(Pid::HAS_PARENTHESES).value<ParenthesesMode>() == ParenthesesMode::NONE) {
105 7 : add = true;
106 7 : break;
107 : }
108 : }
109 :
110 9 : if (add) {
111 7 : addParenthesesToNotes(tx, notes);
112 : } else {
113 2 : removeParenthesesFromNotes(tx, notes);
114 : }
115 9 : }
116 :
117 11 : void EditParentheses::addParenthesesToNotes(Transaction&, const std::vector<Note*>& notes)
118 : {
119 11 : std::map<Chord*, std::set<Note*, NoteComparator> > notesByChord = getNotesByChord(notes);
120 :
121 24 : for (auto& [chord, noteSet] : notesByChord) {
122 13 : std::vector<Note*> noteVec(noteSet.begin(), noteSet.end());
123 :
124 47 : for (Note* note : noteVec) {
125 : // User has overriden generated parentheses
126 34 : note->undoChangeProperty(Pid::HIDE_GENERATED_PARENTHESES, true);
127 34 : note->undoChangeProperty(Pid::HAS_PARENTHESES, ParenthesesMode::BOTH);
128 : }
129 :
130 13 : EditChord::removeChordParentheses(chord, noteVec);
131 13 : EditChord::addChordParentheses(chord, std::move(noteVec));
132 13 : }
133 11 : }
134 :
135 0 : void EditParentheses::addParenthesesToNotes(Transaction& tx, Score* score)
136 : {
137 0 : addParenthesesToNotes(tx, score->selection().uniqueNotes(muse::nidx, false));
138 0 : }
139 :
140 3 : void EditParentheses::removeParenthesesFromNotes(Transaction&, const std::vector<Note*>& notes)
141 : {
142 3 : std::map<Chord*, std::set<Note*, NoteComparator> > notesByChord = getNotesByChord(notes);
143 :
144 6 : for (auto& [chord, noteSet] : notesByChord) {
145 3 : std::vector<Note*> noteVec(noteSet.begin(), noteSet.end());
146 :
147 10 : for (Note* note : noteVec) {
148 7 : note->undoChangeProperty(Pid::HAS_PARENTHESES, ParenthesesMode::NONE);
149 :
150 : // User has overriden generated parentheses
151 7 : note->undoChangeProperty(Pid::HIDE_GENERATED_PARENTHESES, true);
152 : }
153 :
154 3 : EditChord::removeChordParentheses(chord, std::move(noteVec));
155 3 : }
156 3 : }
157 :
158 1 : void EditParentheses::removeParenthesesFromNotes(Transaction& tx, Score* score)
159 : {
160 1 : removeParenthesesFromNotes(tx, score->selection().uniqueNotes(muse::nidx, false));
161 1 : }
162 :
163 9 : void EditParentheses::toggleParentheses(Transaction& tx, Score* score)
164 : {
165 9 : toggleParenthesesOnNotes(tx, score);
166 :
167 38 : for (EngravingItem* el : score->selection().elements()) {
168 29 : if (el->isNote()) {
169 29 : continue;
170 : }
171 0 : toggleParenthesesOnItem(tx, el);
172 : }
173 9 : }
174 :
175 0 : void EditParentheses::toggleParenthesesOnItem(Transaction&, EngravingItem* el)
176 : {
177 0 : if (el->isAccidental()) {
178 0 : Accidental* acc = toAccidental(el);
179 0 : acc->undoChangeProperty(Pid::ACCIDENTAL_BRACKET, int(AccidentalBracket::PARENTHESIS));
180 0 : } else if (el->type() == ElementType::TIMESIG) {
181 0 : TimeSig* ts = toTimeSig(el);
182 0 : ts->setLargeParentheses(true);
183 : } else {
184 0 : ParenthesesMode p = el->leftParen() || el->rightParen() ? ParenthesesMode::NONE : ParenthesesMode::BOTH;
185 0 : el->undoChangeProperty(Pid::HAS_PARENTHESES, p);
186 : }
187 0 : }
|