LCOV - code coverage report
Current view: top level - src/engraving/rw/read500 - read500.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 60.4 % 831 502
Test Date: 2026-11-05 04:07:02

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

Generated by: LCOV version 2.0-1