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

Generated by: LCOV version 2.0-1