LCOV - code coverage report
Current view: top level - src/engraving/editing - flip.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 40.2 % 234 94
Test Date: 2026-11-05 04:07:02

            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 : }
        

Generated by: LCOV version 2.0-1