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

Generated by: LCOV version 2.0-1