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 "editrehearsalmark.h"
24 :
25 : #include "../dom/measure.h"
26 : #include "../dom/rehearsalmark.h"
27 : #include "../dom/score.h"
28 : #include "../dom/segment.h"
29 : #include "../dom/select.h"
30 :
31 : using namespace mu;
32 : using namespace mu::engraving;
33 :
34 : //---------------------------------------------------------
35 : // resequenceRehearsalMarks
36 : /// resequences rehearsal marks within a range selection
37 : /// or, if nothing is selected, the entire score
38 : //---------------------------------------------------------
39 :
40 4 : void EditRehearsalMark::resequenceRehearsalMarks(Transaction&, Score* score)
41 : {
42 4 : bool noSelection = !score->selection().isRange();
43 :
44 4 : if (noSelection) {
45 4 : score->cmdSelectAll();
46 0 : } else if (!score->selection().isRange()) {
47 0 : return;
48 : }
49 :
50 4 : RehearsalMark* last = nullptr;
51 153 : for (Segment* s = score->selection().startSegment(); s && s != score->selection().endSegment(); s = s->next1()) {
52 161 : for (EngravingItem* e : s->annotations()) {
53 12 : if (!e->isRehearsalMark()) {
54 0 : continue;
55 : }
56 12 : RehearsalMark* rm = toRehearsalMark(e);
57 12 : if (!rm->isTopSystemObject()) {
58 0 : continue;
59 : }
60 12 : if (last) {
61 8 : String rmText = nextRehearsalMarkText(last, rm);
62 18 : for (EngravingObject* le : rm->linkList()) {
63 10 : le->undoChangeProperty(Pid::TEXT, rmText);
64 8 : }
65 8 : }
66 12 : last = rm;
67 : }
68 : }
69 :
70 4 : if (noSelection) {
71 4 : score->deselectAll();
72 : }
73 : }
74 :
75 : //---------------------------------------------------------
76 : // createRehearsalMarkText
77 : //---------------------------------------------------------
78 :
79 0 : String EditRehearsalMark::createRehearsalMarkText(const Score* score, RehearsalMark* current)
80 : {
81 0 : Fraction tick = current->segment()->tick();
82 0 : RehearsalMark* before = 0;
83 0 : RehearsalMark* after = 0;
84 0 : for (Segment* s = score->firstSegment(SegmentType::All); s; s = s->next1()) {
85 0 : for (EngravingItem* e : s->annotations()) {
86 0 : if (e && e->isRehearsalMark()) {
87 0 : if (s->tick() < tick) {
88 0 : before = toRehearsalMark(e);
89 0 : } else if (s->tick() > tick) {
90 0 : after = toRehearsalMark(e);
91 0 : break;
92 : }
93 : }
94 : }
95 0 : if (after) {
96 0 : break;
97 : }
98 : }
99 0 : String s = u"A";
100 0 : String s1 = before ? before->xmlText() : u"";
101 0 : String s2 = after ? after->xmlText() : u"";
102 0 : if (s1.isEmpty()) {
103 0 : return s;
104 : }
105 0 : s = nextRehearsalMarkText(before, current); // try to sequence
106 0 : if (s == current->xmlText()) {
107 : // no sequence detected (or current happens to be correct)
108 0 : return s;
109 0 : } else if (s == s2) {
110 : // next in sequence already present
111 0 : if (s1.at(0).isLetter()) {
112 0 : if (s1.size() == 2) {
113 0 : s = String(s1.at(0)) + Char::fromAscii(s1.at(1).toAscii() + 1); // BB, BC, CC
114 : } else {
115 0 : s = s1 + u'1'; // B, B1, C
116 : }
117 : } else {
118 0 : s = s1 + u'A'; // 2, 2A, 3
119 : }
120 : }
121 0 : return s;
122 0 : }
123 :
124 : //---------------------------------------------------------
125 : // nextRehearsalMarkText
126 : // finds next rehearsal in sequence established by previous
127 : // Alphabetic sequences:
128 : // A, B, ..., Y, Z, AA, BB, ..., YY, ZZ
129 : // a, b, ..., y, z, aa, bb, ..., yy, zz
130 : // Numeric sequences:
131 : // 1, 2, 3, ...
132 : // If number of previous rehearsal mark matches measure number, assume use of measure numbers throughout
133 : //---------------------------------------------------------
134 :
135 8 : String EditRehearsalMark::nextRehearsalMarkText(const RehearsalMark* previous, const RehearsalMark* current)
136 : {
137 8 : String previousText = previous->xmlText();
138 8 : String fallback = current ? current->xmlText() : previousText + u"'";
139 :
140 8 : if (previousText.size() == 1 && previousText.at(0).isLetter()) {
141 : // single letter sequence
142 4 : if (previousText == "Z") {
143 1 : return u"AA";
144 3 : } else if (previousText == "z") {
145 0 : return u"aa";
146 : } else {
147 3 : return String(Char::fromAscii(previousText.at(0).toAscii() + 1));
148 : }
149 4 : } else if (previousText.size() == 2 && previousText.at(0).isLetter() && previousText.at(1).isLetter()) {
150 : // double letter sequence
151 0 : if (previousText.at(0) == previousText.at(1)) {
152 : // repeated letter sequence
153 0 : if (previousText.toUpper() != "ZZ") {
154 0 : String c = Char::fromAscii(previousText.at(0).toAscii() + 1);
155 0 : return c + c;
156 0 : } else {
157 0 : return fallback;
158 : }
159 : } else {
160 0 : return fallback;
161 : }
162 : } else {
163 : // try to interpret as number
164 : bool ok;
165 4 : int n = previousText.toInt(&ok);
166 4 : if (!ok) {
167 0 : return fallback;
168 4 : } else if (current && n == previous->segment()->measure()->measureNumber() + 1) {
169 : // use measure number
170 2 : n = current->segment()->measure()->measureNumber() + 1;
171 2 : return String::number(n);
172 : } else {
173 : // use number sequence
174 2 : n = previousText.toInt() + 1;
175 2 : return String::number(n);
176 : }
177 : }
178 8 : }
|