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
|