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