LCOV - code coverage report
Current view: top level - src/engraving/rw/compat - compatutils.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 74.3 % 690 513
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) 2021 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 "compatutils.h"
      24              : 
      25              : #include "dom/articulation.h"
      26              : #include "dom/capo.h"
      27              : #include "dom/chord.h"
      28              : #include "dom/chordline.h"
      29              : #include "dom/dynamic.h"
      30              : #include "dom/excerpt.h"
      31              : #include "dom/expression.h"
      32              : #include "dom/factory.h"
      33              : #include "dom/harmony.h"
      34              : #include "dom/image.h"
      35              : #include "dom/instrchange.h"
      36              : #include "dom/key.h"
      37              : #include "dom/keylist.h"
      38              : #include "dom/laissezvib.h"
      39              : #include "dom/linkedobjects.h"
      40              : #include "dom/masterscore.h"
      41              : #include "dom/measure.h"
      42              : #include "dom/note.h"
      43              : #include "dom/noteline.h"
      44              : #include "dom/ornament.h"
      45              : #include "dom/part.h"
      46              : #include "dom/playtechannotation.h"
      47              : #include "dom/rest.h"
      48              : #include "dom/score.h"
      49              : #include "dom/staff.h"
      50              : #include "dom/staffstate.h"
      51              : #include "dom/stafftext.h"
      52              : #include "dom/stafftextbase.h"
      53              : #include "dom/stem.h"
      54              : #include "dom/tempotext.h"
      55              : #include "dom/textline.h"
      56              : 
      57              : #include "editing/editchord.h"
      58              : #include "editing/editkeysig.h"
      59              : #include "editing/transaction/transaction.h"
      60              : #include "editing/transpose.h"
      61              : 
      62              : #include "style/defaultstyle.h"
      63              : #include "style/styledef.h"
      64              : #include "style/textstyle.h"
      65              : 
      66              : #include "../xmlreader.h"
      67              : 
      68              : #include "types/string.h"
      69              : 
      70              : #include "log.h"
      71              : 
      72              : using namespace mu::engraving;
      73              : using namespace mu::engraving::compat;
      74              : 
      75              : const std::set<SymId> CompatUtils::ORNAMENT_IDS {
      76              :     SymId::ornamentTurn,
      77              :     SymId::ornamentTurnInverted,
      78              :     SymId::ornamentTurnSlash,
      79              :     SymId::ornamentTrill,
      80              :     SymId::brassMuteClosed,
      81              :     SymId::ornamentMordent,
      82              :     SymId::ornamentShortTrill,
      83              :     SymId::ornamentTremblement,
      84              :     SymId::ornamentPrallMordent,
      85              :     SymId::ornamentLinePrall,
      86              :     SymId::ornamentUpPrall,
      87              :     SymId::ornamentUpMordent,
      88              :     SymId::ornamentPrecompMordentUpperPrefix,
      89              :     SymId::ornamentDownMordent,
      90              :     SymId::ornamentPrallUp,
      91              :     SymId::ornamentPrallDown,
      92              :     SymId::ornamentPrecompSlide,
      93              :     SymId::ornamentShake3,
      94              :     SymId::ornamentShakeMuffat1,
      95              :     SymId::ornamentTremblementCouperin,
      96              :     SymId::ornamentPinceCouperin
      97              : };
      98              : 
      99              : const std::map<Sid, Sid> CompatUtils::ALIGN_VALS_TO_CONVERT {
     100              :     { Sid::defaultAlign, Sid::defaultPosition },
     101              :     { Sid::titleAlign, Sid::titlePosition },
     102              :     { Sid::subTitleAlign, Sid::subTitlePosition },
     103              :     { Sid::composerAlign, Sid::composerPosition },
     104              :     { Sid::lyricistAlign, Sid::lyricistPosition },
     105              :     { Sid::lyricsEvenAlign, Sid::lyricsEvenPosition },
     106              :     { Sid::lyricsOddAlign, Sid::lyricsOddPosition },
     107              :     { Sid::fingeringAlign, Sid::fingeringPosition },
     108              :     { Sid::lhGuitarFingeringAlign, Sid::lhGuitarFingeringPosition },
     109              :     { Sid::rhGuitarFingeringAlign, Sid::rhGuitarFingeringPosition },
     110              :     { Sid::hammerOnPullOffTappingAlign, Sid::hammerOnPullOffTappingPosition },
     111              :     { Sid::stringNumberAlign, Sid::stringNumberPosition },
     112              :     { Sid::staffTextAlign, Sid::staffTextPosition },
     113              :     { Sid::fretDiagramFingeringAlign, Sid::fretDiagramFingeringPosition },
     114              :     { Sid::fretDiagramFretNumberAlign, Sid::fretDiagramFretNumberPosition },
     115              :     { Sid::harpPedalDiagramAlign, Sid::harpPedalDiagramPosition },
     116              :     { Sid::harpPedalTextDiagramAlign, Sid::harpPedalTextDiagramPosition },
     117              :     { Sid::longInstrumentAlign, Sid::longInstrumentPosition },
     118              :     { Sid::shortInstrumentAlign, Sid::shortInstrumentPosition },
     119              :     { Sid::partInstrumentAlign, Sid::partInstrumentPosition },
     120              :     { Sid::dynamicsAlign, Sid::dynamicsPosition },
     121              :     { Sid::expressionAlign, Sid::expressionPosition },
     122              :     { Sid::tempoAlign, Sid::tempoPosition },
     123              :     { Sid::tempoChangeAlign, Sid::tempoChangePosition },
     124              :     { Sid::metronomeAlign, Sid::metronomePosition },
     125              :     { Sid::measureNumberAlign, Sid::measureNumberPosition },
     126              :     { Sid::mmRestRangeAlign, Sid::mmRestRangePosition },
     127              :     { Sid::translatorAlign, Sid::translatorPosition },
     128              :     { Sid::tupletAlign, Sid::tupletPosition },
     129              :     { Sid::systemTextAlign, Sid::systemTextPosition },
     130              :     { Sid::chordSymbolAAlign, Sid::chordSymPosition },
     131              :     { Sid::repeatLeftAlign, Sid::repeatLeftPosition },
     132              :     { Sid::repeatRightAlign, Sid::repeatRightPosition },
     133              :     { Sid::frameAlign, Sid::framePosition },
     134              :     { Sid::textLineTextAlign, Sid::textLinePosition },
     135              :     { Sid::noteLineAlign, Sid::noteLinePosition },
     136              :     { Sid::glissandoAlign, Sid::glissandoPosition },
     137              :     { Sid::ottavaTextAlignAbove, Sid::ottavaPosition },
     138              :     { Sid::voltaAlign, Sid::voltaPosition },
     139              :     { Sid::pedalTextAlign, Sid::pedalPosition },
     140              :     { Sid::letRingTextAlign, Sid::letRingPosition },
     141              :     { Sid::palmMuteTextAlign, Sid::palmMutePosition },
     142              :     { Sid::hairpinTextAlign, Sid::hairpinPosition },
     143              :     { Sid::bendAlign, Sid::bendPosition },
     144              :     { Sid::headerAlign, Sid::headerPosition },
     145              :     { Sid::footerAlign, Sid::footerPosition },
     146              :     { Sid::copyrightAlign, Sid::copyrightPosition },
     147              :     { Sid::pageNumberAlign, Sid::pageNumberPosition },
     148              :     { Sid::instrumentChangeAlign, Sid::instrumentChangePosition },
     149              :     { Sid::stickingAlign, Sid::stickingPosition },
     150              :     { Sid::user1Align, Sid::user1Position },
     151              :     { Sid::user2Align, Sid::user2Position },
     152              :     { Sid::user3Align, Sid::user3Position },
     153              :     { Sid::user4Align, Sid::user4Position },
     154              :     { Sid::user5Align, Sid::user5Position },
     155              :     { Sid::user6Align, Sid::user6Position },
     156              :     { Sid::user7Align, Sid::user7Position },
     157              :     { Sid::user8Align, Sid::user8Position },
     158              :     { Sid::user9Align, Sid::user9Position },
     159              :     { Sid::user10Align, Sid::user10Position },
     160              :     { Sid::user11Align, Sid::user11Position },
     161              :     { Sid::user12Align, Sid::user12Position },
     162              : };
     163              : 
     164         1008 : void CompatUtils::doCompatibilityConversions(MasterScore* masterScore)
     165              : {
     166         1016 :     TRACEFUNC;
     167              : 
     168         1008 :     if (!masterScore) {
     169            0 :         return;
     170              :     }
     171              : 
     172              :     // TODO: collect all compatibility conversions here
     173         1008 :     if (masterScore->mscVersion() < 400) {
     174          473 :         replaceStaffTextWithPlayTechniqueAnnotation(masterScore);
     175              :     }
     176         1008 :     if (masterScore->mscVersion() < 410) {
     177          546 :         reconstructTypeOfCustomDynamics(masterScore);
     178          546 :         replaceOldWithNewExpressions(masterScore);
     179          546 :         replaceOldWithNewOrnaments(masterScore);
     180          546 :         resetRestVerticalOffset(masterScore);
     181          546 :         splitArticulations(masterScore);
     182          546 :         resetArticulationOffsets(masterScore);
     183          546 :         resetStemLengthsForTwoNoteTrems(masterScore);
     184          546 :         replaceStaffTextWithCapo(masterScore);
     185              :     }
     186         1008 :     if (masterScore->mscVersion() < 420) {
     187          600 :         addMissingInitKeyForTransposingInstrument(masterScore);
     188          600 :         resetFramesExclusionFromParts(masterScore);
     189              :     }
     190         1008 :     if (masterScore->mscVersion() < 440) {
     191          635 :         mapHeaderFooterStyles(masterScore);
     192              :     }
     193              : 
     194         1008 :     if (masterScore->mscVersion() < 450) {
     195          673 :         convertTextLineToNoteAnchoredLine(masterScore);
     196          673 :         convertLaissezVibArticToTie(masterScore);
     197              :     }
     198              : 
     199         1008 :     if (masterScore->mscVersion() < 500) {
     200          838 :         removeMMRestElements(masterScore);
     201              :     }
     202         1008 : }
     203              : 
     204          473 : void CompatUtils::replaceStaffTextWithPlayTechniqueAnnotation(MasterScore* score)
     205              : {
     206          479 :     TRACEFUNC;
     207              : 
     208              :     //! NOTE: Before MU4, they were available in the Staff Text Properties dialog (Change Channel page)
     209              :     static const std::unordered_map<muse::String, PlayingTechniqueType> textToPlayTechniqueType {
     210            0 :         { u"natural", PlayingTechniqueType::Natural },
     211            0 :         { u"normal", PlayingTechniqueType::Natural },
     212              : 
     213            0 :         { u"open", PlayingTechniqueType::Open },
     214            0 :         { u"mute", PlayingTechniqueType::Mute },
     215              : 
     216            0 :         { u"distortion", PlayingTechniqueType::Distortion },
     217            0 :         { u"overdriven", PlayingTechniqueType::Overdrive },
     218            0 :         { u"harmonics", PlayingTechniqueType::Harmonics },
     219            0 :         { u"jazz", PlayingTechniqueType::JazzTone },
     220              : 
     221            0 :         { u"arco", PlayingTechniqueType::Natural },
     222            0 :         { u"pizzicato", PlayingTechniqueType::Pizzicato },
     223            0 :         { u"tremolo", PlayingTechniqueType::Tremolo },
     224          551 :     };
     225              : 
     226          473 :     std::map<StaffTextBase*, PlayingTechniqueType> oldPlayTechniquesAndNewTypes;
     227              : 
     228         4574 :     for (Measure* measure = score->firstMeasure(); measure; measure = measure->nextMeasure()) {
     229        15399 :         for (Segment* segment = measure->first(); segment; segment = segment->next()) {
     230        11298 :             std::vector<EngravingItem*> annotations = segment->annotations();
     231              : 
     232        12515 :             for (EngravingItem* annotation : annotations) {
     233         1217 :                 if (!annotation || !annotation->isStaffTextBase()) {
     234         1217 :                     continue;
     235              :                 }
     236              : 
     237          194 :                 StaffTextBase* text = toStaffTextBase(annotation);
     238              :                 PlayingTechniqueType type
     239          194 :                     = muse::value(textToPlayTechniqueType, text->plainText().toLower(), PlayingTechniqueType::Undefined);
     240              : 
     241          194 :                 if (type == PlayingTechniqueType::Undefined) {
     242          194 :                     muse::String channelName = text->channelName(0).toLower();
     243              : 
     244          194 :                     if (!channelName.isEmpty()) {
     245            0 :                         type = muse::value(textToPlayTechniqueType, channelName, PlayingTechniqueType::Undefined);
     246              :                     }
     247          194 :                 }
     248              : 
     249          194 :                 if (type == PlayingTechniqueType::Undefined) {
     250          194 :                     continue;
     251              :                 }
     252              : 
     253            0 :                 oldPlayTechniquesAndNewTypes.emplace(text, type);
     254            0 :                 LinkedObjects* links = text->links();
     255            0 :                 if (!links || links->empty()) {
     256            0 :                     continue;
     257              :                 }
     258            0 :                 for (EngravingObject* linked : *links) {
     259            0 :                     if (linked != text) {
     260            0 :                         oldPlayTechniquesAndNewTypes.emplace(toStaffTextBase(linked), type);
     261              :                     }
     262              :                 }
     263              :             }
     264        11298 :         }
     265              :     }
     266              : 
     267          473 :     for (auto& pair : oldPlayTechniquesAndNewTypes) {
     268            0 :         StaffTextBase* oldPlayTech = pair.first;
     269            0 :         PlayingTechniqueType type = pair.second;
     270            0 :         Segment* parentSegment = oldPlayTech->segment();
     271              : 
     272            0 :         PlayTechAnnotation* newPlayTech = Factory::createPlayTechAnnotation(parentSegment, type, oldPlayTech->textStyleType());
     273            0 :         newPlayTech->setXmlText(oldPlayTech->xmlText());
     274            0 :         newPlayTech->setTrack(oldPlayTech->track());
     275              : 
     276            0 :         LinkedObjects* links = oldPlayTech->links();
     277            0 :         newPlayTech->setLinks(links);
     278            0 :         if (links) {
     279            0 :             links->push_back(newPlayTech);
     280              :         }
     281            0 :         parentSegment->add(newPlayTech);
     282            0 :         parentSegment->removeAnnotation(oldPlayTech);
     283              : 
     284            0 :         delete oldPlayTech;
     285              :     }
     286          479 : }
     287              : 
     288          473 : void CompatUtils::assignInitialPartToExcerpts(const std::vector<Excerpt*>& excerpts)
     289              : {
     290          479 :     TRACEFUNC;
     291              : 
     292          473 :     std::set<ID> assignedPartIdSet;
     293              : 
     294           22 :     auto assignInitialPartId = [&assignedPartIdSet](Excerpt* excerpt, const ID& initialPartId) {
     295           22 :         excerpt->setInitialPartId(initialPartId);
     296           22 :         assignedPartIdSet.insert(initialPartId);
     297          495 :     };
     298              : 
     299          495 :     for (Excerpt* excerpt : excerpts) {
     300           30 :         for (const Part* part : excerpt->excerptScore()->parts()) {
     301           22 :             if (excerpt->name() == part->partName()) {
     302           14 :                 assignInitialPartId(excerpt, part->id());
     303           14 :                 break;
     304              :             }
     305              :         }
     306              :     }
     307              : 
     308          495 :     for (Excerpt* excerpt : excerpts) {
     309           22 :         if (excerpt->initialPartId().isValid()) {
     310           14 :             continue;
     311              :         }
     312              : 
     313            8 :         for (Part* part : excerpt->excerptScore()->parts()) {
     314            8 :             if (muse::contains(assignedPartIdSet, part->id())) {
     315            0 :                 continue;
     316              :             }
     317              : 
     318            8 :             if (excerpt->name().contains(part->partName())) {
     319            8 :                 assignInitialPartId(excerpt, part->id());
     320            8 :                 break;
     321              :             }
     322              :         }
     323              :     }
     324          473 : }
     325              : 
     326          546 : void CompatUtils::replaceOldWithNewOrnaments(MasterScore* score)
     327              : {
     328          546 :     std::set<Articulation*> oldOrnaments;     // ornaments used to be articulations
     329              : 
     330         4982 :     for (Measure* meas = score->firstMeasure(); meas; meas = meas->nextMeasure()) {
     331        16715 :         for (Segment& seg : meas->segments()) {
     332        12279 :             if (!seg.isChordRestType()) {
     333         2253 :                 continue;
     334              :             }
     335        66090 :             for (EngravingItem* item : seg.elist()) {
     336        56064 :                 if (!item || !item->isChord()) {
     337        47829 :                     continue;
     338              :                 }
     339         8505 :                 for (Articulation* articulation : toChord(item)->articulations()) {
     340          270 :                     if (articulation->isOrnament()) {
     341           14 :                         continue;
     342              :                     }
     343          256 :                     if (ORNAMENT_IDS.find(articulation->symId()) != ORNAMENT_IDS.end()) {
     344           97 :                         oldOrnaments.insert(articulation);
     345           97 :                         LinkedObjects* links = articulation->links();
     346           97 :                         if (!links || links->empty()) {
     347           93 :                             continue;
     348              :                         }
     349           12 :                         for (EngravingObject* linked : *links) {
     350            8 :                             if (linked != articulation) {
     351            4 :                                 oldOrnaments.insert(toArticulation(linked));
     352              :                             }
     353              :                         }
     354              :                     }
     355              :                 }
     356              :             }
     357              :         }
     358              :     }
     359              : 
     360          647 :     for (Articulation* oldOrnament : oldOrnaments) {
     361          101 :         Chord* parentChord = toChord(oldOrnament->chordRest());
     362              : 
     363          101 :         Ornament* newOrnament = Factory::createOrnament(parentChord);
     364          101 :         newOrnament->setTrack(oldOrnament->track());
     365          101 :         newOrnament->setSymId(oldOrnament->symId());
     366          101 :         newOrnament->setPos(oldOrnament->pos());
     367          101 :         newOrnament->setOrnamentStyle(oldOrnament->ornamentStyle());
     368          101 :         newOrnament->setDirection(oldOrnament->direction());
     369          101 :         newOrnament->setAutoplace(oldOrnament->autoplace());
     370          101 :         newOrnament->setPlayArticulation(oldOrnament->playArticulation());
     371              : 
     372          101 :         LinkedObjects* links = oldOrnament->links();
     373          101 :         newOrnament->setLinks(links);
     374          101 :         if (links) {
     375            8 :             links->push_back(newOrnament);
     376              :         }
     377          101 :         parentChord->add(newOrnament);
     378          101 :         parentChord->remove(oldOrnament);
     379          101 :         delete oldOrnament;
     380              :     }
     381          546 : }
     382              : 
     383          546 : void CompatUtils::replaceOldWithNewExpressions(MasterScore* score)
     384              : {
     385          546 :     std::set<StaffText*> oldExpressions; // Expressions used to be staff text
     386              : 
     387         4982 :     for (Measure* measure = score->firstMeasure(); measure; measure = measure->nextMeasure()) {
     388        16715 :         for (Segment& seg : measure->segments()) {
     389        13517 :             for (EngravingItem* item : seg.annotations()) {
     390         1238 :                 if (item && item->isStaffText() && toStaffText(item)->textStyleType() == TextStyleType::EXPRESSION) {
     391           24 :                     oldExpressions.insert(toStaffText(item));
     392           24 :                     LinkedObjects* links = item->links();
     393           24 :                     if (!links || links->empty()) {
     394           24 :                         continue;
     395              :                     }
     396            0 :                     for (EngravingObject* linkedItem : *links) {
     397            0 :                         if (linkedItem != item) {
     398            0 :                             oldExpressions.insert(toStaffText(linkedItem));
     399              :                         }
     400              :                     }
     401              :                 }
     402              :             }
     403              :         }
     404              :     }
     405              : 
     406          570 :     for (StaffText* oldExpression : oldExpressions) {
     407           24 :         Segment* parentSegment = oldExpression->segment();
     408              : 
     409           24 :         Expression* newExpression = Factory::createExpression(parentSegment);
     410           24 :         newExpression->setTrack(oldExpression->track());
     411           24 :         newExpression->setXmlText(oldExpression->xmlText());
     412           24 :         newExpression->mapPropertiesFromOldExpressions(oldExpression);
     413              : 
     414           24 :         LinkedObjects* links = oldExpression->links();
     415           24 :         newExpression->setLinks(links);
     416           24 :         if (links) {
     417            0 :             links->push_back(newExpression);
     418              :         }
     419              : 
     420           24 :         parentSegment->add(newExpression);
     421           24 :         parentSegment->removeAnnotation(oldExpression);
     422           24 :         delete oldExpression;
     423              :     }
     424          546 : }
     425              : 
     426          546 : void CompatUtils::reconstructTypeOfCustomDynamics(MasterScore* score)
     427              : {
     428              :     // Before version 4.1, Dynamics containing custom text were saved as type "other"
     429              :     // We check the string to see if we can reconstruct their true type.
     430          546 :     std::set<Dynamic*> otherTypeDynamic;
     431              : 
     432         4982 :     for (Measure* measure = score->firstMeasure(); measure; measure = measure->nextMeasure()) {
     433        16715 :         for (Segment& seg : measure->segments()) {
     434        13517 :             for (EngravingItem* item : seg.annotations()) {
     435         1238 :                 if (item && item->isDynamic() && toDynamic(item)->dynamicType() == DynamicType::OTHER) {
     436            2 :                     otherTypeDynamic.insert(toDynamic(item));
     437            2 :                     LinkedObjects* links = item->links();
     438            2 :                     if (!links || links->empty()) {
     439            2 :                         continue;
     440              :                     }
     441            0 :                     for (EngravingObject* linkedItem : *links) {
     442            0 :                         if (linkedItem != item) {
     443            0 :                             otherTypeDynamic.insert(toDynamic(linkedItem));
     444              :                         }
     445              :                     }
     446              :                 }
     447              :             }
     448              :         }
     449              :     }
     450              : 
     451          548 :     for (Dynamic* dynamic : otherTypeDynamic) {
     452            2 :         DynamicType dynType = reconstructDynamicTypeFromString(dynamic);
     453            2 :         dynamic->setDynamicType(dynType);
     454              :     }
     455          546 : }
     456              : 
     457          546 : void CompatUtils::splitArticulations(MasterScore* masterScore)
     458              : {
     459          546 :     std::set<Articulation*> toRemove;
     460         4982 :     for (Measure* meas = masterScore->firstMeasure(); meas; meas = meas->nextMeasure()) {
     461        16715 :         for (Segment& seg : meas->segments()) {
     462        12279 :             if (!seg.isChordRestType()) {
     463         2253 :                 continue;
     464              :             }
     465        66090 :             for (EngravingItem* item : seg.elist()) {
     466        56064 :                 if (!item || !item->isChord()) {
     467        47829 :                     continue;
     468              :                 }
     469         8235 :                 Chord* chord = toChord(item);
     470         8505 :                 for (Articulation* a : chord->articulations()) {
     471          270 :                     if (a->isLinked()) {
     472          233 :                         continue; // only worry about main artics, links will be done later
     473              :                     }
     474          540 :                     std::set<SymId> ids = mu::engraving::splitArticulations({ a->symId() });
     475          270 :                     if (ids.size() <= 1) {
     476          233 :                         continue;
     477              :                     }
     478           37 :                     toRemove.insert(a);
     479          270 :                 }
     480              :             }
     481              :         }
     482              :     }
     483              :     // separate into individual articulations
     484          583 :     for (Articulation* combinedArtic : toRemove) {
     485           74 :         auto components = mu::engraving::splitArticulations({ combinedArtic->symId() });
     486           37 :         Chord* parentChord = toChord(combinedArtic->chordRest());
     487          111 :         for (SymId id : components) {
     488           74 :             Articulation* newArtic = Factory::createArticulation(parentChord);
     489           74 :             newArtic->setSymId(id);
     490           74 :             if (parentChord->hasArticulation(newArtic)) {
     491            0 :                 delete newArtic;
     492            0 :                 continue;
     493              :             }
     494           74 :             newArtic->setTrack(combinedArtic->track());
     495           74 :             newArtic->setPos(combinedArtic->pos());
     496           74 :             newArtic->setDirection(combinedArtic->direction());
     497           74 :             newArtic->setAnchor(combinedArtic->anchor());
     498           74 :             newArtic->setColor(combinedArtic->color());
     499           74 :             newArtic->setPlayArticulation(combinedArtic->playArticulation());
     500           74 :             newArtic->setVisible(combinedArtic->visible());
     501           74 :             newArtic->setOrnamentStyle(combinedArtic->ornamentStyle());
     502           74 :             LinkedObjects* links = new LinkedObjects();
     503           74 :             links->push_back(newArtic);
     504           74 :             newArtic->setLinks(links);
     505           74 :             parentChord->add(newArtic);
     506              : 
     507              :             // newArtic is the main articulation
     508           74 :             LinkedObjects* oldLinks = combinedArtic->links();
     509           74 :             if (!oldLinks || oldLinks->empty()) {
     510           74 :                 continue;
     511              :             }
     512            0 :             for (EngravingObject* linkedItem : *oldLinks) {
     513            0 :                 IF_ASSERT_FAILED(linkedItem && linkedItem->isArticulation()) {
     514            0 :                     continue;
     515              :                 }
     516            0 :                 if (linkedItem == combinedArtic) {
     517            0 :                     continue;
     518              :                 }
     519            0 :                 Articulation* oldArtic = toArticulation(linkedItem);
     520            0 :                 Chord* oldParent = toChord(oldArtic->chordRest());
     521            0 :                 oldParent->add(newArtic->linkedClone());
     522              :             }
     523              :         }
     524           37 :     }
     525              :     // finally, remove the combined articulations
     526          583 :     for (Articulation* combinedArtic : toRemove) {
     527           37 :         LinkedObjects* links = combinedArtic->links();
     528           37 :         if (!links || links->empty()) {
     529           37 :             Chord* parentChord = toChord(combinedArtic->chordRest());
     530           37 :             parentChord->remove(combinedArtic);
     531           37 :             delete combinedArtic;
     532           37 :             continue;
     533           37 :         }
     534            0 :         std::set<Articulation*> removeLinks;
     535            0 :         for (auto linked : *links) {
     536            0 :             IF_ASSERT_FAILED(linked && linked->isArticulation()) {
     537            0 :                 continue;
     538              :             }
     539            0 :             removeLinks.insert(toArticulation(linked));
     540              :         }
     541            0 :         for (Articulation* linkedArtic : removeLinks) {
     542            0 :             if (linkedArtic != combinedArtic) {
     543            0 :                 Chord* linkedParent = toChord(linkedArtic->chordRest());
     544            0 :                 linkedParent->remove(linkedArtic);
     545            0 :                 delete linkedArtic;
     546              :             }
     547              :         }
     548            0 :         Chord* parentChord = toChord(combinedArtic->chordRest());
     549            0 :         parentChord->remove(combinedArtic);
     550            0 :         delete combinedArtic;
     551            0 :     }
     552          546 : }
     553              : 
     554            2 : DynamicType CompatUtils::reconstructDynamicTypeFromString(Dynamic* dynamic)
     555              : {
     556            2 :     static std::vector<Dyn> sortedDynList; // copy of dynList sorted by string length
     557              : 
     558            2 :     if (sortedDynList.empty()) {
     559            1 :         sortedDynList = Dynamic::dynamicList();
     560            1 :         std::sort(sortedDynList.begin(), sortedDynList.end(), [](const Dyn& a, const Dyn& b) {
     561          186 :             String stringA = String::fromUtf8(a.text);
     562          186 :             String stringB = String::fromUtf8(b.text);
     563          372 :             return stringA.size() > stringB.size();
     564          186 :         });
     565              :     }
     566              : 
     567           62 :     for (Dyn dyn : sortedDynList) {
     568           62 :         String dynText = String::fromUtf8(dyn.text);
     569           62 :         if (dynText.size() == 0) {
     570            1 :             return DynamicType::OTHER;
     571              :         }
     572           61 :         if (dynamic->xmlText().contains(dynText)) {
     573            1 :             return dyn.type;
     574              :         }
     575           62 :     }
     576            0 :     return DynamicType::OTHER;
     577              : }
     578              : 
     579          188 : ArticulationAnchor CompatUtils::translateToNewArticulationAnchor(int anchor)
     580              : {
     581          188 :     switch (anchor) {
     582           80 :     case 0: // ArticulationAnchor::TOP_STAFF
     583              :     case 3: // ArticulationAnchor::TOP_CHORD
     584           80 :         return ArticulationAnchor::TOP;
     585              :         break;
     586           56 :     case 1: // ArticulationAnchor::BOTTOM_STAFF
     587              :     case 4: // ArticulationAnchor::BOTTOM_CHORD
     588           56 :         return ArticulationAnchor::BOTTOM;
     589              :         break;
     590           52 :     case 2: // ArticulationAnchor::CHORD
     591              :     default:
     592           52 :         return ArticulationAnchor::AUTO;
     593              :         break;
     594              :     }
     595              : }
     596              : 
     597        11660 : double CompatUtils::convertChordExtModUnits(double val)
     598              : {
     599              :     // Pre 4.6 this value was in 1/5 of a spatium
     600              :     // After 4.6 this is in % of root cap height
     601              :     // The best we can do for conversion of old files is to assume a default spatium of 1.75mm and a default font size of 10pt
     602              :     // The height value is calculated from Edwin at 10pt using FontMetrics::capHeight
     603        11660 :     const double DEFAULT_SPATIUM = StyleDef::styleValues[static_cast<size_t>(Sid::spatium)].defaultValue.toDouble();
     604        11660 :     const double DEFAULT_FONT_CAP_HEIGHT = 121; // Edwin at 10pt
     605              : 
     606        11660 :     return ((val / 5) * DEFAULT_SPATIUM) / DEFAULT_FONT_CAP_HEIGHT;
     607              : }
     608              : 
     609          546 : void CompatUtils::resetRestVerticalOffset(MasterScore* masterScore)
     610              : {
     611         1114 :     for (Score* score : masterScore->scoreList()) {
     612         5168 :         for (Measure* measure = score->firstMeasure(); measure; measure = measure->nextMeasure()) {
     613        17288 :             for (Segment& segment : measure->segments()) {
     614        12688 :                 if (!segment.isChordRestType()) {
     615         2296 :                     continue;
     616              :                 }
     617        68132 :                 for (EngravingItem* item : segment.elist()) {
     618        57740 :                     if (!item || !item->isRest()) {
     619        52825 :                         continue;
     620              :                     }
     621         4915 :                     Rest* rest = toRest(item);
     622         4915 :                     if (rest->offset().y() != 0) {
     623           10 :                         PointF newOffset = PointF(rest->offset().x(), 0.0);
     624           10 :                         rest->setProperty(Pid::OFFSET, newOffset);
     625              :                     }
     626              :                 }
     627              :             }
     628              :         }
     629          546 :     }
     630          546 : }
     631              : 
     632          546 : void CompatUtils::resetArticulationOffsets(MasterScore* masterScore)
     633              : {
     634         1114 :     for (Score* score : masterScore->scoreList()) {
     635         5168 :         for (Measure* measure = score->firstMeasure(); measure; measure = measure->nextMeasure()) {
     636        17288 :             for (Segment& segment : measure->segments()) {
     637        12688 :                 if (!segment.isChordRestType()) {
     638         2296 :                     continue;
     639              :                 }
     640        68132 :                 for (EngravingItem* item : segment.elist()) {
     641        57740 :                     if (!item || !item->isChord()) {
     642        49192 :                         continue;
     643              :                     }
     644         8548 :                     Chord* chord = toChord(item);
     645         8877 :                     for (Articulation* artic : chord->articulations()) {
     646          329 :                         if (!artic) {
     647            0 :                             continue;
     648              :                         }
     649          329 :                         artic->setProperty(Pid::OFFSET, PointF());
     650              :                     }
     651              :                 }
     652              :             }
     653              :         }
     654          546 :     }
     655          546 : }
     656              : 
     657          546 : void CompatUtils::resetStemLengthsForTwoNoteTrems(MasterScore* masterScore)
     658              : {
     659         1114 :     for (Score* score : masterScore->scoreList()) {
     660         5168 :         for (Measure* measure = score->firstMeasure(); measure; measure = measure->nextMeasure()) {
     661        17288 :             for (Segment& segment : measure->segments()) {
     662        12688 :                 if (!segment.isChordRestType()) {
     663         2296 :                     continue;
     664              :                 }
     665        68132 :                 for (EngravingItem* item : segment.elist()) {
     666        57740 :                     if (!item || !item->isChord()) {
     667        49192 :                         continue;
     668              :                     }
     669         8548 :                     Chord* chord = toChord(item);
     670         8548 :                     TremoloTwoChord* trem = chord->tremoloTwoChord();
     671         8548 :                     Stem* stem = chord->stem();
     672         8548 :                     if (stem && trem) {
     673            0 :                         if (!stem->userLength().isZero()) {
     674            0 :                             stem->setUserLength(0_sp);
     675              :                         }
     676              :                     }
     677              :                 }
     678              :             }
     679              :         }
     680          546 :     }
     681          546 : }
     682              : 
     683          546 : void CompatUtils::replaceStaffTextWithCapo(MasterScore* score)
     684              : {
     685          552 :     TRACEFUNC;
     686              : 
     687          546 :     std::set<StaffTextBase*> oldCapoSet;
     688              : 
     689         4982 :     for (Measure* measure = score->firstMeasure(); measure; measure = measure->nextMeasure()) {
     690        16715 :         for (Segment* segment = measure->first(); segment; segment = segment->next()) {
     691        13517 :             for (EngravingItem* annotation : segment->annotations()) {
     692         1238 :                 if (!annotation || !annotation->isStaffTextBase()) {
     693         1238 :                     continue;
     694              :                 }
     695              : 
     696          178 :                 StaffTextBase* text = toStaffTextBase(annotation);
     697              : 
     698          178 :                 if (text->capo() > 0) {
     699            0 :                     oldCapoSet.insert(text);
     700              :                 } else {
     701          178 :                     continue;
     702              :                 }
     703              : 
     704            0 :                 LinkedObjects* links = text->links();
     705            0 :                 if (!links || links->empty()) {
     706            0 :                     continue;
     707              :                 }
     708              : 
     709            0 :                 for (EngravingObject* linked : *links) {
     710            0 :                     if (linked != text && linked && linked->isStaffTextBase()) {
     711            0 :                         oldCapoSet.insert(toStaffTextBase(linked));
     712              :                     }
     713              :                 }
     714              :             }
     715              :         }
     716              :     }
     717              : 
     718          546 :     for (StaffTextBase* oldCapo : oldCapoSet) {
     719            0 :         Segment* parentSegment = oldCapo->segment();
     720            0 :         Capo* newCapo = Factory::createCapo(parentSegment);
     721              : 
     722            0 :         int capoFretPosition = oldCapo->capo() - 1;
     723              : 
     724            0 :         CapoParams params;
     725            0 :         params.active = capoFretPosition > 0;
     726            0 :         params.fretPosition = capoFretPosition;
     727              : 
     728            0 :         newCapo->setTrack(oldCapo->track());
     729            0 :         newCapo->setParams(params);
     730            0 :         newCapo->setProperty(Pid::PLACEMENT, oldCapo->placement());
     731              : 
     732            0 :         LinkedObjects* links = oldCapo->links();
     733            0 :         newCapo->setLinks(links);
     734            0 :         if (links) {
     735            0 :             links->push_back(newCapo);
     736              :         }
     737              : 
     738            0 :         parentSegment->add(newCapo);
     739            0 :         parentSegment->removeAnnotation(oldCapo);
     740              : 
     741            0 :         delete oldCapo;
     742            0 :     }
     743          546 : }
     744              : 
     745          600 : void CompatUtils::addMissingInitKeyForTransposingInstrument(MasterScore* score)
     746              : {
     747          606 :     TRACEFUNC;
     748              : 
     749         1475 :     for (Part* part : score->parts()) {
     750          875 :         Instrument* instrument = part->instrument();
     751          875 :         Interval v = instrument->transpose();
     752          875 :         if (v.chromatic % 12) {
     753           42 :             for (Staff* staff : part->staves()) {
     754           21 :                 KeyList* keys = staff->keyList();
     755           21 :                 if (keys->find(0) == keys->end()) {
     756            8 :                     KeySigEvent kse;
     757            8 :                     Key key = Key::C;
     758            8 :                     Key cKey = key;
     759            8 :                     if (!score->style().styleB(Sid::concertPitch)) {
     760            3 :                         cKey = Transpose::transposeKey(key, v);
     761              :                     }
     762            8 :                     kse.setConcertKey(cKey);
     763            8 :                     kse.setKey(key);
     764           16 :                     EditKeySig::undoChangeKeySig(score->transactionManager()->currentOrDummyTransaction(), score, staff,
     765           16 :                                                  Fraction(0, 1), kse);
     766            8 :                 }
     767              :             }
     768              :         }
     769              :     }
     770          600 : }
     771              : 
     772          600 : void CompatUtils::resetFramesExclusionFromParts(MasterScore* masterScore)
     773              : {
     774         1222 :     for (Score* score : masterScore->scoreList()) {
     775         5956 :         for (MeasureBase* measureBase = score->first(); measureBase; measureBase = measureBase->next()) {
     776         5334 :             if (!measureBase->isMeasure()) {
     777          552 :                 measureBase->setExcludeFromOtherParts(false);
     778              :             }
     779              :         }
     780          600 :     }
     781          600 : }
     782              : 
     783          635 : void CompatUtils::mapHeaderFooterStyles(MasterScore* score)
     784              : {
     785              :     // Copyright and page numbers used header/footer styling before 4.4 - after 4.4 these have their own styles. To ensure nothing
     786              :     // changes visually when loading a pre-4.4 score for the first time, we must search the header/footer strings for copyright/page
     787              :     // number macros and set the "defaults" for copyright/page number styles based on where the macros were inserted...
     788         1270 :     const auto doMap = [score](const TextStyleType type, const std::vector<Sid>& headerFooterStringSids) {
     789         1270 :         const TextStyle* headerFooterTextStyle = textStyle(type);
     790         1270 :         const TextStyle* copyrightTextStyle = textStyle(TextStyleType::COPYRIGHT);
     791         1270 :         const TextStyle* pageNumberTextStyle = textStyle(TextStyleType::PAGE_NUMBER);
     792              : 
     793              :         //! NOTE: Keep in sync with Page::replaceTextMacros
     794         1270 :         const std::wregex copyrightSearch(LR"(\$[cC])");
     795         1270 :         const std::wregex pageNumberSearch(LR"(\$[pPnN])");
     796              : 
     797         1270 :         bool haveMappedCopyright = false;
     798         1270 :         bool haveMappedPageNumber = false;
     799         8890 :         for (const Sid sid : headerFooterStringSids) {
     800         7620 :             const String s = score->style().styleSt(sid);
     801         7620 :             if (!haveMappedCopyright && s.contains(copyrightSearch)) {
     802         2091 :                 for (size_t i = 0; i < TEXT_STYLE_SIZE; ++i) {
     803         1968 :                     const Sid headerFooterSid = headerFooterTextStyle->at(i).sid;
     804         1968 :                     const PropertyValue pv = score->style().styleV(headerFooterSid);
     805         1968 :                     const Sid copyrightSid = copyrightTextStyle->at(i).sid;
     806         1968 :                     score->style().set(copyrightSid, pv);
     807         1968 :                 }
     808          123 :                 haveMappedCopyright = true;
     809              :             }
     810         7620 :             if (!haveMappedPageNumber && s.contains(pageNumberSearch)) {
     811        10863 :                 for (size_t i = 0; i < TEXT_STYLE_SIZE; ++i) {
     812        10224 :                     const Sid headerFooterSid = headerFooterTextStyle->at(i).sid;
     813        10224 :                     const Sid pageNumberSid = pageNumberTextStyle->at(i).sid;
     814        10224 :                     const PropertyValue pv = score->style().styleV(headerFooterSid);
     815        10224 :                     score->style().set(pageNumberSid, pv);
     816        10224 :                 }
     817          639 :                 haveMappedPageNumber = true;
     818              :             }
     819         7620 :         }
     820         1270 :     };
     821              : 
     822         1270 :     doMap(TextStyleType::HEADER, { Sid::oddHeaderL,  Sid::oddHeaderC,  Sid::oddHeaderR,
     823              :                                    Sid::evenHeaderL, Sid::evenHeaderC, Sid::evenHeaderR });
     824         1270 :     doMap(TextStyleType::FOOTER, { Sid::oddFooterL,  Sid::oddFooterC,  Sid::oddFooterR,
     825              :                                    Sid::evenFooterL, Sid::evenFooterC, Sid::evenFooterR });
     826          635 : }
     827              : 
     828            0 : NoteLine* CompatUtils::createNoteLineFromTextLine(TextLine* textLine)
     829              : {
     830            0 :     IF_ASSERT_FAILED((textLine->startElement() && textLine->startElement()->isNote())
     831              :                      && (textLine->endElement() && textLine->endElement()->isNote())) {
     832            0 :         LOGW("Skipping textline with non note endpoints");
     833            0 :         return nullptr;
     834              :     }
     835            0 :     Note* startNote = toNote(textLine->startElement());
     836            0 :     Note* endNote = toNote(textLine->endElement());
     837              : 
     838            0 :     NoteLine* noteLine = Factory::createNoteLine(startNote);
     839            0 :     noteLine->setStartElement(startNote);
     840            0 :     noteLine->setTrack(textLine->track());
     841            0 :     noteLine->setTick(textLine->tick());
     842            0 :     noteLine->setEndElement(endNote);
     843            0 :     noteLine->setTick2(textLine->tick2());
     844            0 :     noteLine->setVisible(textLine->visible());
     845              : 
     846              :     // Preserve old layout style
     847            0 :     noteLine->setLineEndPlacement(NoteLineEndPlacement::LEFT_EDGE);
     848              : 
     849            0 :     for (Pid pid : textLine->textLineBasePropertyIds()) {
     850            0 :         noteLine->setProperty(pid, textLine->getProperty(pid));
     851              :     }
     852              : 
     853            0 :     for (const SpannerSegment* oldSeg : textLine->spannerSegments()) {
     854            0 :         LineSegment* newSeg = noteLine->createLineSegment();
     855            0 :         newSeg->setOffset(oldSeg->offset());
     856            0 :         newSeg->setUserOff2(oldSeg->userOff2());
     857              : 
     858            0 :         noteLine->add(newSeg);
     859              :     }
     860              : 
     861            0 :     LinkedObjects* links = textLine->links();
     862            0 :     noteLine->setLinks(links);
     863            0 :     if (links) {
     864            0 :         links->push_back(noteLine);
     865              :     }
     866              : 
     867            0 :     return noteLine;
     868              : }
     869              : 
     870          673 : void CompatUtils::convertTextLineToNoteAnchoredLine(MasterScore* masterScore)
     871              : {
     872          673 :     std::set<TextLine*> oldLines; // NoteLines used to be TextLines
     873              : 
     874         5584 :     for (Measure* measure = masterScore->firstMeasure(); measure; measure = measure->nextMeasure()) {
     875        18902 :         for (Segment& segment : measure->segments()) {
     876        13991 :             if (!segment.isChordRestType()) {
     877         2602 :                 continue;
     878              :             }
     879        86618 :             for (track_idx_t track = 0; track <= masterScore->ntracks(); track++) {
     880        75229 :                 EngravingItem* el = segment.element(track);
     881        75229 :                 if (!el || !el->isChord()) {
     882        65605 :                     continue;
     883              :                 }
     884         9624 :                 Chord* chord = toChord(el);
     885        19948 :                 for (Note* note : chord->notes()) {
     886        10427 :                     for (Spanner* spanner : note->spannerFor()) {
     887          103 :                         if (!spanner->isTextLine()) {
     888          103 :                             continue;
     889              :                         }
     890              : 
     891            0 :                         oldLines.insert(toTextLine(spanner));
     892              : 
     893            0 :                         LinkedObjects* links = spanner->links();
     894            0 :                         if (!links || links->empty()) {
     895            0 :                             continue;
     896              :                         }
     897              : 
     898            0 :                         for (EngravingObject* linked : *links) {
     899            0 :                             if (linked != spanner && linked && linked->isTextLine()) {
     900            0 :                                 oldLines.insert(toTextLine(linked));
     901              :                             }
     902              :                         }
     903              :                     }
     904              :                 }
     905              :             }
     906              :         }
     907              :     }
     908              : 
     909          673 :     for (TextLine* oldLine : oldLines) {
     910            0 :         NoteLine* newLine = createNoteLineFromTextLine(oldLine);
     911            0 :         if (!newLine) {
     912            0 :             oldLine->ownershipParentItem()->remove(oldLine);
     913            0 :             delete oldLine;
     914            0 :             continue;
     915              :         }
     916            0 :         EngravingItem* parent = newLine->ownershipParentItem();
     917              : 
     918            0 :         parent->remove(oldLine);
     919            0 :         parent->add(newLine);
     920              : 
     921            0 :         delete oldLine;
     922              :     }
     923          673 : }
     924              : 
     925          673 : void CompatUtils::convertLaissezVibArticToTie(MasterScore* masterScore)
     926              : {
     927          673 :     std::set<Articulation*> oldArtics; // NoteLines used to be TextLines
     928              : 
     929         5584 :     for (Measure* meas = masterScore->firstMeasure(); meas; meas = meas->nextMeasure()) {
     930        18902 :         for (Segment& seg : meas->segments()) {
     931        13991 :             if (!seg.isChordRestType()) {
     932         2602 :                 continue;
     933              :             }
     934        75229 :             for (EngravingItem* item : seg.elist()) {
     935        63840 :                 if (!item || !item->isChord()) {
     936        54216 :                     continue;
     937              :                 }
     938         9624 :                 Chord* chord = toChord(item);
     939         9998 :                 for (Articulation* a : chord->articulations()) {
     940          374 :                     if (!a->isLaissezVib()) {
     941          374 :                         continue;
     942              :                     }
     943            0 :                     oldArtics.insert(a);
     944              : 
     945            0 :                     LinkedObjects* links = a->links();
     946            0 :                     if (!links || links->empty()) {
     947            0 :                         continue;
     948              :                     }
     949              : 
     950            0 :                     for (EngravingObject* linked : *links) {
     951            0 :                         if (linked != a && linked && linked->isLaissezVib()) {
     952            0 :                             oldArtics.insert(toArticulation(linked));
     953              :                         }
     954              :                     }
     955              :                 }
     956              :             }
     957              :         }
     958              :     }
     959              : 
     960          673 :     for (Articulation* oldArtic : oldArtics) {
     961            0 :         Chord* parentChord = toChord(oldArtic->chordRest());
     962            0 :         Note* parentNote = oldArtic->up() ? parentChord->upNote() : parentChord->downNote();
     963              : 
     964            0 :         parentChord->remove(oldArtic);
     965              : 
     966            0 :         LaissezVib* lv = Factory::createLaissezVib(parentNote);
     967            0 :         parentNote->add(lv);
     968              : 
     969            0 :         delete oldArtic;
     970              :     }
     971          673 : }
     972              : 
     973            1 : void CompatUtils::setHarmonyRootTpcFromFunction(HarmonyInfo* info, const Harmony* h, const muse::String& s)
     974              : {
     975            1 :     Key key = Key::INVALID;
     976            1 :     const Staff* st = h->staff();
     977            1 :     key = st ? st->key(h->tick()) : Key::INVALID;
     978            1 :     info->setRootTpc(function2Tpc(s, key));
     979            1 : }
     980              : 
     981      2446732 : Sid CompatUtils::positionStyleFromAlign(Sid align)
     982              : {
     983      2446732 :     return muse::value(ALIGN_VALS_TO_CONVERT, align, Sid::NOSTYLE);
     984              : }
     985              : 
     986         1193 : void CompatUtils::setPositionStylesFromAlign(MStyle* style, std::vector<Sid> ignoreSids)
     987              : {
     988              :     // Make sure new position styles are initially the same as align values
     989      2451615 :     for (const StyleDef::StyleValue& st : StyleDef::styleValues) {
     990      2450422 :         if (muse::contains(ignoreSids, st.sid)) {
     991         3690 :             continue;
     992              :         }
     993      2446732 :         Sid positionSid = CompatUtils::positionStyleFromAlign(st.sid);
     994      2446732 :         if (positionSid == Sid::NOSTYLE) {
     995      2374611 :             continue;
     996              :         }
     997        72121 :         AlignH val = style->value(st.sid).value<Align>().horizontal;
     998        72121 :         style->set(positionSid, val);
     999              :     }
    1000         1193 : }
    1001              : 
    1002          428 : void CompatUtils::setTextLineTextPositionFromAlign(TextLineBase* tl)
    1003              : {
    1004          428 :     tl->setBeginTextPosition(tl->beginTextAlign().horizontal);
    1005          428 :     if (tl->beginTextPosition() != tl->propertyDefault(Pid::BEGIN_TEXT_POSITION).value<AlignH>()) {
    1006            0 :         tl->setPropertyFlags(Pid::BEGIN_TEXT_POSITION, PropertyFlags::UNSTYLED);
    1007              :     }
    1008          428 :     tl->setContinueTextPosition(tl->continueTextAlign().horizontal);
    1009          428 :     if (tl->continueTextPosition() != tl->propertyDefault(Pid::CONTINUE_TEXT_POSITION).value<AlignH>()) {
    1010            0 :         tl->setPropertyFlags(Pid::CONTINUE_TEXT_POSITION, PropertyFlags::UNSTYLED);
    1011              :     }
    1012          428 :     tl->setEndTextPosition(tl->endTextAlign().horizontal);
    1013          428 :     if (tl->endTextPosition() != tl->propertyDefault(Pid::END_TEXT_POSITION).value<AlignH>()) {
    1014           12 :         tl->setPropertyFlags(Pid::END_TEXT_POSITION, PropertyFlags::UNSTYLED);
    1015              :     }
    1016          428 : }
    1017              : 
    1018          395 : void CompatUtils::resetHookHeightSign(TextLineBase* tl)
    1019              : {
    1020          395 :     if (!tl->placeBelow()) {
    1021          172 :         return;
    1022              :     }
    1023              : 
    1024          223 :     if (!tl->isStyled(Pid::BEGIN_HOOK_HEIGHT)) {
    1025          173 :         Spatium beginHookHeight = tl->getProperty(Pid::BEGIN_HOOK_HEIGHT).value<Spatium>();
    1026          173 :         tl->setProperty(Pid::BEGIN_HOOK_HEIGHT, -beginHookHeight);
    1027              :     }
    1028          223 :     if (!tl->isStyled(Pid::END_HOOK_HEIGHT)) {
    1029          172 :         Spatium endHookHeight = tl->getProperty(Pid::END_HOOK_HEIGHT).value<Spatium>();
    1030          172 :         tl->setProperty(Pid::END_HOOK_HEIGHT, -endHookHeight);
    1031              :     }
    1032              : }
    1033              : 
    1034          615 : void CompatUtils::setMusicSymbolSize470(MStyle& style)
    1035              : {
    1036              :     // Music symbols have their own point size in 4.7
    1037              :     // Initialize this to the text type's default font size
    1038        46125 :     for (TextStyleType textStyleType : allTextStyles()) {
    1039        45510 :         if (textStyleType == TextStyleType::REPEAT_LEFT || textStyleType == TextStyleType::REPEAT_RIGHT) {
    1040         1230 :             continue;
    1041              :         }
    1042              : 
    1043        44280 :         const TextStyle* ts = textStyle(textStyleType);
    1044        44280 :         Sid musicSymbolSizeSid = Sid::NOSTYLE;
    1045        44280 :         Sid fontSizeSid = Sid::NOSTYLE;
    1046              : 
    1047       752760 :         for (size_t i = 0; i < TEXT_STYLE_SIZE; ++i) {
    1048       708480 :             if (ts->at(i).pid == Pid::MUSIC_SYMBOL_SIZE) {
    1049        44280 :                 musicSymbolSizeSid = ts->at(i).sid;
    1050              :             }
    1051              : 
    1052       708480 :             if (ts->at(i).pid == Pid::FONT_SIZE) {
    1053        38745 :                 fontSizeSid = ts->at(i).sid;
    1054              :             }
    1055              :         }
    1056              : 
    1057        44280 :         if (musicSymbolSizeSid == Sid::NOSTYLE || fontSizeSid == Sid::NOSTYLE) {
    1058         5535 :             continue;
    1059              :         }
    1060              : 
    1061        38745 :         double size = style.value(fontSizeSid).toDouble();
    1062        38745 :         if (textStyleType == TextStyleType::TEMPO) {
    1063          615 :             size *= TempoText::DEFAULT_SYM_SIZE_RATIO;
    1064              :         }
    1065              : 
    1066        38745 :         style.set(musicSymbolSizeSid, size);
    1067              :     }
    1068          615 : }
    1069              : 
    1070            9 : void CompatUtils::doMigrateNoteParens(EngravingItem* item)
    1071              : {
    1072            9 :     if (!item->isNote()) {
    1073            0 :         return;
    1074              :     }
    1075            9 :     Note* note = toNote(item);
    1076            9 :     if (!note->leftParen() && !note->rightParen()) {
    1077            9 :         return;
    1078              :     }
    1079              : 
    1080            0 :     if (note->leftParen()) {
    1081            0 :         note->remove(note->leftParen());
    1082              :     }
    1083              : 
    1084            0 :     if (note->rightParen()) {
    1085            0 :         note->remove(note->rightParen());
    1086              :     }
    1087              : 
    1088            0 :     Chord* chord = note->chord();
    1089              : 
    1090            0 :     EditChord::addChordParentheses(chord, { note });
    1091              : }
    1092              : 
    1093              : static constexpr double PRE_470_DPI = 360;
    1094              : 
    1095         4360 : Spatium CompatUtils::convertPre470FrameRadius(double frameRadius)
    1096              : {
    1097              :     // The frame radius used to be expressed in raster units and divided by 2 at drawing. Since 4.7 it is expressed in spatium.
    1098         4360 :     return Spatium(frameRadius * (DPI / PRE_470_DPI) / DefaultStyle::baseStyle().value(Sid::spatium).toDouble()) / 2;
    1099              : }
    1100              : 
    1101           21 : void CompatUtils::convertPre470ImageSize(Image* image)
    1102              : {
    1103           21 :     if (image->size().isNull() && image->score()->mscVersion() < 470) {
    1104            7 :         image->init();
    1105            7 :         image->setSize(image->size() * (DPI / PRE_470_DPI));
    1106              :     }
    1107           21 : }
    1108              : 
    1109          190 : PointF CompatUtils::getAdjustedOffset(EngravingItem* item, PointF offset)
    1110              : {
    1111          190 :     PointF defaultOffset = item->defaultPos();
    1112          380 :     return offset - defaultOffset;
    1113              : }
    1114              : 
    1115          257 : void CompatUtils::migrateOffset500(EngravingItem* item, PropertyValue& offset)
    1116              : {
    1117          257 :     if (!item->isTextBase() && !item->isSpanner() && !item->isSpannerSegment()) {
    1118           54 :         return;
    1119              :     }
    1120              : 
    1121              :     // We need additional context for items with voice assignment properties
    1122              :     // Migrate in EngravingCompat after layout
    1123          203 :     if (item->hasVoiceAssignmentProperties()) {
    1124           13 :         return;
    1125              :     }
    1126              : 
    1127          190 :     offset = getAdjustedOffset(item, offset.value<PointF>());
    1128              : }
    1129              : 
    1130         2610 : void CompatUtils::migrateOffsetPre302(EngravingItem* item, int mscVersion)
    1131              : {
    1132         2610 :     if (mscVersion > 301 || item->offset().isNull()) {
    1133         2566 :         return;
    1134              :     }
    1135              : 
    1136           44 :     PropertyValue offset = item->getProperty(Pid::OFFSET);
    1137           44 :     CompatUtils::migrateOffset500(item, offset);
    1138           44 :     item->setProperty(Pid::OFFSET, offset);
    1139           44 : }
    1140              : 
    1141            0 : static void reregisterInstrumentChanges(Score* score)
    1142              : {
    1143              :     /* When reading the <5.0 InstrumentChange MMRest copy, it has overwritten the Part's InstrumentList
    1144              :      * entry for this segment's tick. Deleting the copies unregistered their Instruments from the
    1145              :      * InstrumentList. So we need to restore the InstrumentList entries with the originals. */
    1146            0 :     for (MeasureBase* mb = score->first(); mb; mb = mb->next()) {
    1147            0 :         if (!mb->isMeasure()) {
    1148            0 :             continue;
    1149              :         }
    1150            0 :         for (Segment& seg : toMeasure(mb)->segments()) {
    1151            0 :             for (EngravingItem* annotation : seg.annotations()) {
    1152            0 :                 Instrument* instr = nullptr;
    1153            0 :                 if (annotation->isInstrumentChange()) {
    1154            0 :                     instr = toInstrumentChange(annotation)->instrument();
    1155            0 :                 } else if (annotation->isStaffState() && toStaffState(annotation)->staffStateType() == StaffStateType::INSTRUMENT) {
    1156            0 :                     instr = toStaffState(annotation)->instrument();
    1157              :                 }
    1158            0 :                 if (instr) {
    1159            0 :                     annotation->part()->setInstrument(instr, seg.tick());
    1160              :                 }
    1161              :             }
    1162              :         }
    1163              :     }
    1164            0 : }
    1165              : 
    1166          838 : void CompatUtils::removeMMRestElements(MasterScore* masterScore)
    1167              : {
    1168              :     // <5.0 MMRests had copies of underlying elements. We now move the element when toggling the rests.
    1169              :     // Remove redundant copies which are written to the file
    1170              : 
    1171         1716 :     for (Score* score : masterScore->scoreList()) {
    1172          878 :         bool removedInstrumentChange = false;
    1173              : 
    1174         8248 :         for (MeasureBase* mb = score->first(); mb; mb = mb->next()) {
    1175         7370 :             if (!mb->isMeasure()) {
    1176         7323 :                 continue;
    1177              :             }
    1178         6717 :             Measure* m = toMeasure(mb);
    1179         6717 :             Measure* mmrest = m->mmRest();
    1180              : 
    1181         6717 :             if (!mmrest) {
    1182         6670 :                 continue;
    1183              :             }
    1184              : 
    1185              :             // Remove jumps/markers/layout breaks
    1186           47 :             std::vector<EngravingItem*> measureEls = mmrest->el();
    1187           55 :             for (EngravingItem* el : measureEls) {
    1188            8 :                 el->unlink();
    1189            8 :                 score->removeElement(el);
    1190            8 :                 delete el;
    1191              :             }
    1192              : 
    1193              :             // Remove annotations
    1194          124 :             for (Segment& seg : mmrest->segments()) {
    1195           77 :                 std::vector<EngravingItem*> annotations = seg.annotations();
    1196           82 :                 for (EngravingItem* annotation : annotations) {
    1197            5 :                     removedInstrumentChange |= annotation->isInstrumentChange()
    1198            5 :                                                || (annotation->isStaffState()
    1199            0 :                                                    && toStaffState(annotation)->staffStateType() == StaffStateType::INSTRUMENT);
    1200            5 :                     annotation->unlink();
    1201            5 :                     score->removeElement(annotation);
    1202            5 :                     delete annotation;
    1203              :                 }
    1204           77 :                 if (seg.isChordRestType()) {
    1205           47 :                     continue;
    1206              :                 }
    1207              :                 // Remove time sigs, key sigs, clefs, ambitus, breaths, barlines
    1208           61 :                 for (staff_idx_t staffIdx = 0; staffIdx < score->nstaves(); ++staffIdx) {
    1209           31 :                     EngravingItem* el = seg.element(staff2track(staffIdx));
    1210           31 :                     if (!el) {
    1211            0 :                         continue;
    1212              :                     }
    1213           31 :                     el->unlink();
    1214           31 :                     score->removeElement(el);
    1215           31 :                     delete el;
    1216              :                 }
    1217           77 :             }
    1218           47 :         }
    1219          878 :         if (removedInstrumentChange) {
    1220            0 :             reregisterInstrumentChanges(score);
    1221              :         }
    1222          838 :     }
    1223          838 : }
    1224              : 
    1225            3 : void CompatUtils::readChordLinePath(ChordLine* chordLine, XmlReader& e)
    1226              : {
    1227              :     /* Before 5.0, a manually edited chord line stored its whole path.
    1228              :      * Now only the offsets of its points from the default shape are stored. */
    1229              : 
    1230            3 :     std::vector<PointF> points;
    1231           13 :     while (e.readNextStartElement()) {
    1232           10 :         if (e.name() == "Element") {
    1233           10 :             points.emplace_back(e.doubleAttribute("x"), e.doubleAttribute("y"));
    1234           10 :             e.skipCurrentElement();
    1235              :         } else {
    1236            0 :             e.unknown();
    1237              :         }
    1238              :     }
    1239              : 
    1240            3 :     std::vector<ChordLine::Point> pointTypes;
    1241            3 :     if (points.size() == 2) {
    1242            1 :         pointTypes = { ChordLine::Point::START, ChordLine::Point::END };
    1243            2 :     } else if (points.size() == size_t(ChordLine::Point::POINTS_COUNT)) {
    1244            2 :         pointTypes = { ChordLine::Point::START, ChordLine::Point::BEZIER1, ChordLine::Point::BEZIER2, ChordLine::Point::END };
    1245              :     } else {
    1246            0 :         LOGW() << "Unexpected number of chord line path elements: " << points.size();
    1247            0 :         return;
    1248              :     }
    1249              : 
    1250              :     // The saved path was in spatium units of the chord's staff
    1251            3 :     const EngravingObject* parent = chordLine->ownershipParent();
    1252            3 :     const Chord* chord = parent && parent->isChord() ? toChord(parent) : nullptr;
    1253            3 :     const double spatium = chord ? chord->spatium() : chordLine->spatium();
    1254            3 :     const double mag = chord ? chord->intrinsicMag() : 1.0;
    1255            3 :     const ChordLine::Points defaultPoints = chordLine->defaultPoints();
    1256              : 
    1257           13 :     for (size_t i = 0; i < points.size(); ++i) {
    1258              :         // the old line was drawn at the saved point * spatium; layout will draw (default * spatium + offset) * mag
    1259           10 :         const ChordLine::Point point = pointTypes.at(i);
    1260           10 :         PointF offset = points.at(i) * spatium / mag - defaultPoints[size_t(point)] * spatium;
    1261              :         // ignore rounding errors, so unedited points remain unedited:
    1262           10 :         if (std::abs(offset.x()) < 0.001 * spatium) {
    1263            5 :             offset.setX(0.0);
    1264              :         }
    1265           10 :         if (std::abs(offset.y()) < 0.001 * spatium) {
    1266            6 :             offset.setY(0.0);
    1267              :         }
    1268           10 :         chordLine->setBezierOffset(point, offset);
    1269              :     }
    1270            3 : }
        

Generated by: LCOV version 2.0-1