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