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

Generated by: LCOV version 2.0-1