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