LCOV - code coverage report
Current view: top level - src/engraving/compat - engravingcompat.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 462 0
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 "engravingcompat.h"
      24              : 
      25              : #include <set>
      26              : 
      27              : #include "dom/beam.h"
      28              : #include "dom/box.h"
      29              : #include "dom/chord.h"
      30              : #include "dom/hammeronpulloff.h"
      31              : #include "dom/harmony.h"
      32              : #include "dom/instrument.h"
      33              : #include "dom/lyrics.h"
      34              : #include "dom/marker.h"
      35              : #include "dom/masterscore.h"
      36              : #include "dom/measure.h"
      37              : #include "dom/note.h"
      38              : #include "dom/part.h"
      39              : #include "dom/pedal.h"
      40              : #include "dom/spanner.h"
      41              : #include "dom/staff.h"
      42              : #include "dom/system.h"
      43              : #include "dom/tapping.h"
      44              : #include "editing/editchord.h"
      45              : #include "editing/reset.h"
      46              : #include "editing/transaction/transaction.h"
      47              : #include "rendering/iscorerenderer.h"
      48              : #include "rw/compat/compatutils.h"
      49              : #include "types/types.h"
      50              : 
      51              : using namespace mu::engraving;
      52              : 
      53              : namespace mu::engraving::compat {
      54            0 : void EngravingCompat::doPreLayoutCompatIfNeeded(MasterScore* score)
      55              : {
      56            0 :     int mscVersion = score->mscVersion();
      57              : 
      58            0 :     if (mscVersion < 400) {
      59            0 :         applyPre400StyleCompat(score);
      60              :     }
      61              : 
      62            0 :     if (mscVersion < 440) {
      63            0 :         correctPedalEndPoints(score);
      64            0 :         migrateDynamicPosOnVocalStaves(score);
      65            0 :         if (mscVersion >= 420) {
      66            0 :             undoStaffTextExcludeFromPart(score);
      67              :         }
      68              :     }
      69              : 
      70            0 :     if (mscVersion < 460) {
      71            0 :         resetMarkerLeftFontSize(score);
      72            0 :         resetRestVerticalOffsets(score);
      73            0 :         adjustVBoxDistances(score);
      74              :     }
      75              : 
      76            0 :     if (mscVersion < 470) {
      77            0 :         pre470TextCompat(score);
      78            0 :         migrateNoteParens(score);
      79              :     }
      80              : 
      81            0 :     if (mscVersion < 500) {
      82            0 :         resetIneffectiveCenterBetweenStaves(score);
      83              :     }
      84            0 : }
      85              : 
      86            0 : void EngravingCompat::correctPedalEndPoints(MasterScore* score)
      87              : {
      88              :     // Pedal lines ending with 45° hook used to be hacked to end before their actual end duration.
      89              :     // Hack is now removed, so we need to correct them to preserve engraving result. (M.S.)
      90            0 :     for (auto pair : score->spanner()) {
      91            0 :         Spanner* spanner = pair.second;
      92            0 :         if (spanner->isPedal() && toPedal(spanner)->endHookType() == HookType::HOOK_45) {
      93            0 :             ChordRest* endCR = score->findChordRestEndingBeforeTickInStaff(spanner->tick2(), track2staff(spanner->track()));
      94            0 :             if (endCR) {
      95            0 :                 for (EngravingObject* item : spanner->linkList()) {
      96            0 :                     toSpanner(item)->setTick2(endCR->tick());
      97            0 :                 }
      98              :             }
      99              :         }
     100              :     }
     101            0 : }
     102              : 
     103            0 : void EngravingCompat::undoStaffTextExcludeFromPart(MasterScore* masterScore)
     104              : {
     105            0 :     for (Score* score : masterScore->scoreList()) {
     106            0 :         for (MeasureBase* mb = score->first(); mb; mb = mb->next()) {
     107            0 :             if (!mb->isMeasure()) {
     108            0 :                 continue;
     109              :             }
     110            0 :             for (Segment& segment : toMeasure(mb)->segments()) {
     111            0 :                 if (!segment.isChordRestType()) {
     112            0 :                     continue;
     113              :                 }
     114            0 :                 for (EngravingItem* item : segment.annotations()) {
     115            0 :                     if (!item || !item->isStaffText()) {
     116            0 :                         continue;
     117              :                     }
     118            0 :                     if (item->excludeFromOtherParts()) {
     119            0 :                         item->undoChangeProperty(Pid::EXCLUDE_FROM_OTHER_PARTS, false);
     120            0 :                         for (EngravingObject* linkedItem : item->linkList()) {
     121            0 :                             if (linkedItem == item && !linkedItem->score()->isMaster()) {
     122            0 :                                 toEngravingItem(item)->setAppearanceLinkedToMaster(false);
     123            0 :                             } else if (linkedItem != item) {
     124            0 :                                 linkedItem->undoChangeProperty(Pid::VISIBLE, false);
     125              :                             }
     126            0 :                         }
     127              :                     }
     128              :                 }
     129              :             }
     130              :         }
     131            0 :     }
     132            0 : }
     133              : 
     134            0 : void EngravingCompat::migrateDynamicPosOnVocalStaves(MasterScore* masterScore)
     135              : {
     136            0 :     auto migrateVoiceAssignmentAndPosition = [masterScore](EngravingItem* item) {
     137            0 :         if (item->voice() != 0) {
     138            0 :             item->setProperty(Pid::VOICE_ASSIGNMENT, VoiceAssignment::CURRENT_VOICE_ONLY);
     139              :         }
     140              :         // Migrate position on vocal staves (to match old default, which used to be below)
     141            0 :         Staff* staff = item->staff();
     142            0 :         Part* part = staff ? staff->part() : nullptr;
     143            0 :         Instrument* instrument = part ? part->instrument() : nullptr;
     144            0 :         const bool isVocalInstrument = instrument && instrument->isVocalInstrument();
     145            0 :         const bool directionIsDefault = item->getProperty(Pid::DIRECTION) == item->propertyDefault(Pid::DIRECTION);
     146            0 :         const PlacementV defaultPlacement = masterScore->style().styleV(item->getPropertyStyle(Pid::PLACEMENT)).value<PlacementV>();
     147            0 :         const bool defaultIsBelow = defaultPlacement == PlacementV::BELOW;
     148              : 
     149            0 :         if (isVocalInstrument && directionIsDefault && defaultIsBelow) {
     150            0 :             item->setProperty(Pid::DIRECTION, DirectionV::DOWN);
     151            0 :             item->setPropertyFlags(Pid::DIRECTION, PropertyFlags::UNSTYLED);
     152              :         }
     153            0 :     };
     154              : 
     155            0 :     for (Score* score : masterScore->scoreList()) {
     156            0 :         for (MeasureBase* mb = score->first(); mb; mb = mb->next()) {
     157            0 :             if (!mb->isMeasure()) {
     158            0 :                 continue;
     159              :             }
     160            0 :             for (Segment& segment : toMeasure(mb)->segments()) {
     161            0 :                 if (!segment.isChordRestType()) {
     162            0 :                     continue;
     163              :                 }
     164            0 :                 for (EngravingItem* item : segment.annotations()) {
     165            0 :                     if (item && item->hasVoiceAssignmentProperties()) {
     166            0 :                         migrateVoiceAssignmentAndPosition(item);
     167              :                     }
     168              :                 }
     169              :             }
     170              :         }
     171              : 
     172            0 :         for (auto pair : score->spanner()) {
     173            0 :             Spanner* spanner = pair.second;
     174            0 :             if (spanner->isHairpin()) {
     175            0 :                 migrateVoiceAssignmentAndPosition(spanner);
     176              :             }
     177              :         }
     178            0 :     }
     179            0 : }
     180              : 
     181            0 : void EngravingCompat::resetIneffectiveCenterBetweenStaves(MasterScore* masterScore)
     182              : {
     183              :     /* Before 5.0, dynamics and hairpins could only be centered between staves of their own part, but
     184              :      * they could carry the property, e.g. if the item was copied from a piano part into a vocal part,
     185              :      * even if it visually had no effect. Now it can also center between vocal staves, so we need to
     186              :      * reset it to keep the old layout. */
     187            0 :     auto resetIfIneffective = [](EngravingItem* item) {
     188            0 :         const Part* part = item->part();
     189            0 :         if (part && part->nstaves() <= 1
     190            0 :             && item->getProperty(Pid::CENTER_BETWEEN_STAVES).value<AutoOnOff>() == AutoOnOff::ON) {
     191            0 :             item->resetProperty(Pid::CENTER_BETWEEN_STAVES);
     192              :         }
     193            0 :     };
     194              : 
     195            0 :     for (Score* score : masterScore->scoreList()) {
     196            0 :         for (MeasureBase* mb = score->first(); mb; mb = mb->next()) {
     197            0 :             if (!mb->isMeasure()) {
     198            0 :                 continue;
     199              :             }
     200            0 :             for (Segment& segment : toMeasure(mb)->segments()) {
     201            0 :                 if (!segment.isChordRestType() && !segment.isTimeTickType()) {
     202            0 :                     continue;
     203              :                 }
     204            0 :                 for (EngravingItem* item : segment.annotations()) {
     205            0 :                     if (item && (item->isDynamic() || item->isExpression())) {
     206            0 :                         resetIfIneffective(item);
     207              :                     }
     208              :                 }
     209              :             }
     210              :         }
     211              : 
     212            0 :         for (auto pair : score->spanner()) {
     213            0 :             Spanner* spanner = pair.second;
     214            0 :             if (spanner->isHairpin()) {
     215            0 :                 resetIfIneffective(spanner);
     216              :             }
     217              :         }
     218            0 :     }
     219            0 : }
     220              : 
     221            0 : void EngravingCompat::resetMarkerLeftFontSize(MasterScore* masterScore)
     222              : {
     223              :     // Reset the new incorrect 4.4.0 - 4.4.2 default size of 11 to the previous correct size of 18
     224            0 :     const double INCORRECT_DEFAULT_SIZE = 11.0;
     225            0 :     const double CORRECT_DEFAULT_SIZE = 18.0;
     226            0 :     bool needsAdjustMarkerSize = masterScore->mscoreVersion().contains(u"4.4") && masterScore->mscoreVersion() != u"4.4.3";
     227            0 :     if (!needsAdjustMarkerSize || masterScore->style().styleD(Sid::repeatLeftFontSize) != INCORRECT_DEFAULT_SIZE) {
     228            0 :         return;
     229              :     }
     230            0 :     masterScore->style().set(Sid::repeatLeftFontSize, CORRECT_DEFAULT_SIZE);
     231              : 
     232            0 :     for (Score* score : masterScore->scoreList()) {
     233            0 :         for (MeasureBase* mb = score->first(); mb; mb = mb->next()) {
     234            0 :             if (!mb->isMeasure()) {
     235            0 :                 continue;
     236              :             }
     237            0 :             Measure* meas = toMeasure(mb);
     238            0 :             for (EngravingItem* item : meas->el()) {
     239            0 :                 if (!item->isMarker()) {
     240            0 :                     continue;
     241              :                 }
     242            0 :                 Marker* marker = toMarker(item);
     243            0 :                 if (marker->textStyleType() != TextStyleType::REPEAT_LEFT || marker->size() != INCORRECT_DEFAULT_SIZE) {
     244            0 :                     continue;
     245              :                 }
     246            0 :                 marker->setSize(CORRECT_DEFAULT_SIZE);
     247              :             }
     248              :         }
     249            0 :     }
     250              : }
     251              : 
     252            0 : void EngravingCompat::resetRestVerticalOffsets(MasterScore* masterScore)
     253              : {
     254            0 :     for (Score* score : masterScore->scoreList()) {
     255            0 :         for (MeasureBase* mb = score->first(); mb; mb = mb->next()) {
     256            0 :             if (!mb->isMeasure()) {
     257            0 :                 continue;
     258              :             }
     259            0 :             Measure* measure = toMeasure(mb);
     260            0 :             for (staff_idx_t staff = 0; staff < score->nstaves(); ++staff) {
     261            0 :                 if (!measure->hasVoices(staff)) {
     262            0 :                     continue;
     263              :                 }
     264            0 :                 track_idx_t startTrack = staff2track(staff);
     265            0 :                 track_idx_t endTrack = startTrack + VOICES;
     266            0 :                 for (Segment& seg : measure->segments()) {
     267            0 :                     if (!seg.isChordRestType()) {
     268            0 :                         continue;
     269              :                     }
     270            0 :                     for (track_idx_t track = startTrack; track < endTrack; ++track) {
     271            0 :                         EngravingItem* item = seg.element(track);
     272            0 :                         if (item && item->isRest()) {
     273            0 :                             item->resetProperty(Pid::OFFSET);
     274              :                         }
     275              :                     }
     276              :                 }
     277              :             }
     278              :         }
     279            0 :     }
     280            0 : }
     281              : 
     282            0 : void EngravingCompat::adjustVBoxDistances(MasterScore* masterScore)
     283              : {
     284            0 :     for (Score* score : masterScore->scoreList()) {
     285            0 :         for (MeasureBase* mb = score->first(); mb; mb = mb->next()) {
     286            0 :             MeasureBase* nextmb = mb->next();
     287            0 :             if (mb->isVBoxBase()) {
     288            0 :                 VBox* vbox = static_cast<VBox*>(mb);
     289            0 :                 vbox->setProperty(Pid::PADDING_TO_NOTATION_ABOVE, Spatium()); // Because pre-4.6 these didn't exist
     290            0 :                 vbox->setPropertyFlags(Pid::PADDING_TO_NOTATION_ABOVE, PropertyFlags::UNSTYLED);
     291            0 :                 vbox->setProperty(Pid::PADDING_TO_NOTATION_BELOW, Spatium());
     292            0 :                 vbox->setPropertyFlags(Pid::PADDING_TO_NOTATION_BELOW, PropertyFlags::UNSTYLED);
     293            0 :                 if (nextmb && nextmb->isVBoxBase()) {
     294            0 :                     VBox* first = static_cast<VBox*>(mb);
     295            0 :                     VBox* second = static_cast<VBox*>(nextmb);
     296            0 :                     if (first->bottomGap() > 0_sp && second->topGap() > 0_sp) {
     297            0 :                         first->setProperty(Pid::BOTTOM_GAP, first->bottomGap() + second->topGap()); // Because pre-4.6 these used to be added
     298            0 :                         first->setPropertyFlags(Pid::BOTTOM_GAP, PropertyFlags::UNSTYLED);
     299              :                     }
     300              :                 }
     301              :             }
     302              :         }
     303            0 :     }
     304            0 : }
     305              : 
     306              : static constexpr std::array<ElementType, 6> OFFSET_UNIT_CONVERT_TYPES = {
     307              :     ElementType::FINGERING,
     308              :     ElementType::HAMMER_ON_PULL_OFF_TEXT,
     309              :     ElementType::LYRICS,
     310              :     ElementType::TAPPING
     311              : };
     312              : 
     313            0 : void EngravingCompat::pre470TextCompat(MasterScore* masterScore)
     314              : {
     315            0 :     auto doCompat = [](EngravingItem* item) {
     316            0 :         if (!item->isTextBase()) {
     317            0 :             return;
     318              :         }
     319              : 
     320            0 :         TextBase* text = toTextBase(item);
     321              : 
     322            0 :         if (!text->isStyled(Pid::FRAME_ROUND)) {
     323            0 :             text->setFrameRound(compat::CompatUtils::convertPre470FrameRadius(text->frameRound().val()));
     324              :         }
     325              : 
     326              :         // Text offset could have been in mm for these types prior to 4.7
     327              :         // Convert actual distance to spatium
     328            0 :         if (muse::contains(OFFSET_UNIT_CONVERT_TYPES, item->type()) && !item->sizeIsSpatiumDependent()) {
     329            0 :             PointF offset = item->offset();
     330            0 :             double spatium = item->style().spatium();
     331            0 :             offset /= spatium;
     332            0 :             offset *= DPMM;
     333              : 
     334            0 :             item->setProperty(Pid::OFFSET, offset);
     335              :         }
     336              : 
     337              :         // Staff text, system text, and harp pedal diagrams are the only types which are attached to notes and weren't already
     338              :         // placed with their left edges / centres / right edges aligned to the left / centre / right of the notehead
     339            0 :         if (text->positionRelativeToNoteheadRest()
     340            0 :             && (text->isStaffText() || text->isSystemText() || text->isHarpPedalDiagram())) {
     341            0 :             double mag = item->staff() ? item->staff()->staffMag(item) : 1.0;
     342            0 :             double xAdj = item->symWidth(SymId::noteheadBlack) * mag;
     343              : 
     344            0 :             switch (text->position()) {
     345            0 :             case AlignH::HCENTER:
     346            0 :                 text->setProperty(Pid::OFFSET, PointF(text->offset().x() - xAdj / 2, text->offset().y()));
     347            0 :                 break;
     348            0 :             case AlignH::RIGHT:
     349            0 :                 text->setProperty(Pid::OFFSET, PointF(text->offset().x() - xAdj, text->offset().y()));
     350            0 :                 break;
     351            0 :             default:
     352            0 :                 break;
     353              :             }
     354              :         }
     355              :     };
     356              : 
     357            0 :     for (Score* score : masterScore->scoreList()) {
     358            0 :         score->scanElements(doCompat);
     359            0 :     }
     360            0 : }
     361              : 
     362            0 : void EngravingCompat::migrateNoteParens(MasterScore* masterScore)
     363              : {
     364            0 :     for (Score* score : masterScore->scoreList()) {
     365            0 :         score->scanElements(CompatUtils::doMigrateNoteParens);
     366            0 :     }
     367            0 : }
     368              : 
     369            0 : static void doMigrateOffset500(EngravingItem* item)
     370              : {
     371            0 :     if (item->offset().isNull()
     372            0 :         || (!item->isTextBase() && !item->isSpanner() && !item->isSpannerSegment()) || !item->hasVoiceAssignmentProperties()) {
     373            0 :         return;
     374              :     }
     375              : 
     376            0 :     item->setProperty(Pid::OFFSET, CompatUtils::getAdjustedOffset(item, item->offset()));
     377              : 
     378            0 :     if (!muse::RealIsNull(item->offset().y())) {
     379              :         // In versions <5 any adjustment to y offset meant we couldn't centre items between staves
     380            0 :         item->setProperty(Pid::CENTER_BETWEEN_STAVES, AutoOnOff::OFF);
     381            0 :         if (item->isStyled(Pid::CENTER_BETWEEN_STAVES)) {
     382            0 :             item->setPropertyFlags(Pid::CENTER_BETWEEN_STAVES, PropertyFlags::UNSTYLED);
     383              :         }
     384              :     }
     385              : }
     386              : 
     387            0 : void EngravingCompat::migrateOffset500(MasterScore* masterScore)
     388              : {
     389            0 :     for (Score* score : masterScore->scoreList()) {
     390            0 :         for (Spanner* sp : score->spannerList()) {
     391            0 :             if (!sp->hasVoiceAssignmentProperties()) {
     392            0 :                 continue;
     393              :             }
     394            0 :             sp->setPlacementBasedOnVoiceAssignment(sp->style().styleV(Sid::dynamicsHairpinVoiceBasedPlacement).value<DirectionV>());
     395            0 :             doMigrateOffset500(sp);
     396            0 :             for (SpannerSegment* seg : sp->spannerSegments()) {
     397            0 :                 doMigrateOffset500(seg);
     398              :             }
     399            0 :         }
     400              : 
     401            0 :         score->scanElements(doMigrateOffset500);
     402            0 :     }
     403            0 : }
     404              : 
     405            0 : void EngravingCompat::doPostLayoutCompatIfNeeded(MasterScore* score)
     406              : {
     407            0 :     bool needRelayout = false;
     408              : 
     409            0 :     int mscVersion = score->mscVersion();
     410              : 
     411            0 :     if (mscVersion < 300) {
     412            0 :         resetAllElementsPositions(score);
     413            0 :         needRelayout = true;
     414              :     }
     415              : 
     416            0 :     if (mscVersion <= 206) {
     417            0 :         resetAllCrossBeams(score);
     418            0 :         needRelayout = true;
     419              :     }
     420              : 
     421            0 :     if (mscVersion < 440) {
     422            0 :         needRelayout |= relayoutUserModifiedCrossStaffBeams(score);
     423              :     }
     424              : 
     425            0 :     if (mscVersion < 470) {
     426            0 :         needRelayout |= setLyricLineVisibility(score);
     427              :     }
     428              : 
     429            0 :     if (mscVersion < 500) {
     430            0 :         migrateOffset500(score);
     431            0 :         AlignmentMigration500::migrateSnappedAndSameItemTypeAlignment(score);
     432            0 :         AlignmentMigration500::migrateHopoLetterAlignment(score);
     433            0 :         migrateOffsetAfterAutoplace(score);
     434            0 :         needRelayout = true;
     435              :     }
     436              : 
     437            0 :     if (needRelayout) {
     438            0 :         score->update();
     439              :     }
     440            0 : }
     441              : 
     442            0 : static void doMigrateOffsetAfterAutoplace(EngravingItem* item)
     443              : {
     444              :     // Before 5.0, item's were autoplaced from their offset position
     445              :     // After 5.0, offset is applied after autoplace
     446              : 
     447              :     // We can't fix the offset for some types.
     448              :     // These are types where layout is spread out in multiple steps and cannot be accurately calculated with one TLayout::layoutItem call.
     449            0 :     if (item->generated() || !autoplaceAppliesToType(item->type()) || !item->autoplace() || item->offset().isNull()
     450            0 :         || item->isRest()
     451            0 :         || item->isLyrics()
     452            0 :         || item->isLyricsLineSegment()
     453            0 :         || item->isArticulationFamily()
     454            0 :         || item->isFingering()
     455            0 :         || item->isSlurSegment()
     456            0 :         || item->isTieSegment()) {
     457            0 :         return;
     458              :     }
     459              : 
     460              :     // Initial position
     461            0 :     PointF pushedPos = item->ldata()->pos();
     462              : 
     463              :     // Calculate item's position without autoplace
     464            0 :     PropertyFlags autoplacePf = item->propertyFlags(Pid::AUTOPLACE);
     465            0 :     item->setProperty(Pid::AUTOPLACE, false);
     466              : 
     467            0 :     item->mutldata()->setPos(PointF());
     468            0 :     item->renderer()->layoutItem(item);
     469            0 :     PointF originPos = item->ldata()->pos();
     470              : 
     471            0 :     item->setProperty(Pid::AUTOPLACE, true);
     472            0 :     item->setPropertyFlags(Pid::AUTOPLACE, autoplacePf);
     473              : 
     474              :     // The difference is the amount autoplace moves the item
     475            0 :     const double difference = pushedPos.y() - originPos.y();
     476            0 :     if (muse::RealIsNull(difference)) {
     477              :         // No difference, no need to change the offset
     478            0 :         return;
     479              :     }
     480              : 
     481            0 :     PointF newOffset = item->offset();
     482            0 :     newOffset.ry() -= difference;
     483              : 
     484            0 :     newOffset.ry() = item->placeAbove() ? std::min(newOffset.y(), 0.0) : std::max(newOffset.y(), 0.0);
     485              : 
     486            0 :     item->setProperty(Pid::OFFSET, newOffset);
     487            0 :     if (item->propertyFlags(Pid::OFFSET) == PropertyFlags::STYLED) {
     488            0 :         item->setPropertyFlags(Pid::OFFSET, PropertyFlags::UNSTYLED);
     489              :     }
     490              : }
     491              : 
     492            0 : void EngravingCompat::migrateOffsetAfterAutoplace(MasterScore* masterScore)
     493              : {
     494            0 :     for (Score* score : masterScore->scoreList()) {
     495            0 :         score->scanElements(doMigrateOffsetAfterAutoplace);
     496            0 :     }
     497            0 : }
     498              : 
     499            0 : bool EngravingCompat::relayoutUserModifiedCrossStaffBeams(MasterScore* score)
     500              : {
     501            0 :     if (score->mscVersion() >= 440) {
     502            0 :         return false;
     503              :     }
     504            0 :     bool found = false;
     505              : 
     506            0 :     auto findBeam = [&found](ChordRest* cr) {
     507            0 :         Beam* beam = cr->beam();
     508            0 :         if (beam && beam->userModified() && beam->cross() && beam->elements().front() == cr) {
     509            0 :             found = true;
     510            0 :             beam->triggerLayout();
     511              :         }
     512            0 :     };
     513              : 
     514            0 :     for (MeasureBase* mb = score->first(); mb; mb = mb->next()) {
     515            0 :         if (!mb->isMeasure()) {
     516            0 :             continue;
     517              :         }
     518            0 :         for (Segment& seg : toMeasure(mb)->segments()) {
     519            0 :             if (!seg.isChordRestType()) {
     520            0 :                 continue;
     521              :             }
     522            0 :             for (EngravingItem* item : seg.elist()) {
     523            0 :                 if (!item) {
     524            0 :                     continue;
     525              :                 }
     526            0 :                 findBeam(toChordRest(item));
     527            0 :                 if (item->isChord()) {
     528            0 :                     for (Chord* grace : toChord(item)->graceNotes()) {
     529            0 :                         findBeam(grace);
     530              :                     }
     531              :                 }
     532              :             }
     533              :         }
     534              :     }
     535              : 
     536            0 :     return found;
     537              : }
     538              : 
     539            0 : bool EngravingCompat::setLyricLineVisibility(MasterScore* masterScore)
     540              : {
     541            0 :     bool needRelayout = false;
     542            0 :     for (Score* score : masterScore->scoreList()) {
     543            0 :         for (Spanner* sp : score->unmanagedSpanners()) {
     544            0 :             if (!sp->isLyricsLine()) {
     545            0 :                 continue;
     546              :             }
     547              : 
     548            0 :             LyricsLine* ll = toLyricsLine(sp);
     549            0 :             if (!ll->lyrics() || ll->visible() == ll->lyrics()->visible()) {
     550            0 :                 continue;
     551              :             }
     552              : 
     553            0 :             ll->setVisible(ll->lyrics()->visible());
     554            0 :             needRelayout = true;
     555              :         }
     556            0 :     }
     557              : 
     558            0 :     return needRelayout;
     559              : }
     560              : 
     561            0 : void EngravingCompat::resetAllElementsPositions(MasterScore* masterScore)
     562              : {
     563            0 :     for (Score* score : masterScore->scoreList()) {
     564            0 :         Transaction& tx = score->transactionManager()->currentOrDummyTransaction();
     565            0 :         Reset::resetAllPositions(tx, score);
     566            0 :     }
     567            0 : }
     568              : 
     569            0 : static void resetBeamOffset(EngravingItem* e)
     570              : {
     571            0 :     if (e->isBeam() && toBeam(e)->fullCross()) {
     572            0 :         e->reset();
     573              :     }
     574            0 : }
     575              : 
     576            0 : void EngravingCompat::resetAllCrossBeams(MasterScore* masterScore)
     577              : {
     578            0 :     for (Score* score : masterScore->scoreList()) {
     579            0 :         score->scanElements(resetBeamOffset);
     580            0 :     }
     581            0 : }
     582              : 
     583            0 : void EngravingCompat::applyPre400StyleCompat(MasterScore* masterScore)
     584              : {
     585            0 :     if (MScore::testMode) {
     586            0 :         return;
     587              :     }
     588              : 
     589            0 :     for (Score* score : masterScore->scoreList()) {
     590            0 :         MStyle& style = score->style();
     591            0 :         const double sp = style.spatium();
     592              : 
     593            0 :         style.set(Sid::dynamicsFontSize, 10.0);
     594            0 :         double doubleBarDistance = style.styleAbsolute(Sid::doubleBarDistance);
     595            0 :         doubleBarDistance -= style.styleAbsolute(Sid::doubleBarWidth);
     596            0 :         style.set(Sid::doubleBarDistance, doubleBarDistance / sp);
     597            0 :         double endBarDistance = style.styleAbsolute(Sid::endBarDistance);
     598            0 :         endBarDistance -= (style.styleAbsolute(Sid::barWidth) + style.styleAbsolute(Sid::endBarWidth)) / 2;
     599            0 :         style.set(Sid::endBarDistance, endBarDistance / sp);
     600            0 :         double repeatBarlineDotSeparation = style.styleAbsolute(Sid::repeatBarlineDotSeparation);
     601            0 :         double dotWidth = score->engravingFont()->width(SymId::repeatDot, 1.0);
     602            0 :         repeatBarlineDotSeparation -= (style.styleAbsolute(Sid::barWidth) + dotWidth) / 2;
     603            0 :         style.set(Sid::repeatBarlineDotSeparation, repeatBarlineDotSeparation / sp);
     604            0 :         score->resetStyleValue(Sid::measureSpacing);
     605            0 :     }
     606              : }
     607              : 
     608              : // Migrate item centring & alignment. Before 5.0 offsets to one item would move the whole aligned group
     609              : // From 5.0, offsets are applied after centring & alignment which means items can be individually offset from their group
     610              : // Replicate the appearance of <5.0 scores by giving all items in a centred or aligned group the same offset
     611              : // These functions collect the same groups of items as are collected in AlignmentLayout & SystemLayout, then set their offset to the highest in the group
     612              : 
     613            0 : bool AlignmentMigration500::rowItemIsAbove(const EngravingItem* item)
     614              : {
     615            0 :     return item->isArticulationFamily() ? toArticulation(item)->ldata()->up() : item->placeAbove();
     616              : }
     617              : 
     618            0 : void AlignmentMigration500::alignItemOffsetGroup(const std::vector<EngravingItem*>& group)
     619              : {
     620            0 :     if (group.size() < 2) {
     621            0 :         return;
     622              :     }
     623              : 
     624            0 :     bool above = rowItemIsAbove(group.front());
     625            0 :     bool outermostYOffsetFound = false;
     626            0 :     double outermostYOffset = 0.0;
     627            0 :     for (const EngravingItem* item : group) {
     628            0 :         double y = item->offset().y();
     629            0 :         if (muse::RealIsNull(y)) {
     630            0 :             continue;
     631              :         }
     632            0 :         if (!outermostYOffsetFound || (above ? y < outermostYOffset : y > outermostYOffset)) {
     633            0 :             outermostYOffset = y;
     634            0 :             outermostYOffsetFound = true;
     635              :         }
     636              :     }
     637              : 
     638            0 :     if (!outermostYOffsetFound) {
     639            0 :         return;
     640              :     }
     641              : 
     642            0 :     for (EngravingItem* item : group) {
     643            0 :         if (!muse::RealIsEqual(item->offset().y(), outermostYOffset)) {
     644            0 :             item->setProperty(Pid::OFFSET, PointF(item->offset().x(), outermostYOffset));
     645              :         }
     646              :     }
     647              : }
     648              : 
     649            0 : void AlignmentMigration500::migrateSnappedAndSameItemTypeAlignment(MasterScore* masterScore)
     650              : {
     651              :     // For items aligned by AlignmentLayout::alignItemsWithTheirSnappingChain
     652              :     // Used for dynamics, expressions, hairpins, tempo text and gradual tempo change lines
     653              :     //
     654              :     // For items aligned by AlignmentLayout::alignItemsForSystem
     655              :     // Used for harmony, fret diagrams and sticking
     656              :     using RowKey = std::tuple<const System*, staff_idx_t, bool>;
     657              : 
     658            0 :     for (Score* score : masterScore->scoreList()) {
     659            0 :         std::vector<EngravingItem*> snappedCandidates;
     660              : 
     661            0 :         std::map<RowKey, std::vector<EngravingItem*> > harmonyGroups;
     662            0 :         std::map<RowKey, std::vector<EngravingItem*> > harmonyOnFretDiagramGroups;
     663            0 :         std::map<RowKey, std::vector<EngravingItem*> > fretGroups;
     664            0 :         std::map<RowKey, std::vector<EngravingItem*> > stickingGroups;
     665              : 
     666            0 :         bool alignChordSymbols = score->style().styleB(Sid::verticallyAlignChordSymbols);
     667              : 
     668            0 :         score->scanElements([&](EngravingItem* item) {
     669            0 :             if (!item) {
     670            0 :                 return;
     671              :             }
     672              : 
     673            0 :             if (((item->isDynamic() && item->autoplace())
     674            0 :                  || (item->isExpression() && item->addToSkyline())
     675            0 :                  || item->isHairpinSegment()
     676            0 :                  || item->isTempoText()
     677            0 :                  || item->isGradualTempoChangeSegment())) {
     678            0 :                 snappedCandidates.push_back(item);
     679              :             }
     680              : 
     681            0 :             if (!item->addToSkyline() || item->excludeVerticalAlign()) {
     682            0 :                 return;
     683              :             }
     684            0 :             bool isHarmony = alignChordSymbols && item->isHarmony();
     685            0 :             bool isFretDiagram = alignChordSymbols && item->isFretDiagram();
     686            0 :             bool isSticking = item->isSticking();
     687            0 :             if (!isHarmony && !isFretDiagram && !isSticking) {
     688            0 :                 return;
     689              :             }
     690              : 
     691            0 :             const System* system = toSystem(item->findAncestor(ElementType::SYSTEM));
     692            0 :             if (!system) {
     693            0 :                 return;
     694              :             }
     695            0 :             RowKey key(system, item->staffIdx(), item->placeAbove());
     696            0 :             if (isHarmony && !toHarmony(item)->getParentFretDiagram()) {
     697            0 :                 harmonyGroups[key].push_back(item);
     698            0 :             } else if (isHarmony && toHarmony(item)->getParentFretDiagram()) {
     699            0 :                 harmonyOnFretDiagramGroups[key].push_back(item);
     700            0 :             } else if (isFretDiagram) {
     701            0 :                 fretGroups[key].push_back(item);
     702              :             } else {
     703            0 :                 stickingGroups[key].push_back(item);
     704              :             }
     705              :         });
     706              : 
     707            0 :         std::set<EngravingItem*> visited;
     708            0 :         for (EngravingItem* item : snappedCandidates) {
     709            0 :             if (muse::contains(visited, item)) {
     710            0 :                 continue;
     711              :             }
     712            0 :             const System* system = toSystem(item->findAncestor(ElementType::SYSTEM));
     713            0 :             std::vector<EngravingItem*> group;
     714            0 :             item->ldata()->scanConnectedItemsInSnappingChain(system, [&visited, &group](EngravingItem* chainItem) {
     715            0 :                 if (visited.insert(chainItem).second) {
     716            0 :                     group.push_back(chainItem);
     717              :                 }
     718            0 :             });
     719            0 :             alignItemOffsetGroup(group);
     720            0 :         }
     721              : 
     722            0 :         for (auto& pair : harmonyGroups) {
     723            0 :             alignItemOffsetGroup(pair.second);
     724              :         }
     725            0 :         for (auto& pair : harmonyOnFretDiagramGroups) {
     726            0 :             alignItemOffsetGroup(pair.second);
     727              :         }
     728            0 :         for (auto& pair : fretGroups) {
     729            0 :             alignItemOffsetGroup(pair.second);
     730              :         }
     731            0 :         for (auto& pair : stickingGroups) {
     732            0 :             alignItemOffsetGroup(pair.second);
     733              :         }
     734            0 :     }
     735            0 : }
     736              : 
     737            0 : void AlignmentMigration500::migrateHopoLetterAlignment(MasterScore* masterScore)
     738              : {
     739              :     // For items aligned by AlignmentLayout::alignHopoLetters
     740            0 :     for (Score* score : masterScore->scoreList()) {
     741            0 :         const bool alignLettersTab = score->style().styleB(Sid::hopoAlignLettersTabStaves);
     742            0 :         const bool alignLettersStd = score->style().styleB(Sid::hopoAlignLettersStandardStaves);
     743              : 
     744            0 :         for (Spanner* spanner : score->spannerList()) {
     745            0 :             if (!spanner->isHammerOnPullOff()) {
     746            0 :                 continue;
     747              :             }
     748            0 :             HammerOnPullOff* hopo = toHammerOnPullOff(spanner);
     749            0 :             const Staff* staff = hopo->staff();
     750            0 :             const StaffType* staffType = staff ? staff->staffType(hopo->tick()) : nullptr;
     751            0 :             if (!staffType) {
     752            0 :                 continue;
     753              :             }
     754            0 :             bool alignLetters = staffType->isTabStaff() ? alignLettersTab : alignLettersStd;
     755            0 :             if (!alignLetters) {
     756            0 :                 continue;
     757              :             }
     758              : 
     759            0 :             for (SpannerSegment* seg : hopo->spannerSegments()) {
     760            0 :                 if (!seg->system()) {
     761            0 :                     continue;
     762              :                 }
     763              : 
     764            0 :                 std::vector<EngravingItem*> group;
     765            0 :                 for (HammerOnPullOffText* text : toHammerOnPullOffSegment(seg)->hopoText()) {
     766            0 :                     group.push_back(text);
     767              :                 }
     768            0 :                 if (group.empty()) {
     769            0 :                     continue;
     770              :                 }
     771              : 
     772            0 :                 bool above = rowItemIsAbove(group.front());
     773            0 :                 bool itemsSamePlacement = true;
     774            0 :                 for (EngravingItem* item : group) {
     775            0 :                     if (rowItemIsAbove(item) != above) {
     776            0 :                         itemsSamePlacement = false; // Don't try to align items split above/below
     777              :                     }
     778              :                 }
     779              : 
     780            0 :                 if (!itemsSamePlacement) {
     781            0 :                     continue;
     782              :                 }
     783              : 
     784            0 :                 if (hopo->startElement() && hopo->startElement()->isChordRest()) {
     785            0 :                     ChordRest* scr = toChordRest(hopo->startElement());
     786            0 :                     if (scr->isChord()) {
     787            0 :                         for (Articulation* art : toChord(scr)->articulations()) {
     788            0 :                             if (art->isTapping() && toTapping(art)->text() && rowItemIsAbove(art) == above) {
     789            0 :                                 group.push_back(art);
     790              :                             }
     791              :                         }
     792              :                     }
     793              :                 }
     794              : 
     795            0 :                 alignItemOffsetGroup(group);
     796            0 :             }
     797            0 :         }
     798            0 :     }
     799            0 : }
     800              : } // namespace mu::engraving::compat
        

Generated by: LCOV version 2.0-1