Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2026 MuseScore Limited
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include "read500.h"
23 :
24 : #include "../editing/mscoreview.h"
25 : #include "../editing/noteinput.h"
26 : #include "../editing/paste.h"
27 : #include "../editing/transaction/transaction.h"
28 : #include "../editing/transpose.h"
29 : #include "../types/types.h"
30 :
31 : #include "dom/anchors.h"
32 : #include "dom/beam.h"
33 : #include "dom/breath.h"
34 : #include "dom/chord.h"
35 : #include "dom/dynamic.h"
36 : #include "dom/excerpt.h"
37 : #include "dom/factory.h"
38 : #include "dom/figuredbass.h"
39 : #include "dom/fret.h"
40 : #include "dom/harmony.h"
41 : #include "dom/lyrics.h"
42 : #include "dom/masterscore.h"
43 : #include "dom/measure.h"
44 : #include "dom/measurerepeat.h"
45 : #include "dom/note.h"
46 : #include "dom/part.h"
47 : #include "dom/score.h"
48 : #include "dom/sharedpart.h"
49 : #include "dom/spanner.h"
50 : #include "dom/staff.h"
51 : #include "dom/text.h"
52 : #include "dom/tie.h"
53 : #include "dom/tremolotwochord.h"
54 : #include "dom/tuplet.h"
55 :
56 : #include "engravingerrors.h"
57 :
58 : #include "../compat/readstyle.h"
59 : #include "../compat/tremolocompat.h"
60 : #include "../inoutdata.h"
61 : #include "staffread.h"
62 : #include "tread.h"
63 :
64 : #include "log.h"
65 :
66 : using namespace mu::engraving;
67 : using namespace mu::engraving::read500;
68 :
69 170 : muse::Ret Read500::readScoreFile(Score* score, XmlReader& e, rw::ReadInOutData* data)
70 : {
71 170 : ReadContext ctx(score);
72 170 : if (data) {
73 170 : if (data->overriddenSpatium.has_value()) {
74 0 : ctx.setSpatium(data->overriddenSpatium.value());
75 : }
76 :
77 170 : ctx.setPropertiesToSkip(data->propertiesToSkip);
78 170 : ctx.setForcePageMode(data->forcePageMode);
79 : }
80 :
81 388 : while (e.readNextStartElement()) {
82 218 : const AsciiStringView tag(e.name());
83 218 : if (tag == "programVersion") {
84 24 : String ver = e.readText();
85 24 : if (score->isMaster()) {
86 24 : score->setMscoreVersion(ver);
87 : }
88 218 : } else if (tag == "programRevision") {
89 24 : int rev = e.readInt(nullptr, 16);
90 24 : if (score->isMaster()) {
91 24 : score->setMscoreRevision(rev);
92 : }
93 170 : } else if (tag == "Score") {
94 170 : if (!readScoreTag(score, e, ctx)) {
95 0 : if (e.error() == muse::XmlStreamReader::CustomError) {
96 0 : return make_ret(Err::FileCriticallyCorrupted, e.errorString());
97 : }
98 0 : return make_ret(Err::FileBadFormat, e.errorString());
99 : }
100 0 : } else if (tag == "museScore") {
101 : // pass
102 : } else {
103 0 : e.unknown();
104 : }
105 : }
106 :
107 170 : if (!score->isMaster()) {
108 0 : Excerpt* ex = score->excerpt();
109 0 : ex->setTracksMapping(ctx.tracks());
110 : }
111 :
112 170 : if (data) {
113 170 : data->settingsCompat = ctx.settingCompat();
114 : }
115 :
116 170 : return muse::make_ok();
117 170 : }
118 :
119 238 : bool Read500::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx)
120 : {
121 238 : std::vector<int> sysStaves;
122 4357 : while (e.readNextStartElement()) {
123 4119 : ctx.setTrack(muse::nidx);
124 4119 : const AsciiStringView tag(e.name());
125 4119 : if (tag == "eid") {
126 188 : TRead::readItemEID(score, e, ctx);
127 3931 : } else if (tag == "Staff") {
128 389 : StaffRead::readStaff(score, e, ctx);
129 3542 : } else if (tag == "Synthesizer") {
130 0 : score->m_synthesizerState.read(e);
131 3542 : } else if (tag == "page-offset") {
132 0 : score->m_pageNumberOffset = e.readInt();
133 3542 : } else if (tag == "Division") {
134 238 : score->m_fileDivision = e.readInt();
135 3304 : } else if (tag == "open") {
136 32 : score->m_isOpen = e.readBool();
137 3272 : } else if (tag == "showInvisible") {
138 227 : score->m_showInvisible = e.readInt();
139 3045 : } else if (tag == "showUnprintable") {
140 227 : score->m_showUnprintable = e.readInt();
141 2818 : } else if (tag == "showFrames") {
142 227 : score->m_showFrames = e.readInt();
143 2591 : } else if (tag == "showMargins") {
144 227 : score->m_showPageborders = e.readInt();
145 2364 : } else if (tag == "showSoundFlags") {
146 0 : score->m_showSoundFlags = e.readInt();
147 2364 : } else if (tag == "markIrregularMeasures") {
148 0 : score->m_markIrregularMeasures = e.readInt();
149 2364 : } else if (tag == "Style") {
150 : // Since version 400, the Style is usually stored in a separate file,
151 : // but we also support reading it from the mscx file.
152 205 : compat::ReadStyleHook::readStyleTag(score, e);
153 205 : score->m_engravingFont = score->engravingFonts()->fontByName(score->style().styleSt(Sid::musicalSymbolFont).toStdString());
154 2159 : } else if (tag == "copyright" || tag == "rights") {
155 0 : score->setMetaTag(u"copyright", Text::readXmlText(e, score));
156 2159 : } else if (tag == "movement-number") {
157 0 : score->setMetaTag(u"movementNumber", e.readText());
158 2159 : } else if (tag == "movement-title") {
159 0 : score->setMetaTag(u"movementTitle", e.readText());
160 2159 : } else if (tag == "work-number") {
161 0 : score->setMetaTag(u"workNumber", e.readText());
162 2159 : } else if (tag == "work-title") {
163 0 : score->setMetaTag(u"workTitle", e.readText());
164 2159 : } else if (tag == "source") {
165 0 : score->setMetaTag(u"source", e.readText());
166 2159 : } else if (tag == "metaTag") {
167 1598 : String name = e.attribute("name");
168 1598 : score->setMetaTag(name, e.readText());
169 2159 : } else if (tag == "Order") {
170 33 : ScoreOrder order;
171 33 : order.read(e);
172 33 : if (order.isValid()) {
173 33 : score->setScoreOrder(order);
174 : }
175 561 : } else if (tag == "SystemObjects") {
176 0 : score->clearSystemObjectStaves();
177 0 : while (e.readNextStartElement()) {
178 0 : if (e.name() == "Instance") {
179 0 : int staffIdx = e.attribute("staffId").toInt() - 1;
180 0 : if (staffIdx > 0) {
181 0 : sysStaves.push_back(staffIdx);
182 : }
183 0 : e.skipCurrentElement();
184 : } else {
185 0 : e.skipCurrentElement();
186 : }
187 : }
188 528 : } else if (tag == "PageLocks") {
189 7 : TRead::readPageLocks(score, e);
190 521 : } else if (tag == "SystemLocks") {
191 0 : TRead::readSystemLocks(score, e);
192 521 : } else if (tag == "SystemDividers") {
193 0 : TRead::readSystemDividers(score, e, ctx);
194 521 : } else if (tag == "SpannerMap") {
195 29 : TRead::readScoreSpanners(score, e, ctx);
196 492 : } else if (tag == "Part") {
197 357 : Part* part = new Part(score);
198 357 : TRead::read(part, e, ctx);
199 357 : score->appendPart(part);
200 135 : } else if (tag == "SharedPart") {
201 1 : SharedPart* sharedPart = new SharedPart(score);
202 1 : TRead::read(sharedPart, e, ctx);
203 1 : score->appendPart(sharedPart);
204 134 : } else if (e.name() == "initialPartId") {
205 0 : if (score->excerpt()) {
206 0 : score->excerpt()->setInitialPartId(ID(e.readInt()));
207 : } else {
208 0 : e.skipCurrentElement();
209 : }
210 134 : } else if (e.name() == "Tracklist") {
211 0 : int strack = e.intAttribute("sTrack", -1);
212 0 : int dtrack = e.intAttribute("dstTrack", -1);
213 0 : if (strack != -1 && dtrack != -1) {
214 0 : ctx.tracks().insert({ strack, dtrack });
215 : }
216 0 : e.skipCurrentElement();
217 134 : } else if (tag == "Score") {
218 : // Since version 400, Excerpts are usually stored in separate files,
219 : // but we also support reading them from the main mscx file.
220 :
221 68 : ctx.tracks().clear(); // ???
222 68 : MasterScore* m = score->masterScore();
223 68 : Score* s = m->createScore();
224 :
225 68 : compat::ReadStyleHook::setupDefaultStyle(s);
226 :
227 68 : Excerpt* ex = new Excerpt(m);
228 68 : ex->setExcerptScore(s);
229 68 : ctx.setLastMeasure(nullptr);
230 :
231 68 : Score* curScore = ctx.score();
232 68 : ctx.setScore(s);
233 :
234 68 : readScoreTag(s, e, ctx); // recursion
235 :
236 68 : ctx.setScore(curScore);
237 :
238 68 : Excerpt::linkMeasures(s, m);
239 68 : ex->setTracksMapping(ctx.tracks());
240 68 : m->addExcerpt(ex);
241 66 : } else if (tag == "name") {
242 66 : String n = e.readText();
243 66 : if (!score->isMaster()) { //ignore the name if it's not a child score
244 66 : score->excerpt()->setName(n, /*saveAndNotify=*/ false);
245 : }
246 66 : } else if (tag == "layoutMode") {
247 0 : if (ctx.forcePageMode()) {
248 0 : e.skipCurrentElement();
249 0 : continue;
250 : }
251 0 : String s = e.readText();
252 0 : if (s == "line") {
253 0 : score->setLayoutMode(LayoutMode::LINE);
254 0 : } else if (s == "system") {
255 0 : score->setLayoutMode(LayoutMode::SYSTEM);
256 : } else {
257 0 : LOGD("layoutMode: %s", muPrintable(s));
258 : }
259 0 : } else {
260 0 : e.unknown();
261 : }
262 : }
263 238 : ctx.setMMRestEndMeasures();
264 238 : ctx.connectNoteAnchoredSpanners();
265 238 : if (e.error() != muse::XmlStreamReader::NoError) {
266 0 : if (e.error() == muse::XmlStreamReader::CustomError) {
267 0 : LOGE() << e.errorString();
268 : } else {
269 0 : LOGE() << String(u"XML read error at byte offset %1: %2").arg(e.byteOffset())
270 0 : .arg(String::fromAscii(e.name().ascii()));
271 : }
272 0 : return false;
273 : }
274 :
275 238 : for (Spanner* sp : score->unmanagedSpanners()) {
276 0 : if (sp->isLyricsLine() && toLyricsLine(sp)->isDash()) {
277 0 : LyricsLine* line = toLyricsLine(sp);
278 0 : line->setNextLyrics(searchNextLyrics(line->lyrics()->segment(),
279 : line->staffIdx(),
280 0 : line->lyrics()->verse(),
281 0 : line->lyrics()->placement()
282 : ));
283 0 : line->setTrack2(line->nextLyrics() ? line->nextLyrics()->track() : line->track());
284 : }
285 : }
286 :
287 238 : score->m_fileDivision = Constants::DIVISION;
288 :
289 : // Make sure every instrument has an instrumentId set.
290 596 : for (Part* part : score->parts()) {
291 721 : for (const auto& pair : part->instruments()) {
292 363 : pair.second->updateInstrumentId();
293 : }
294 : }
295 :
296 238 : score->updateTicksAndTimeSigMap();
297 238 : if (score->isMaster()) {
298 : // While reading the score, some elements might use `score->repeatList()` (which is incorrect
299 : // anyway, because the repeatList will be incomplete because the score is incomplete, but some
300 : // elements still do it).
301 : // `score->repeatList()` calls `_repeatList->update()`; the repeat list then thinks that it is
302 : // up-to-date from that point. But we weren't finished reading the score, so the score will still
303 : // change. We need to tell the repeat list about that, so that it will be updated next time
304 : // someone uses it.
305 170 : static_cast<MasterScore*>(score)->invalidateRepeatList();
306 : }
307 238 : score->connectTies();
308 238 : score->undoRemoveStaleTieJumpPoints(false);
309 :
310 596 : for (Part* p : score->parts()) {
311 358 : p->updateHarmonyChannels(false);
312 : }
313 :
314 238 : score->masterScore()->rebuildMidiMapping();
315 238 : score->masterScore()->updateChannel();
316 :
317 627 : for (Staff* staff : score->staves()) {
318 389 : staff->updateOttava();
319 : }
320 238 : for (int idx : sysStaves) {
321 0 : score->addSystemObjectStaff(score->staff(idx));
322 : }
323 :
324 238 : return true;
325 238 : }
326 :
327 362 : bool Read500::preparePasteDurationElement(Score* score, const Fraction& tick, const Fraction& ticks, const track_idx_t track)
328 : {
329 362 : Measure* destinationMeasure = score->undoGetMeasure(tick);
330 362 : IF_ASSERT_FAILED(destinationMeasure) {
331 0 : return false;
332 : }
333 :
334 362 : Segment* pasteDestinationSeg = destinationMeasure->undoGetSegment(SegmentType::ChordRest, tick);
335 362 : IF_ASSERT_FAILED(pasteDestinationSeg) {
336 0 : return false;
337 : }
338 :
339 : // First make a gap for as long as we need...
340 362 : IF_ASSERT_FAILED(score->makeGapVoice(pasteDestinationSeg, track, ticks, tick, /*deleteAnnotations*/ false)) {
341 0 : return false;
342 : }
343 :
344 : // And shorten any segments that overlap with our destination...
345 362 : if (Segment* leftSeg = score->tick2leftSegment(tick)) {
346 317 : ChordRest* prevCr = leftSeg->nextChordRest(track, /*backwards*/ true, /*stopAtMeasureBoundary*/ true);
347 317 : if (prevCr && prevCr->endTick() > tick) {
348 0 : NoteInput::truncateChordRest(
349 : score->transactionManager()->currentOrDummyTransaction(), score, prevCr, tick, /*fillWithRest*/ false);
350 : }
351 : }
352 :
353 362 : return true;
354 : }
355 :
356 42 : bool Read500::pasteStaff(XmlReader& e, Segment* dst, staff_idx_t dstStaff, Fraction scale)
357 : {
358 42 : assert(dst->isType(SegmentType::Duration));
359 :
360 42 : Score* score = dst->score();
361 42 : Transaction& tx = score->transactionManager()->currentOrDummyTransaction();
362 :
363 42 : ReadContext ctx(score, /* pasteMode = */ true);
364 :
365 42 : std::vector<Harmony*> pastedHarmony;
366 42 : std::vector<Chord*> graceNotes;
367 42 : Beam* startingBeam = nullptr;
368 42 : Tuplet* tuplet = nullptr;
369 42 : TremoloTwoChord* prevTremolo = nullptr;
370 42 : Fraction dstTick = dst->tick();
371 42 : bool pasted = false;
372 42 : Fraction tickLen = Fraction(0, 1);
373 42 : int staves = 0;
374 42 : bool done = false;
375 42 : bool doScale = (scale != Fraction(1, 1));
376 :
377 84 : while (e.readNextStartElement()) {
378 42 : if (done) {
379 0 : break;
380 : }
381 42 : if (e.name() != "StaffList") {
382 0 : e.unknown();
383 0 : break;
384 : }
385 42 : String version = e.attribute("version", u"NONE");
386 42 : if (!MScore::testMode) {
387 0 : if (version != Constants::MSC_VERSION_STR) {
388 0 : LOGD("pasteStaff: bad version");
389 0 : break;
390 : }
391 : }
392 42 : Fraction tickStart = Fraction::fromString(e.attribute("tick"));
393 42 : Fraction oTickLen = Fraction::fromString(e.attribute("len"));
394 42 : Fraction timeStretch = e.hasAttribute("timeStretch") ? Fraction::fromString(e.attribute("timeStretch")) : Fraction(1, 1);
395 42 : tickLen = oTickLen * scale;
396 42 : int staffStart = e.intAttribute("staff", 0);
397 42 : staves = e.intAttribute("staves", 0);
398 :
399 42 : if (tickLen.isZero() || staves == 0) {
400 0 : break;
401 : }
402 42 : if (doScale) {
403 0 : Fraction tickLenStretched = tickLen * timeStretch;
404 0 : if (!TDuration(tickLenStretched).isValid()) {
405 0 : LOGD("Can't paste: invalid duration %d/%d", tickLenStretched.numerator(), tickLenStretched.denominator());
406 0 : return false;
407 : }
408 : }
409 :
410 42 : Fraction oEndTick = dstTick + oTickLen;
411 42 : auto oSpanner = score->spannerMap().findContained(dstTick.ticks(), oEndTick.ticks());
412 42 : bool spannerFound = false;
413 :
414 42 : ctx.setTickOffset(dstTick - tickStart);
415 42 : ctx.setTick(Fraction(0, 1));
416 :
417 100 : while (e.readNextStartElement()) {
418 58 : if (done) {
419 0 : break;
420 : }
421 58 : if (e.name() == "SpannerMap") {
422 13 : spannerFound = true;
423 13 : TRead::readScoreSpanners(score, e, ctx);
424 13 : continue;
425 : }
426 45 : if (e.name() != "Staff") {
427 0 : e.unknown();
428 0 : break;
429 : }
430 45 : ctx.setTransposeChromatic(0);
431 45 : ctx.setTransposeDiatonic(0);
432 :
433 45 : int srcStaffIdx = e.intAttribute("id", 0);
434 45 : ctx.setTrack(srcStaffIdx * static_cast<int>(VOICES));
435 45 : ctx.setTrackOffset(static_cast<int>((dstStaff - staffStart) * VOICES));
436 45 : size_t dstStaffIdx = ctx.track() / VOICES;
437 45 : if (dstStaffIdx >= dst->score()->nstaves()) {
438 0 : LOGD("paste beyond staves");
439 0 : done = true;
440 0 : break;
441 : }
442 45 : if (dst->isInsideTupletOnStaff(dstStaffIdx)) {
443 0 : done = true;
444 0 : break;
445 : }
446 : // Check the time stretch for all measures overlapping the destination range.
447 125 : for (Measure* m = dst->measure(); m && m->tick() < oEndTick; m = m->nextMeasure()) {
448 80 : Fraction mTimeStretch = dst->score()->staff(dstStaffIdx)->timeStretch(m->tick());
449 80 : if (mTimeStretch != timeStretch) {
450 0 : LOGD(
451 : "Can't paste due to different time stretch ratios (src time stretch: %d/%d, dst time stretch: %d/%d)",
452 : timeStretch.numerator(), timeStretch.denominator(), mTimeStretch.numerator(), mTimeStretch.denominator());
453 0 : MScore::setError(MsError::DEST_LOCAL_TIME_SIGNATURE);
454 0 : return false;
455 : }
456 : }
457 :
458 795 : while (e.readNextStartElement()) {
459 750 : pasted = true;
460 750 : const AsciiStringView tag(e.name());
461 :
462 750 : if (tag == "transposeChromatic") {
463 3 : ctx.setTransposeChromatic(static_cast<int8_t>(e.readInt()));
464 747 : } else if (tag == "transposeDiatonic") {
465 3 : ctx.setTransposeDiatonic(static_cast<int8_t>(e.readInt()));
466 744 : } else if (tag == "location") {
467 130 : Location loc = Location::relative();
468 130 : TRead::read(&loc, e, ctx);
469 130 : ctx.setLocation(loc);
470 130 : if (loc.isTimeTick()) {
471 0 : Measure* measure = score->tick2measure(ctx.tick());
472 0 : EditTimeTickAnchors::createTimeTickAnchor(measure, ctx.tick() - measure->tick(), track2staff(ctx.track()));
473 : }
474 614 : } else if (tag == "Tuplet") {
475 45 : Tuplet* oldTuplet = tuplet;
476 45 : Fraction tick = doScale ? (ctx.tick() - dstTick) * scale + dstTick : ctx.tick();
477 45 : Measure* measure = score->tick2measure(tick);
478 45 : tuplet = Factory::createTuplet(measure);
479 45 : tuplet->setTrack(ctx.track());
480 45 : TRead::read(tuplet, e, ctx);
481 45 : if (doScale) {
482 0 : Fraction ticksScaled = tuplet->ticks() * scale;
483 0 : if (!TDuration(ticksScaled).isValid()) {
484 0 : LOGD("Can't paste: invalid duration %d/%d", ticksScaled.numerator(), ticksScaled.denominator());
485 0 : return false;
486 : }
487 0 : tuplet->setTicks(ticksScaled);
488 0 : tuplet->setBaseLen(tuplet->baseLen().fraction() * scale);
489 : }
490 45 : tuplet->setOwnershipParent(measure);
491 45 : tuplet->setTick(tick);
492 45 : tuplet->setTuplet(oldTuplet);
493 45 : if (tuplet->rtick() + tuplet->actualTicks() > measure->ticks()) {
494 0 : delete tuplet;
495 0 : if (oldTuplet && oldTuplet->elements().empty()) {
496 0 : delete oldTuplet;
497 : }
498 0 : MScore::setError(MsError::TUPLET_CROSSES_BAR);
499 0 : return false;
500 : }
501 45 : if (!tuplet->tuplet()) {
502 25 : IF_ASSERT_FAILED(preparePasteDurationElement(score, tick, tuplet->actualTicksAt(tick), tuplet->track())) {
503 0 : e.skipCurrentElement();
504 0 : continue;
505 : }
506 : }
507 45 : if (oldTuplet) {
508 20 : tuplet->readAddTuplet(oldTuplet);
509 : }
510 569 : } else if (tag == "endTuplet") {
511 45 : if (!tuplet) {
512 0 : LOGD("Score::pasteStaff: encountered <endTuplet/> when no tuplet was started");
513 0 : e.skipCurrentElement();
514 0 : continue;
515 : }
516 45 : Tuplet* oldTuplet = tuplet;
517 45 : tuplet = tuplet->tuplet();
518 45 : if (oldTuplet->elements().empty()) {
519 0 : LOGD("Score::pasteStaff: ended tuplet is empty");
520 0 : if (tuplet) {
521 0 : tuplet->remove(oldTuplet);
522 : }
523 0 : delete oldTuplet;
524 : } else {
525 45 : oldTuplet->sortElements();
526 : }
527 45 : e.readNext();
528 524 : } else if (tag == "Chord" || tag == "Rest" || tag == "MeasureRepeat") {
529 463 : ChordRest* cr = toChordRest(Factory::createItemByName(tag, score->dummy()));
530 463 : cr->setTrack(ctx.track());
531 463 : TRead::readItem(cr, e, ctx);
532 463 : cr->setSelected(false);
533 463 : Fraction tick = doScale ? (ctx.tick() - dstTick) * scale + dstTick : ctx.tick();
534 463 : if (score->tick2measure(tick)->isMeasureRepeatGroup(dstStaffIdx)) {
535 0 : MeasureRepeat* mr = score->tick2measure(tick)->measureRepeatElement(dstStaffIdx);
536 0 : score->deleteItem(mr); // resets any measures related to mr
537 : }
538 463 : if (startingBeam) {
539 38 : startingBeam->add(cr); // also calls cr->setBeam(startingBeam)
540 38 : startingBeam = nullptr;
541 : }
542 463 : if (cr->isGrace()) {
543 4 : graceNotes.push_back(toChord(cr));
544 : } else {
545 459 : if (tuplet) {
546 122 : cr->readAddTuplet(tuplet);
547 : }
548 459 : ctx.incTick(cr->actualTicksAt(tick));
549 459 : if (doScale) {
550 0 : Fraction ticksScaled = cr->ticks() * scale;
551 0 : if (!TDuration(ticksScaled).isValid()) {
552 0 : LOGD("Can't paste: invalid duration %d/%d", ticksScaled.numerator(), ticksScaled.denominator());
553 0 : return false;
554 : }
555 0 : cr->setTicks(ticksScaled);
556 0 : cr->setDurationType(cr->durationTypeTicks() * scale);
557 0 : for (Lyrics* l : cr->lyrics()) {
558 0 : l->setTicks(l->ticks() * scale);
559 : }
560 : }
561 459 : if (cr->isChord()) {
562 322 : Chord* chord = toChord(cr);
563 : // disallow tie across barline within two-note tremolo
564 : // tremolos can potentially still straddle the barline if no tie is required
565 : // but these will be removed later
566 :
567 322 : if (chord->tremoloTwoChord()) {
568 1 : prevTremolo = chord->tremoloTwoChord();
569 1 : prevTremolo->setChord1(chord);
570 1 : chord->setTremoloTwoChord(prevTremolo);
571 321 : } else if (!chord->tremoloTwoChord() && prevTremolo) {
572 1 : prevTremolo->setChord2(chord);
573 1 : chord->setTremoloTwoChord(prevTremolo);
574 1 : prevTremolo = nullptr;
575 : }
576 :
577 322 : TremoloTwoChord* tremolo = chord->tremoloTwoChord();
578 322 : if (tremolo && chord == tremolo->chord2()) {
579 1 : if (doScale) {
580 0 : Fraction ticksScaled = tremolo->durationType().ticks() * scale;
581 0 : if (!TDuration(ticksScaled).isValid()) {
582 0 : LOGD("Can't paste: invalid duration %d/%d", ticksScaled.numerator(), ticksScaled.denominator());
583 0 : return false;
584 : }
585 0 : tremolo->setDurationType(ticksScaled);
586 : }
587 1 : Fraction tremoloEndTick = tick + chord->actualTicksAt(tick);
588 1 : Fraction measureEndTick = score->tick2measure(tick)->endTick();
589 1 : if (tremoloEndTick > measureEndTick) {
590 0 : MScore::setError(MsError::DEST_TREMOLO);
591 0 : return false;
592 : }
593 : }
594 326 : for (size_t i = 0; i < graceNotes.size(); ++i) {
595 4 : Chord* gc = graceNotes.at(i);
596 4 : gc->setGraceIndex(i);
597 4 : if (gc->vStaffIdx() >= gc->score()->nstaves()) {
598 : // check if staffMove moves a note to a
599 : // nonexistent staff
600 0 : gc->setStaffMove(0);
601 : }
602 4 : Transpose::transposeChord(gc, tick);
603 4 : chord->add(gc);
604 : }
605 322 : graceNotes.clear();
606 : }
607 : // delete pending ties, they are not selected when copy
608 459 : if ((tick - dstTick) + cr->actualTicksAt(tick) >= tickLen) {
609 55 : if (cr->isChord()) {
610 49 : Chord* c = toChord(cr);
611 115 : for (Note* note: c->notes()) {
612 66 : Tie* tie = note->tieForNonPartial();
613 66 : if (tie) {
614 0 : note->setTieFor(0);
615 0 : delete tie;
616 : }
617 : }
618 : }
619 : }
620 : // shorten last cr to fit in the space made by makeGap
621 459 : if ((tick - dstTick) + cr->actualTicksAt(tick) > tickLen) {
622 0 : Fraction newLength = tickLen - (tick - dstTick);
623 : // check previous CR on same track, if it has tremolo, delete the tremolo
624 : // we don't want a tremolo and two different chord durations
625 0 : if (cr->isChord()) {
626 0 : Segment* s = score->tick2leftSegment(tick - Fraction::eps());
627 0 : if (s) {
628 0 : ChordRest* crt = toChordRest(s->element(cr->track()));
629 0 : if (!crt) {
630 0 : crt = s->nextChordRest(cr->track(), true);
631 : }
632 0 : if (crt && crt->isChord()) {
633 0 : Chord* chrt = toChord(crt);
634 0 : TremoloTwoChord* tr = chrt->tremoloTwoChord();
635 0 : if (tr) {
636 0 : tr->setChords(chrt, toChord(cr));
637 0 : chrt->remove(tr);
638 0 : delete tr;
639 : }
640 : }
641 : }
642 : }
643 0 : if (!cr->tuplet()) {
644 : // shorten duration
645 : // exempt notes in tuplets, since we don't allow copy of partial tuplet anyhow
646 : // TODO: figure out a reasonable fudge factor to make sure shorten tuplets appropriately if we do ever copy a partial tuplet
647 0 : cr->setTicks(newLength * timeStretch);
648 0 : cr->setDurationType(newLength * timeStretch);
649 : }
650 : }
651 459 : if (!cr->tuplet()) {
652 337 : IF_ASSERT_FAILED(preparePasteDurationElement(score, tick, cr->actualTicksAt(tick), cr->track())) {
653 0 : e.skipCurrentElement();
654 0 : continue;
655 : }
656 : }
657 459 : Paste::pasteChordRest(tx, score, cr, tick);
658 : }
659 61 : } else if (tag == "Harmony") {
660 : // transpose
661 6 : Fraction tick = doScale ? (ctx.tick() - dstTick) * scale + dstTick : ctx.tick();
662 6 : Measure* m = score->tick2measure(tick);
663 6 : Segment* seg = m->undoGetSegment(SegmentType::ChordRest, tick);
664 6 : Harmony* harmony = Factory::createHarmony(seg);
665 6 : harmony->setTrack(ctx.track());
666 6 : TRead::read(harmony, e, ctx);
667 6 : harmony->setTrack(ctx.track());
668 :
669 6 : Staff* staffDest = score->staff(ctx.track() / VOICES);
670 6 : Interval interval = staffDest->transpose(tick);
671 6 : if (!ctx.style().styleB(Sid::concertPitch) && !interval.isZero()) {
672 0 : interval.flip();
673 0 : Transpose::undoTransposeHarmony(tx, harmony, interval);
674 : }
675 :
676 : // remove pre-existing chords on this track
677 : // but be sure not to remove any we just added
678 11 : for (EngravingItem* el : seg->findAnnotations(ElementType::HARMONY, ctx.track(), ctx.track())) {
679 5 : if (std::find(pastedHarmony.begin(), pastedHarmony.end(), el) == pastedHarmony.end()) {
680 5 : score->undoRemoveElement(el);
681 : }
682 6 : }
683 6 : harmony->setOwnershipParent(seg);
684 6 : score->undoAddElement(harmony);
685 6 : pastedHarmony.push_back(harmony);
686 55 : } else if (tag == "Dynamic"
687 48 : || tag == "Expression"
688 48 : || tag == "Symbol"
689 48 : || tag == "FretDiagram"
690 48 : || tag == "TremoloBar"
691 48 : || tag == "Marker"
692 48 : || tag == "Jump"
693 48 : || tag == "Image"
694 48 : || tag == "Text"
695 48 : || tag == "StaffText"
696 48 : || tag == "PlayTechAnnotation"
697 48 : || tag == "Capo"
698 48 : || tag == "StringTunings"
699 48 : || tag == "TempoText"
700 48 : || tag == "FiguredBass"
701 48 : || tag == "Sticking"
702 48 : || tag == "Fermata"
703 103 : || tag == "HarpPedalDiagram"
704 : ) {
705 8 : EngravingItem* el = Factory::createItemByName(tag, score->dummy());
706 8 : el->setTrack(ctx.track()); // a valid track might be necessary for el->read() to work
707 8 : if (el->isFermata()) {
708 1 : el->setPlacement(el->track() & 1 ? PlacementV::BELOW : PlacementV::ABOVE);
709 : }
710 8 : TRead::readItem(el, e, ctx);
711 :
712 8 : Fraction tick = doScale ? (ctx.tick() - dstTick) * scale + dstTick : ctx.tick();
713 8 : Measure* m = score->tick2measure(tick);
714 8 : Segment* seg = el->isFretDiagram()
715 8 : ? m->undoGetSegment(SegmentType::ChordRest, tick)
716 8 : : m->undoGetChordRestOrTimeTickSegment(tick);
717 8 : el->setOwnershipParent(seg);
718 :
719 : // be sure to paste the element in the destination track;
720 : // setting track needs to be repeated, as it might have been overwritten by el->read()
721 : // preserve *voice* from source, though
722 8 : el->setStaffIdx(ctx.track() / VOICES);
723 8 : score->undoAddElement(el);
724 47 : } else if (tag == "Clef") {
725 2 : Fraction tick = doScale ? (ctx.tick() - dstTick) * scale + dstTick : ctx.tick();
726 2 : Measure* m = score->tick2measure(tick);
727 2 : if (m->tick().isNotZero() && m->tick() == tick) {
728 0 : m = m->prevMeasure();
729 : }
730 2 : Segment* segment = m->undoGetSegment(SegmentType::Clef, tick);
731 2 : Clef* clef = Factory::createClef(segment);
732 2 : TRead::read(clef, e, ctx);
733 2 : clef->setTrack(ctx.track());
734 2 : score->undoChangeElement(segment->element(ctx.track()), clef);
735 45 : } else if (tag == "Breath") {
736 1 : Fraction tick = doScale ? (ctx.tick() - dstTick) * scale + dstTick : ctx.tick();
737 1 : Measure* m = score->tick2measure(tick);
738 1 : if (m->tick() == tick) {
739 0 : m = m->prevMeasure();
740 : }
741 1 : Segment* segment = m->undoGetSegment(SegmentType::Breath, tick);
742 1 : Breath* breath = Factory::createBreath(segment);
743 1 : breath->setTrack(ctx.track());
744 1 : breath->setPlacement(breath->track() & 1 ? PlacementV::BELOW : PlacementV::ABOVE);
745 1 : TRead::read(breath, e, ctx);
746 1 : score->undoChangeElement(segment->element(ctx.track()), breath);
747 44 : } else if (tag == "Beam") {
748 38 : Beam* beam = Factory::createBeam(score);
749 38 : beam->setTrack(ctx.track());
750 38 : TRead::read(beam, e, ctx);
751 38 : if (startingBeam) {
752 0 : LOGD("The read beam was not used");
753 0 : delete startingBeam;
754 : }
755 38 : startingBeam = beam;
756 6 : } else if (tag == "BarLine") {
757 1 : e.skipCurrentElement(); // ignore bar line
758 : } else {
759 5 : LOGD("PasteStaff: element %s not handled", tag.ascii());
760 5 : e.skipCurrentElement(); // ignore
761 : }
762 : }
763 :
764 45 : if (startingBeam) {
765 0 : LOGD("The read beam was not used");
766 0 : delete startingBeam;
767 : }
768 45 : if (tuplet) {
769 0 : LOGD("<endTuplet/> not found");
770 0 : if (tuplet->elements().empty()) {
771 0 : if (tuplet->tuplet()) {
772 0 : tuplet->tuplet()->remove(tuplet);
773 : }
774 0 : delete tuplet;
775 : }
776 : }
777 : }
778 : // fix up spanners
779 42 : if (doScale && spannerFound) {
780 : // build list of original spanners
781 0 : std::vector<Spanner*> oSpannerList;
782 0 : for (auto interval : oSpanner) {
783 0 : Spanner* sp = interval.value;
784 0 : oSpannerList.push_back(sp);
785 : }
786 0 : auto nSpanner = score->spannerMap().findContained(dstTick.ticks(), oEndTick.ticks());
787 0 : for (auto interval : nSpanner) {
788 0 : Spanner* sp = interval.value;
789 : // skip if not in this staff list
790 0 : if (sp->staffIdx() < dstStaff || sp->staffIdx() >= dstStaff + staves) {
791 0 : continue;
792 : }
793 : // CHORDREST and NOTE spanners are normally handled already
794 0 : if (sp->anchor() == Spanner::Anchor::CHORDREST || sp->anchor() == Spanner::Anchor::NOTE) {
795 0 : continue;
796 : }
797 : // skip if present originally
798 0 : auto i = std::find(oSpannerList.begin(), oSpannerList.end(), sp);
799 0 : if (i != oSpannerList.end()) {
800 0 : continue;
801 : }
802 0 : Fraction tick = (sp->tick() - dstTick) * scale + dstTick;
803 0 : sp->undoChangeProperty(Pid::SPANNER_TICK, tick);
804 0 : sp->undoChangeProperty(Pid::SPANNER_TICKS, sp->ticks() * scale);
805 : }
806 0 : }
807 42 : }
808 :
809 42 : ctx.connectNoteAnchoredSpanners();
810 :
811 85 : for (Score* s : score->scoreList()) { // for all parts
812 43 : s->connectTies();
813 43 : s->undoRemoveStaleTieJumpPoints(false);
814 :
815 43 : for (Spanner* sp : score->unmanagedSpanners()) {
816 0 : if (sp->isLyricsLine() && toLyricsLine(sp)->isDash()) {
817 0 : LyricsLine* line = toLyricsLine(sp);
818 0 : line->setNextLyrics(searchNextLyrics(line->lyrics()->segment(),
819 : line->staffIdx(),
820 0 : line->lyrics()->verse(),
821 0 : line->lyrics()->placement()
822 : ));
823 0 : line->setTrack2(line->nextLyrics() ? line->nextLyrics()->track() : line->track());
824 : }
825 : }
826 42 : }
827 :
828 42 : if (pasted) { //select only if we pasted something
829 42 : staff_idx_t endStaff = dstStaff + staves;
830 42 : if (endStaff > score->nstaves()) {
831 0 : endStaff = score->nstaves();
832 : }
833 :
834 42 : if (score->cmdState().layoutRange()) {
835 42 : score->setLayout(dstTick, dstTick + tickLen, dstStaff, endStaff, dst);
836 : }
837 :
838 : //TODO: look if this could be done different
839 42 : Measure* dstM = score->tick2measure(dstTick);
840 42 : Measure* endM = score->tick2measure(dstTick + tickLen);
841 87 : for (staff_idx_t i = dstStaff; i < endStaff; i++) {
842 135 : for (Measure* m = dstM; m && m != endM->nextMeasure(); m = m->nextMeasure()) {
843 90 : m->checkMeasure(i);
844 : }
845 : }
846 42 : score->m_selection.setRangeTicks(dstTick, dstTick + tickLen, dstStaff, endStaff);
847 :
848 : //finding the first element that has a track
849 : //the canvas position will be set to this element
850 42 : EngravingItem* el = nullptr;
851 42 : Segment* s = score->tick2segmentMM(dstTick);
852 42 : Segment* s2 = score->tick2segmentMM(dstTick + tickLen);
853 42 : bool found = false;
854 42 : if (s2) {
855 41 : s2 = s2->next1MM();
856 : }
857 84 : while (!found && s != s2) {
858 42 : for (size_t i = dstStaff * VOICES; i < (endStaff + 1) * VOICES; i++) {
859 42 : el = s->element(i);
860 42 : if (el) {
861 42 : found = true;
862 42 : break;
863 : }
864 : }
865 42 : s = s->next1MM();
866 : }
867 :
868 42 : for (MuseScoreView* v : score->m_viewer) {
869 0 : v->adjustCanvasPosition(el);
870 : }
871 42 : if (!score->selection().isRange()) {
872 0 : score->m_selection.setState(SelState::RANGE);
873 : }
874 : }
875 42 : return true;
876 42 : }
877 :
878 11 : void Read500::pasteSymbols(XmlReader& e, ChordRest* dst)
879 : {
880 11 : Score* score = dst->score();
881 11 : Transaction& tx = score->transactionManager()->currentOrDummyTransaction();
882 :
883 11 : ReadContext ctx(score, /* pasteMode = */ true);
884 :
885 11 : Segment* currSegm = dst->segment();
886 11 : Fraction destTick = Fraction(0, 1); // the tick and track to place the pasted element at
887 11 : track_idx_t destTrack = 0;
888 11 : bool done = false;
889 11 : int segDelta = 0;
890 11 : Segment* startSegm = currSegm;
891 11 : Fraction startTick = dst->tick(); // the initial tick and track where to start pasting
892 11 : track_idx_t startTrack = dst->track();
893 11 : track_idx_t maxTrack = score->ntracks();
894 :
895 22 : while (e.readNextStartElement()) {
896 11 : if (done) {
897 0 : break;
898 : }
899 11 : if (e.name() != "SymbolList") {
900 0 : e.unknown();
901 0 : break;
902 : }
903 11 : String version = e.attribute("version", u"NONE");
904 11 : if (version != Constants::MSC_VERSION_STR) {
905 0 : break;
906 : }
907 :
908 350 : while (e.readNextStartElement()) {
909 339 : if (done) {
910 0 : break;
911 : }
912 339 : const AsciiStringView tag(e.name());
913 :
914 339 : if (tag == "trackOffset") {
915 12 : destTrack = startTrack + e.readInt();
916 12 : currSegm = startSegm;
917 327 : } else if (tag == "tickOffset") {
918 109 : destTick = startTick + Fraction::fromTicks(e.readInt());
919 218 : } else if (tag == "segDelta") {
920 109 : segDelta = e.readInt();
921 109 : } else if (tag == "Articulation"
922 80 : || tag == "Ornament"
923 75 : || tag == "Arpeggio"
924 189 : || tag == "TremoloSingleChord") {
925 : // Elements that can be attached only to a Chord
926 55 : if (destTrack >= maxTrack) {
927 0 : LOGD() << "No track for " << tag;
928 0 : e.skipCurrentElement();
929 13 : continue;
930 : }
931 55 : Measure* meas = score->tick2measure(destTick);
932 55 : Segment* seg = meas ? meas->undoGetSegment(SegmentType::ChordRest, destTick) : nullptr;
933 55 : if (!seg) {
934 0 : LOGD() << "No ChordRest segment for " << tag << " at tick " << destTick.ticks();
935 0 : e.skipCurrentElement();
936 0 : continue;
937 : }
938 :
939 55 : ChordRest* cr = seg->cr(destTrack);
940 55 : if (!cr || !cr->isChord()) {
941 13 : LOGD() << "No Chord for " << tag << " at tick " << destTick.ticks();
942 13 : e.skipCurrentElement();
943 13 : continue;
944 : }
945 :
946 42 : EngravingItem* el = Factory::createItemByName(tag, score->dummy());
947 42 : el->setTrack(destTrack);
948 42 : TRead::readItem(el, e, ctx);
949 42 : el->setTrack(destTrack);
950 42 : el->setOwnershipParent(cr);
951 42 : score->undoAddElement(el);
952 54 : } else if (tag == "Fermata") {
953 0 : if (destTrack >= maxTrack) {
954 0 : LOGD() << "No track for Fermata";
955 0 : e.skipCurrentElement();
956 0 : continue;
957 : }
958 0 : Measure* meas = score->tick2measure(destTick);
959 0 : Segment* seg = meas ? meas->undoGetSegment(SegmentType::ChordRest, destTick) : nullptr;
960 0 : if (!seg) {
961 0 : LOGD() << "No ChordRest segment for Fermata at tick " << destTick.ticks();
962 0 : e.skipCurrentElement();
963 0 : continue;
964 : }
965 :
966 0 : Fermata* b = Factory::createFermata(score->dummy());
967 0 : b->setTrack(destTrack);
968 0 : TRead::read(b, e, ctx);
969 0 : b->setTrack(destTrack);
970 0 : b->setOwnershipParent(seg);
971 0 : score->undoAddElement(b);
972 54 : } else if (tag == "Breath") {
973 0 : if (destTrack >= maxTrack) {
974 0 : LOGD() << "No track for Breath";
975 0 : e.skipCurrentElement();
976 0 : continue;
977 : }
978 0 : Measure* meas = score->tick2measure(destTick);
979 0 : Segment* seg = meas ? meas->undoGetSegment(SegmentType::Breath, destTick) : nullptr;
980 0 : if (!seg) {
981 0 : LOGD() << "No Breath segment for Breath at tick " << destTick.ticks();
982 0 : e.skipCurrentElement();
983 0 : continue;
984 : }
985 :
986 0 : Breath* b = Factory::createBreath(score->dummy());
987 0 : b->setTrack(destTrack);
988 0 : TRead::read(b, e, ctx);
989 0 : b->setTrack(destTrack);
990 0 : b->setOwnershipParent(seg);
991 0 : score->undoAddElement(b);
992 54 : } else if (tag == "Dynamic"
993 54 : || tag == "Expression"
994 54 : || tag == "StaffText"
995 39 : || tag == "PlayTechAnnotation"
996 39 : || tag == "Sticking"
997 24 : || tag == "Capo"
998 24 : || tag == "HarpPedalDiagram"
999 108 : || tag == "StringTunings") {
1000 30 : if (destTrack >= maxTrack) {
1001 0 : LOGD() << "No track for " << tag;
1002 0 : e.skipCurrentElement();
1003 0 : continue;
1004 : }
1005 : // Text elements that can be attached to ChordRest or TimeTick segments
1006 30 : Measure* meas = score->tick2measure(destTick);
1007 30 : Segment* seg = meas ? meas->undoGetChordRestOrTimeTickSegment(destTick) : nullptr;
1008 30 : if (!seg) {
1009 0 : LOGD() << "No segment for " << tag << " at tick " << destTick.ticks();
1010 0 : e.skipCurrentElement();
1011 0 : continue;
1012 : }
1013 :
1014 30 : EngravingItem* el = Factory::createItemByName(tag, score->dummy());
1015 30 : el->setTrack(destTrack);
1016 30 : TRead::readItem(el, e, ctx);
1017 30 : el->setTrack(destTrack);
1018 30 : el->setOwnershipParent(seg);
1019 30 : score->undoAddElement(el);
1020 24 : } else if (tag == "Slur"
1021 24 : || tag == "HairPin"
1022 24 : || tag == "Ottava"
1023 24 : || tag == "Trill"
1024 24 : || tag == "LetRing"
1025 24 : || tag == "Vibrato"
1026 24 : || tag == "PalmMute"
1027 24 : || tag == "WhammyBar"
1028 24 : || tag == "Rasgueado"
1029 24 : || tag == "HarmonicMark"
1030 24 : || tag == "PickScrape"
1031 24 : || tag == "TextLine"
1032 48 : || tag == "Pedal") {
1033 : // Spanners
1034 0 : if (destTrack >= maxTrack) {
1035 0 : LOGD() << "No track for " << tag;
1036 0 : e.skipCurrentElement();
1037 0 : continue;
1038 : }
1039 0 : Spanner* s = toSpanner(Factory::createItemByName(tag, score->dummy()));
1040 0 : s->setTrack(destTrack);
1041 0 : TRead::readItem(s, e, ctx);
1042 0 : s->setTrack(destTrack);
1043 0 : s->setTrack2(destTrack);
1044 0 : s->setTick(destTick);
1045 0 : score->undoAddElement(s);
1046 24 : } else if (tag == "Harmony" || tag == "FretDiagram") {
1047 : //
1048 : // Harmony elements (= chord symbols) are positioned respecting
1049 : // the original tickOffset: advance to destTick (or near)
1050 : // same for FretDiagram elements
1051 : //
1052 13 : Measure* meas = score->tick2measure(destTick);
1053 13 : Segment* seg = meas ? meas->undoGetChordRestOrTimeTickSegment(destTick) : nullptr;
1054 :
1055 13 : if (destTrack >= maxTrack || seg == nullptr) {
1056 0 : LOGD() << "No track or segment for " << tag << " at tick " << destTick.ticks();
1057 0 : e.skipCurrentElement(); // ignore
1058 0 : continue;
1059 : }
1060 13 : if (tag == "Harmony") {
1061 13 : Harmony* el = Factory::createHarmony(seg);
1062 13 : el->setTrack(trackZeroVoice(destTrack));
1063 13 : TRead::read(el, e, ctx);
1064 13 : el->setTrack(trackZeroVoice(destTrack));
1065 : // transpose
1066 13 : Staff* staffDest = score->staff(track2staff(destTrack));
1067 13 : Interval interval = staffDest->transpose(destTick);
1068 13 : if (!ctx.style().styleB(Sid::concertPitch) && !interval.isZero()) {
1069 0 : interval.flip();
1070 0 : Transpose::undoTransposeHarmony(tx, el, interval);
1071 : }
1072 13 : el->setOwnershipParent(seg);
1073 13 : score->undoAddElement(el);
1074 : } else {
1075 0 : FretDiagram* el = Factory::createFretDiagram(seg);
1076 0 : el->setTrack(trackZeroVoice(destTrack));
1077 0 : TRead::read(el, e, ctx);
1078 0 : el->setTrack(trackZeroVoice(destTrack));
1079 0 : score->undoAddElement(el);
1080 : }
1081 11 : } else if (tag == "Lyrics" || tag == "FiguredBass") {
1082 : // These elements are positioned respecting the distance in chords,
1083 : // rather than the time distance.
1084 : // TODO: is that appropriate for FiguredBass too? To be considered
1085 : // when overhauling FiguredBass.
1086 25 : for (; currSegm && segDelta > 0; segDelta--) {
1087 14 : currSegm = currSegm->nextCR(destTrack);
1088 : }
1089 : // check the intended dest. track and segment exist
1090 11 : if (destTrack >= maxTrack || currSegm == nullptr) {
1091 0 : LOGD("PasteSymbols: no track or segment for %s", tag.ascii());
1092 0 : e.skipCurrentElement(); // ignore
1093 0 : continue;
1094 : }
1095 : // check there is a segment element in the required track
1096 11 : if (currSegm->element(destTrack) == nullptr) {
1097 0 : LOGD("PasteSymbols: no track element for %s", tag.ascii());
1098 0 : e.skipCurrentElement();
1099 0 : continue;
1100 : }
1101 :
1102 11 : if (tag == "Lyrics") {
1103 : // with lyrics, skip rests
1104 11 : ChordRest* cr = toChordRest(currSegm->element(destTrack));
1105 11 : while (!cr->isChord() && currSegm) {
1106 0 : currSegm = currSegm->nextCR(destTrack);
1107 0 : if (currSegm) {
1108 0 : cr = toChordRest(currSegm->element(destTrack));
1109 : } else {
1110 0 : break;
1111 : }
1112 : }
1113 11 : if (currSegm == nullptr) {
1114 0 : LOGD("PasteSymbols: no segment for Lyrics");
1115 0 : e.skipCurrentElement();
1116 0 : continue;
1117 : }
1118 11 : if (!cr->isChord()) {
1119 0 : LOGD("PasteSymbols: can't paste Lyrics to rest");
1120 0 : e.skipCurrentElement();
1121 0 : continue;
1122 : }
1123 11 : Lyrics* el = Factory::createLyrics(cr);
1124 11 : el->setTrack(destTrack);
1125 11 : TRead::read(el, e, ctx);
1126 11 : el->setTrack(destTrack);
1127 11 : score->undoAddElement(el);
1128 0 : } else if (tag == "FiguredBass") {
1129 : // FiguredBass always belongs to first staff voice
1130 0 : destTrack = trackZeroVoice(destTrack);
1131 0 : Fraction ticks;
1132 0 : FiguredBass* el = Factory::createFiguredBass(currSegm);
1133 0 : el->setTrack(destTrack);
1134 0 : TRead::read(el, e, ctx);
1135 0 : el->setTrack(destTrack);
1136 : // if f.b. is off-note, we have to locate a place before currSegm
1137 : // where an on-note f.b. element could (potentially) be
1138 : // (while having an off-note f.b. without an on-note one before it
1139 : // is un-idiomatic, possible mismatch in rhythmic patterns between
1140 : // copy source and paste destination does not allow to be too picky)
1141 0 : if (!el->onNote()) {
1142 0 : FiguredBass* onNoteFB = nullptr;
1143 0 : Segment* prevSegm = currSegm;
1144 0 : bool done1 = false;
1145 0 : while (prevSegm) {
1146 0 : if (done1) {
1147 0 : break;
1148 : }
1149 0 : prevSegm = prevSegm->prev1(SegmentType::ChordRest);
1150 : // if there is a ChordRest in the dest. track
1151 : // this segment is a (potential) f.b. location
1152 0 : if (prevSegm->element(destTrack) != nullptr) {
1153 0 : done1 = true;
1154 : }
1155 : // in any case, look for a f.b. in annotations:
1156 : // if there is a f.b. element in the right track,
1157 : // this is an (actual) f.b. location
1158 0 : for (EngravingItem* a : prevSegm->annotations()) {
1159 0 : if (a->isFiguredBass() && a->track() == destTrack) {
1160 0 : onNoteFB = toFiguredBass(a);
1161 0 : done1 = true;
1162 : }
1163 : }
1164 : }
1165 0 : if (!prevSegm) {
1166 0 : LOGD("PasteSymbols: can't place off-note FiguredBass");
1167 0 : delete el;
1168 0 : continue;
1169 : }
1170 : // by default, split on-note duration in half: half on-note and half off-note
1171 0 : Fraction totTicks = currSegm->tick() - prevSegm->tick();
1172 0 : Fraction destTick1 = prevSegm->tick() + (totTicks * Fraction(1, 2));
1173 0 : ticks = totTicks * Fraction(1, 2);
1174 0 : if (onNoteFB) {
1175 0 : onNoteFB->setTicks(totTicks * Fraction(1, 2));
1176 : }
1177 : // look for a segment at this tick; if none, create one
1178 0 : Segment* nextSegm = prevSegm;
1179 0 : while (nextSegm && nextSegm->tick() < destTick1) {
1180 0 : nextSegm = nextSegm->next1(SegmentType::ChordRest);
1181 : }
1182 0 : if (!nextSegm || nextSegm->tick() > destTick1) { // no ChordRest segm at this tick
1183 0 : nextSegm = Factory::createSegment(prevSegm->measure(), SegmentType::ChordRest, destTick1);
1184 0 : if (!nextSegm) {
1185 0 : LOGD("PasteSymbols: can't find or create destination segment for FiguredBass");
1186 0 : delete el;
1187 0 : continue;
1188 : }
1189 0 : score->undoAddElement(nextSegm);
1190 : }
1191 0 : currSegm = nextSegm;
1192 : } else {
1193 : // by default, assign to FiguredBass element the duration of the chord it refers to
1194 0 : ticks = toChordRest(currSegm->element(destTrack))->ticks();
1195 : }
1196 : // in both cases, look for an existing f.b. element in segment and remove it, if found
1197 0 : FiguredBass* oldFB = nullptr;
1198 0 : for (EngravingItem* a : currSegm->annotations()) {
1199 0 : if (a->isFiguredBass() && a->track() == destTrack) {
1200 0 : oldFB = toFiguredBass(a);
1201 0 : break;
1202 : }
1203 : }
1204 0 : if (oldFB) {
1205 0 : score->undoRemoveElement(oldFB);
1206 : }
1207 0 : el->setOwnershipParent(currSegm);
1208 0 : el->setTicks(ticks);
1209 0 : score->undoAddElement(el);
1210 : }
1211 : } else {
1212 0 : LOGD("PasteSymbols: element %s not handled", tag.ascii());
1213 0 : e.skipCurrentElement(); // ignore
1214 : }
1215 : } // outer while readNextstartElement()
1216 11 : } // inner while readNextstartElement()
1217 11 : }
1218 :
1219 0 : void Read500::readTremoloCompat(compat::TremoloCompat* tc, XmlReader& xml)
1220 : {
1221 0 : ReadContext ctx(static_cast<EngravingObject*>(tc->parent)->score(), /* pasteMode = */ true);
1222 0 : TRead::read(tc, xml, ctx);
1223 0 : }
1224 :
1225 582 : void Read500::doReadItem(EngravingItem* item, XmlReader& xml)
1226 : {
1227 582 : ReadContext ctx(item->score(), /* pasteMode = */ true);
1228 582 : TRead::readItem(item, xml, ctx);
1229 582 : }
|