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 "flip.h"
24 :
25 : #include <functional>
26 : #include <set>
27 :
28 : #include "../dom/articulation.h"
29 : #include "../dom/beam.h"
30 : #include "../dom/chord.h"
31 : #include "../dom/chordbracket.h"
32 : #include "../dom/guitarbend.h"
33 : #include "../dom/hairpin.h"
34 : #include "../dom/hook.h"
35 : #include "../dom/note.h"
36 : #include "../dom/ornament.h"
37 : #include "../dom/score.h"
38 : #include "../dom/select.h"
39 : #include "../dom/slurtie.h"
40 : #include "../dom/spanner.h"
41 : #include "../dom/staff.h"
42 : #include "../dom/stem.h"
43 : #include "../dom/tremolotwochord.h"
44 : #include "../dom/trill.h"
45 : #include "../dom/tuplet.h"
46 :
47 : #include "log.h"
48 :
49 : using namespace mu::engraving;
50 :
51 : //---------------------------------------------------------
52 : // flip
53 : //---------------------------------------------------------
54 :
55 5 : void Flip::flip(Transaction&, Score* score)
56 : {
57 5 : const std::vector<EngravingItem*>& el = score->selection().elements();
58 5 : if (el.empty()) {
59 0 : MScore::setError(MsError::NO_FLIPPABLE_SELECTED);
60 0 : return;
61 : }
62 :
63 5 : std::set<const EngravingItem*> alreadyFlippedElements;
64 6 : auto flipOnce = [&alreadyFlippedElements](const EngravingItem* element, std::function<void()> flipFunction) -> void {
65 6 : if (alreadyFlippedElements.insert(element).second) {
66 5 : flipFunction();
67 : }
68 11 : };
69 :
70 16 : for (EngravingItem* e : el) {
71 11 : if (e->hasVoiceAssignmentProperties()) {
72 0 : flipOnce(e, [e]() {
73 0 : PlacementV curPlacement = e->getProperty(Pid::PLACEMENT).value<PlacementV>();
74 0 : e->undoChangeProperty(Pid::DIRECTION, curPlacement == PlacementV::ABOVE ? DirectionV::DOWN : DirectionV::UP);
75 0 : });
76 11 : } else if (e->isNote() || e->isStem() || e->isHook()) {
77 7 : Chord* chord = nullptr;
78 7 : if (e->isNote()) {
79 4 : chord = toNote(e)->chord();
80 3 : } else if (e->isStem()) {
81 3 : chord = toStem(e)->chord();
82 : } else {
83 0 : chord = toHook(e)->chord();
84 : }
85 :
86 7 : IF_ASSERT_FAILED(chord) {
87 0 : continue;
88 : }
89 :
90 7 : if (chord->beam()) {
91 4 : if (!score->selection().isRange()) {
92 0 : e = chord->beam();
93 : } else {
94 4 : continue;
95 : }
96 3 : } else if (chord->tremoloTwoChord()) {
97 1 : if (!score->selection().isRange()) {
98 1 : e = chord->tremoloTwoChord();
99 : } else {
100 0 : continue;
101 : }
102 : } else {
103 2 : flipOnce(chord, [chord]() {
104 1 : DirectionV dir = chord->up() ? DirectionV::DOWN : DirectionV::UP;
105 1 : chord->undoChangeProperty(Pid::STEM_DIRECTION, dir);
106 1 : });
107 : }
108 : }
109 :
110 7 : if (e->isBeam()) {
111 1 : Beam* beam = toBeam(e);
112 1 : flipOnce(beam, [beam]() {
113 1 : if (beam->cross()) {
114 0 : int newCrossStaffMove = beam->crossStaffMove() + 1;
115 0 : if (beam->acceptCrossStaffMove(newCrossStaffMove)) {
116 0 : beam->undoChangeProperty(Pid::BEAM_CROSS_STAFF_MOVE, newCrossStaffMove);
117 : } else {
118 0 : beam->undoChangeProperty(Pid::BEAM_CROSS_STAFF_MOVE,
119 0 : beam->minCRMove() - beam->defaultCrossStaffIdx());
120 : }
121 : } else {
122 1 : DirectionV dir = beam->up() ? DirectionV::DOWN : DirectionV::UP;
123 1 : beam->undoChangeProperty(Pid::STEM_DIRECTION, dir);
124 : }
125 1 : });
126 6 : } else if (e->isType(ElementType::TREMOLO_TWOCHORD)) {
127 1 : TremoloTwoChord* tremolo = item_cast<TremoloTwoChord*>(e);
128 1 : flipOnce(tremolo, [tremolo]() {
129 1 : DirectionV dir = tremolo->up() ? DirectionV::DOWN : DirectionV::UP;
130 1 : tremolo->undoChangeProperty(Pid::STEM_DIRECTION, dir);
131 1 : });
132 5 : } else if (e->isSlurTieSegment()) {
133 2 : auto slurTieSegment = toSlurTieSegment(e)->slurTie();
134 2 : flipOnce(slurTieSegment, [slurTieSegment]() {
135 2 : DirectionV dir = slurTieSegment->up() ? DirectionV::DOWN : DirectionV::UP;
136 2 : slurTieSegment->undoChangeProperty(Pid::SLUR_DIRECTION, dir);
137 2 : });
138 3 : } else if (e->isArticulationFamily()) {
139 0 : auto artic = toArticulation(e);
140 0 : flipOnce(artic, [artic]() {
141 0 : ArticulationAnchor articAnchor = artic->anchor();
142 0 : switch (articAnchor) {
143 0 : case ArticulationAnchor::TOP:
144 0 : articAnchor = ArticulationAnchor::BOTTOM;
145 0 : break;
146 0 : case ArticulationAnchor::BOTTOM:
147 0 : articAnchor = ArticulationAnchor::TOP;
148 0 : break;
149 0 : case ArticulationAnchor::AUTO:
150 0 : articAnchor = artic->up() ? ArticulationAnchor::BOTTOM : ArticulationAnchor::TOP;
151 0 : break;
152 : }
153 0 : PropertyFlags pf = artic->propertyFlags(Pid::ARTICULATION_ANCHOR);
154 0 : if (pf == PropertyFlags::STYLED) {
155 0 : pf = PropertyFlags::UNSTYLED;
156 : }
157 0 : artic->undoChangeProperty(Pid::ARTICULATION_ANCHOR, int(articAnchor), pf);
158 0 : });
159 3 : } else if (e->isTuplet()) {
160 0 : auto tuplet = toTuplet(e);
161 0 : flipOnce(tuplet, [tuplet]() {
162 0 : DirectionV dir = tuplet->isUp() ? DirectionV::DOWN : DirectionV::UP;
163 0 : tuplet->undoChangeProperty(Pid::DIRECTION, PropertyValue::fromValue<DirectionV>(dir), PropertyFlags::UNSTYLED);
164 0 : });
165 3 : } else if (e->isNoteDot() && e->ownershipParent()->isNote()) {
166 0 : Note* note = toNote(e->ownershipParent());
167 0 : DirectionV d = note->dotIsUp() ? DirectionV::DOWN : DirectionV::UP;
168 0 : note->undoChangeProperty(Pid::DOT_POSITION, PropertyValue::fromValue<DirectionV>(d));
169 3 : } else if (e->isChordBracket()) {
170 0 : ChordBracket* cb = toChordBracket(e);
171 0 : flipOnce(cb, [cb]() {
172 0 : DirectionV d = cb->hookPos() == DirectionV::UP ? DirectionV::DOWN : DirectionV::UP;
173 0 : cb->undoChangeProperty(Pid::BRACKET_HOOK_POS, PropertyValue::fromValue<DirectionV>(d));
174 0 : });
175 3 : } else if (e->isGuitarBendSegment()) {
176 0 : GuitarBend* bend = toGuitarBendSegment(e)->guitarBend();
177 0 : flipOnce(bend, [bend] {
178 0 : DirectionV direction = bend->ldata()->up() ? DirectionV::DOWN : DirectionV::UP;
179 0 : bend->undoChangeProperty(Pid::DIRECTION, PropertyValue::fromValue<DirectionV>(direction));
180 0 : });
181 3 : } else if (e->isTrillSegment()) {
182 0 : TrillSegment* trillSegment = toTrillSegment(e);
183 0 : Trill* trill = trillSegment->trill();
184 0 : Ornament* ornament = trill->ornament();
185 :
186 0 : flipOnce(ornament, [ornament]() {
187 0 : ArticulationAnchor articAnchor = ArticulationAnchor(ornament->getProperty(Pid::ARTICULATION_ANCHOR).toInt());
188 :
189 0 : switch (articAnchor) {
190 0 : case ArticulationAnchor::TOP:
191 0 : articAnchor = ArticulationAnchor::BOTTOM;
192 0 : break;
193 0 : case ArticulationAnchor::BOTTOM:
194 0 : articAnchor = ArticulationAnchor::TOP;
195 0 : break;
196 0 : case ArticulationAnchor::AUTO:
197 0 : articAnchor = ornament->up() ? ArticulationAnchor::BOTTOM : ArticulationAnchor::TOP;
198 0 : break;
199 : }
200 0 : PropertyFlags pf = ornament->propertyFlags(Pid::ARTICULATION_ANCHOR);
201 0 : if (pf == PropertyFlags::STYLED) {
202 0 : pf = PropertyFlags::UNSTYLED;
203 : }
204 0 : ornament->undoChangeProperty(Pid::ARTICULATION_ANCHOR, int(articAnchor), pf);
205 0 : });
206 3 : } else if (e->isStaffText()
207 3 : || e->isSystemText()
208 3 : || e->isTempoText()
209 3 : || e->isTripletFeel()
210 3 : || e->isJump()
211 3 : || e->isMarker()
212 3 : || e->isRehearsalMark()
213 3 : || e->isMeasureNumber()
214 3 : || e->isInstrumentChange()
215 3 : || e->isPlayTechAnnotation()
216 3 : || e->isCapo()
217 3 : || e->isStringTunings()
218 3 : || e->isSticking()
219 3 : || e->isFingering()
220 3 : || e->isHarmony()
221 3 : || e->isFretDiagram()
222 3 : || e->isHarpPedalDiagram()
223 3 : || e->isOttava()
224 3 : || e->isOttavaSegment()
225 3 : || e->isTextLine()
226 3 : || e->isTextLineSegment()
227 3 : || e->isLetRing()
228 3 : || e->isLetRingSegment()
229 3 : || e->isVibrato()
230 3 : || e->isVibratoSegment()
231 3 : || e->isPalmMute()
232 3 : || e->isPalmMuteSegment()
233 3 : || e->isWhammyBar()
234 3 : || e->isWhammyBarSegment()
235 3 : || e->isRasgueado()
236 3 : || e->isRasgueadoSegment()
237 3 : || e->isHarmonicMark()
238 3 : || e->isHarmonicMarkSegment()
239 3 : || e->isGradualTempoChange()
240 3 : || e->isGradualTempoChangeSegment()
241 3 : || e->isPedal()
242 3 : || e->isPedalSegment()
243 3 : || e->isLyrics()
244 3 : || e->isBreath()
245 3 : || e->isFermata()
246 6 : || e->isHammerOnPullOffText()) {
247 0 : e->undoChangeProperty(Pid::AUTOPLACE, true);
248 : // TODO: undoChangeProperty() should probably do this directly
249 : // see https://musescore.org/en/node/281432
250 0 : EngravingItem* ee = toEngravingItem(e->propertyDelegate(Pid::PLACEMENT));
251 0 : if (!ee) {
252 0 : ee = e;
253 : }
254 :
255 0 : flipOnce(ee, [ee]() {
256 : // getProperty() delegates call from spannerSegment to Spanner
257 0 : PlacementV p = ee->getProperty(Pid::PLACEMENT).value<PlacementV>();
258 0 : p = (p == PlacementV::ABOVE) ? PlacementV::BELOW : PlacementV::ABOVE;
259 0 : PropertyFlags pf = ee->propertyFlags(Pid::PLACEMENT);
260 0 : if (pf == PropertyFlags::STYLED) {
261 0 : pf = PropertyFlags::UNSTYLED;
262 : }
263 0 : double oldDefaultY = ee->propertyDefault(Pid::OFFSET).value<PointF>().y();
264 0 : ee->undoChangeProperty(Pid::PLACEMENT, p, pf);
265 : // flip and rebase user offset to new default now that placement has changed
266 0 : double newDefaultY = ee->propertyDefault(Pid::OFFSET).value<PointF>().y();
267 0 : if (ee->isSpanner()) {
268 0 : Spanner* spanner = toSpanner(ee);
269 0 : for (SpannerSegment* ss : spanner->spannerSegments()) {
270 0 : if (!ss->offset().isNull()) {
271 0 : PointF off = ss->getProperty(Pid::OFFSET).value<PointF>();
272 0 : double oldY = off.y() - oldDefaultY;
273 0 : off.ry() = newDefaultY - oldY;
274 0 : ss->undoChangeProperty(Pid::OFFSET, off);
275 : }
276 : }
277 0 : } else if (!ee->offset().isNull()) {
278 0 : PointF off = ee->getProperty(Pid::OFFSET).value<PointF>();
279 0 : double oldY = off.y() - oldDefaultY;
280 0 : off.ry() = newDefaultY - oldY;
281 0 : ee->undoChangeProperty(Pid::OFFSET, off);
282 : }
283 0 : });
284 : }
285 : }
286 5 : }
287 :
288 : //---------------------------------------------------------
289 : // flipHorizontally
290 : //---------------------------------------------------------
291 :
292 0 : void Flip::flipHorizontally(Transaction&, Score* score)
293 : {
294 0 : const std::vector<EngravingItem*>& el = score->selection().elements();
295 0 : if (el.empty()) {
296 0 : MScore::setError(MsError::NO_FLIPPABLE_SELECTED);
297 0 : return;
298 : }
299 :
300 0 : std::set<const EngravingItem*> alreadyFlippedElements;
301 0 : auto flipOnce = [&alreadyFlippedElements](const EngravingItem* element, std::function<void()> flipFunction) -> void {
302 0 : if (alreadyFlippedElements.insert(element).second) {
303 0 : flipFunction();
304 : }
305 0 : };
306 :
307 0 : for (EngravingItem* e : el) {
308 0 : if (e->isHairpinSegment() || e->isHairpin()) {
309 0 : Hairpin* h = e->isHairpin() ? toHairpin(e) : toHairpinSegment(e)->hairpin();
310 0 : flipOnce(h, [h] {
311 0 : if (h->hairpinType() == HairpinType::CRESC_HAIRPIN) {
312 0 : h->undoChangeProperty(Pid::HAIRPIN_TYPE, int(HairpinType::DIM_HAIRPIN));
313 0 : } else if (h->hairpinType() == HairpinType::DIM_HAIRPIN) {
314 0 : h->undoChangeProperty(Pid::HAIRPIN_TYPE, int(HairpinType::CRESC_HAIRPIN));
315 0 : } else if (h->hairpinType() == HairpinType::CRESC_LINE) {
316 0 : h->undoChangeProperty(Pid::HAIRPIN_TYPE, int(HairpinType::DIM_LINE));
317 0 : } else if (h->hairpinType() == HairpinType::DIM_LINE) {
318 0 : h->undoChangeProperty(Pid::HAIRPIN_TYPE, int(HairpinType::CRESC_LINE));
319 : }
320 0 : });
321 0 : } else if (e->isChordBracket()) {
322 0 : ChordBracket* cb = toChordBracket(e);
323 0 : flipOnce(cb, [cb] {
324 0 : cb->undoChangeProperty(Pid::BRACKET_RIGHT_SIDE, !cb->rightSide());
325 0 : });
326 : }
327 : }
328 0 : }
329 :
330 : //---------------------------------------------------------
331 : // mirrorNoteHead
332 : //---------------------------------------------------------
333 :
334 0 : void Flip::mirrorNoteHead(Transaction&, Score* score)
335 : {
336 0 : const std::vector<EngravingItem*>& el = score->selection().elements();
337 0 : for (EngravingItem* e : el) {
338 0 : if (e->isNote()) {
339 0 : Note* note = toNote(e);
340 0 : if (note->staff() && note->staff()->isTabStaff(note->chord()->tick())) {
341 0 : e->undoChangeProperty(Pid::DEAD, !note->deadNote());
342 : } else {
343 0 : DirectionH d = note->userMirror();
344 0 : if (d == DirectionH::AUTO) {
345 0 : d = note->chord()->up() ? DirectionH::RIGHT : DirectionH::LEFT;
346 : } else {
347 0 : d = d == DirectionH::LEFT ? DirectionH::RIGHT : DirectionH::LEFT;
348 : }
349 0 : note->undoChangeProperty(Pid::MIRROR_HEAD, d);
350 : }
351 : }
352 : }
353 0 : }
|