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