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 "tiejumppointlist.h"
24 :
25 : #include "../editing/edittie.h"
26 :
27 : #include "barline.h"
28 : #include "chord.h"
29 : #include "factory.h"
30 : #include "marker.h"
31 : #include "measure.h"
32 : #include "note.h"
33 : #include "partialtie.h"
34 : #include "score.h"
35 : #include "segment.h"
36 : #include "tie.h"
37 : #include "volta.h"
38 :
39 : using namespace mu::engraving;
40 :
41 : //---------------------------------------------------------
42 : // TieJumpPoint
43 : //---------------------------------------------------------
44 :
45 189 : TieJumpPoint::TieJumpPoint(Note* note, bool active, int idx, bool followingNote)
46 189 : : m_note(note), m_active(active), m_followingNote(followingNote)
47 : {
48 189 : m_id = u"jumpPoint" + String::fromStdString(std::to_string(idx));
49 189 : if (active && endTie()) {
50 132 : endTie()->setJumpPoint(this);
51 : }
52 189 : }
53 :
54 639 : Tie* TieJumpPoint::endTie() const
55 : {
56 639 : return m_note ? m_note->tieBack() : nullptr;
57 : }
58 :
59 50 : void TieJumpPoint::undoSetActive(bool v)
60 : {
61 50 : Score* score = m_note ? m_note->score() : nullptr;
62 50 : if (!score || m_active == v) {
63 17 : return;
64 : }
65 33 : score->undo(new ChangeTieJumpPointActive(m_jumpPointList, m_id, v));
66 : }
67 :
68 0 : TranslatableString TieJumpPoint::menuTitle() const
69 : {
70 0 : const Measure* measure = m_note->findMeasure();
71 0 : const int measureNo = measure ? measure->measureNumber() + 1 : 0;
72 :
73 : //: %1 represents the preceding jump item eg. coda. %2 represents the measure number
74 0 : return TranslatableString("engraving", "Tie to %1 (m. %2)").arg(precedingJumpItemName(), String::number(measureNo));
75 : }
76 :
77 0 : String TieJumpPoint::precedingJumpItemName() const
78 : {
79 0 : const Chord* startChord = m_note->chord();
80 0 : const Segment* seg = startChord->segment();
81 0 : const Measure* measure = seg->measure();
82 :
83 0 : if (seg->score()->firstSegment(SegmentType::ChordRest) == seg) {
84 : //: Used at %1 in the string "Tie to %1 (m. %2)"
85 0 : return muse::mtrc("engraving", "start of score", "partial tie menu");
86 : }
87 :
88 : // Markers
89 0 : for (const EngravingItem* e : measure->el()) {
90 0 : if (!e->isMarker()) {
91 0 : continue;
92 : }
93 :
94 0 : const Marker* marker = toMarker(e);
95 0 : if (marker->isRightMarker()) {
96 0 : continue;
97 : }
98 :
99 0 : if (marker->markerType() == MarkerType::CODA || marker->markerType() == MarkerType::VARCODA) {
100 : //: Used at %1 in the string "Tie to %1 (m. %2)"
101 0 : return muse::mtrc("engraving", "coda", "partial tie menu");
102 : } else {
103 : //: Used at %1 in the string "Tie to %1 (m. %2)"
104 0 : return muse::mtrc("engraving", "segno", "partial tie menu");
105 : }
106 : }
107 :
108 : // Voltas
109 0 : auto spanners = m_note->score()->spannerMap().findOverlapping(measure->tick().ticks(), measure->tick().ticks());
110 0 : for (auto& spanner : spanners) {
111 0 : if (!spanner.value->isVolta() || Fraction::fromTicks(spanner.start) != startChord->tick()) {
112 0 : continue;
113 : }
114 :
115 0 : Volta* volta = toVolta(spanner.value);
116 :
117 : //: Used at %1 in the string "Tie to %1 (m. %2)". %1 in this string represents the volta's text set by the user
118 0 : return muse::mtrc("engraving", "“%1” volta", "partial tie menu").arg(volta->beginText());
119 : }
120 :
121 : // Repeat barlines
122 0 : if (measure->repeatStart()) {
123 : //: Used at %1 in the string "Tie to %1 (m. %2)"
124 0 : return muse::mtrc("engraving", "start repeat", "partial tie menu");
125 : }
126 :
127 0 : for (Segment* prevSeg = seg->prev(SegmentType::BarLineTypes); prevSeg && prevSeg->tick() == seg->tick();
128 0 : prevSeg = prevSeg->prev(SegmentType::BarLineTypes)) {
129 0 : EngravingItem* el = prevSeg->element(startChord->track());
130 0 : if (!el || !el->isBarLine()) {
131 0 : continue;
132 : }
133 :
134 0 : BarLine* bl = toBarLine(el);
135 0 : if (bl->barLineType() & (BarLineType::START_REPEAT | BarLineType::END_START_REPEAT)) {
136 : //: Used at %1 in the string "Tie to %1 (m. %2)"
137 0 : return muse::mtrc("engraving", "start repeat", "partial tie menu");
138 : }
139 : }
140 :
141 0 : if (m_followingNote) {
142 : //: Used at %1 in the string "Tie to %1 (m. %2)"
143 0 : return muse::mtrc("engraving", "next note", "partial tie menu");
144 : }
145 :
146 : //: Used at %1 in the string "Tie to %1 (m. %2)"
147 0 : return muse::mtrc("engraving", "invalid", "partial tie menu");
148 0 : }
149 :
150 : //---------------------------------------------------------
151 : // TieJumpPointList
152 : //---------------------------------------------------------
153 :
154 36903 : TieJumpPointList::~TieJumpPointList()
155 : {
156 36903 : muse::DeleteAll(m_jumpPoints);
157 36903 : m_jumpPoints.clear();
158 36903 : }
159 :
160 189 : void TieJumpPointList::add(TieJumpPoint* item)
161 : {
162 189 : item->setJumpPointList(this);
163 189 : m_jumpPoints.push_back(item);
164 189 : }
165 :
166 17322 : void TieJumpPointList::clear()
167 : {
168 17465 : for (const TieJumpPoint* jumpPoint : m_jumpPoints) {
169 143 : Tie* endTie = jumpPoint->endTie();
170 143 : if (!endTie) {
171 28 : continue;
172 : }
173 115 : endTie->setJumpPoint(nullptr);
174 : }
175 17322 : muse::DeleteAll(m_jumpPoints);
176 17322 : m_jumpPoints.clear();
177 17322 : }
178 :
179 7830 : Tie* TieJumpPointList::startTie() const
180 : {
181 7830 : return m_note ? m_note->tieFor() : nullptr;
182 : }
183 :
184 54 : TieJumpPoint* TieJumpPointList::findJumpPoint(const String& id)
185 : {
186 95 : for (TieJumpPoint* jumpPoint : m_jumpPoints) {
187 95 : if (jumpPoint->id() != id) {
188 41 : continue;
189 : }
190 :
191 54 : return jumpPoint;
192 : }
193 0 : return nullptr;
194 : }
195 :
196 8 : void TieJumpPointList::toggleJumpPoint(const String& id)
197 : {
198 8 : TieJumpPoint* end = findJumpPoint(id);
199 :
200 8 : if (!end) {
201 0 : LOGE() << "No partial tie end point found with id: " << id;
202 0 : return;
203 : }
204 :
205 8 : Score* score = end->note() ? end->note()->score() : nullptr;
206 8 : if (!score) {
207 0 : return;
208 : }
209 :
210 8 : score->startCmd(TranslatableString("engraving", "Toggle partial tie"));
211 8 : const bool checked = end->active();
212 8 : if (checked) {
213 8 : undoRemoveTieFromScore(end);
214 : } else {
215 0 : undoAddTieToScore(end);
216 : }
217 8 : score->endCmd();
218 : }
219 :
220 16 : void TieJumpPointList::undoAddTieToScore(TieJumpPoint* jumpPoint)
221 : {
222 16 : Note* note = jumpPoint->note();
223 16 : Score* score = note ? note->score() : nullptr;
224 16 : Tie* tie = startTie();
225 16 : if (!tie || !score) {
226 0 : return;
227 : }
228 :
229 16 : if (jumpPoint->followingNote()) {
230 : // Remove partial tie and add full tie
231 0 : if (!tie->isPartialTie() || !toPartialTie(tie)->isOutgoing()) {
232 0 : return;
233 : }
234 0 : jumpPoint->undoSetActive(true);
235 0 : Tie::changeTieType(tie, note);
236 0 : return;
237 : }
238 :
239 16 : jumpPoint->undoSetActive(true);
240 :
241 : // Check if there is already a tie. If so, add partial tie info to it
242 16 : Tie* tieBack = note->tieBack();
243 16 : if (tieBack && !tieBack->isPartialTie()) {
244 1 : tieBack->setJumpPoint(jumpPoint);
245 1 : return;
246 : }
247 : // Otherwise create incoming partial tie on note
248 15 : PartialTie* pt = Factory::createPartialTie(note);
249 15 : pt->setEndNote(note);
250 15 : pt->setJumpPoint(jumpPoint);
251 15 : score->undoAddElement(pt);
252 : }
253 :
254 16 : void TieJumpPointList::undoRemoveTieFromScore(TieJumpPoint* jumpPoint)
255 : {
256 16 : Note* note = jumpPoint->note();
257 16 : Score* score = note ? note->score() : nullptr;
258 16 : Tie* tie = startTie();
259 16 : if (!tie || !score) {
260 0 : return;
261 : }
262 :
263 16 : if (jumpPoint->followingNote()) {
264 : // Remove full tie and add partial tie
265 4 : if (tie->isPartialTie()) {
266 0 : return;
267 : }
268 4 : jumpPoint->undoSetActive(false);
269 :
270 4 : Tie::changeTieType(tie);
271 4 : return;
272 : }
273 :
274 12 : jumpPoint->undoSetActive(false);
275 :
276 : // Check if there is a full tie. If so, remove partial tie info from it
277 12 : Tie* tieBack = note->tieBack();
278 12 : if (tieBack && !tieBack->isPartialTie()) {
279 2 : tieBack->setJumpPoint(nullptr);
280 2 : return;
281 : }
282 : // Otherwise remove incoming partial tie on note
283 10 : PartialTie* pt = note->incomingPartialTie();
284 10 : if (!pt) {
285 0 : return;
286 : }
287 10 : score->undoRemoveElement(pt);
288 : }
|