LCOV - code coverage report
Current view: top level - src/engraving/rw/read460 - connectorinforeader.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 58.1 % 227 132
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 "connectorinforeader.h"
      23              : 
      24              : #include "../../infrastructure/rtti.h"
      25              : 
      26              : #include "../../dom/factory.h"
      27              : #include "../../dom/score.h"
      28              : #include "../../dom/engravingitem.h"
      29              : #include "../../dom/spanner.h"
      30              : #include "../../dom/chordrest.h"
      31              : #include "../../dom/measure.h"
      32              : #include "../../dom/note.h"
      33              : #include "../../dom/noteline.h"
      34              : #include "../../dom/tie.h"
      35              : #include "../../dom/chord.h"
      36              : #include "../../dom/staff.h"
      37              : 
      38              : #include "../types/typesconv.h"
      39              : 
      40              : #include "../xmlreader.h"
      41              : 
      42              : #include "tread.h"
      43              : 
      44              : #include "log.h"
      45              : 
      46              : using namespace mu::engraving;
      47              : using namespace mu::engraving::read460;
      48              : 
      49              : //---------------------------------------------------------
      50              : //   ConnectorInfoReader
      51              : //---------------------------------------------------------
      52              : 
      53          332 : ConnectorInfoReader::ConnectorInfoReader(XmlReader& e, ReadContext* ctx, EngravingItem* current, int track)
      54          332 :     : ConnectorInfo(current, track), m_reader(&e), m_ctx(ctx), m_connector(nullptr), m_connectorReceiver(current)
      55          332 : {}
      56              : 
      57              : //---------------------------------------------------------
      58              : //   readPositionInfo
      59              : //---------------------------------------------------------
      60              : 
      61            0 : static Location readPositionInfo(ReadContext* ctx, int track)
      62              : {
      63            0 :     Location info = ctx->location();
      64            0 :     info.setTrack(track);
      65            0 :     return info;
      66              : }
      67              : 
      68              : //---------------------------------------------------------
      69              : //   ConnectorInfoReader
      70              : //---------------------------------------------------------
      71              : 
      72            0 : ConnectorInfoReader::ConnectorInfoReader(XmlReader& e, ReadContext* ctx, Score* current, int track)
      73            0 :     : ConnectorInfo(current, readPositionInfo(ctx, track)), m_reader(&e), m_ctx(ctx), m_connector(nullptr), m_connectorReceiver(current)
      74              : {
      75            0 :     setCurrentUpdated(true);
      76            0 : }
      77              : 
      78              : //---------------------------------------------------------
      79              : //   ConnectorInfoReader::read
      80              : //---------------------------------------------------------
      81              : 
      82          332 : bool ConnectorInfoReader::read()
      83              : {
      84          332 :     XmlReader& e = *m_reader;
      85          332 :     const AsciiStringView name(e.asciiAttribute("type"));
      86          332 :     m_type = TConv::fromXml(name, ElementType::INVALID);
      87              : 
      88          332 :     m_ctx->fillLocation(m_currentLoc);
      89              : 
      90          830 :     while (e.readNextStartElement()) {
      91          498 :         const AsciiStringView tag(e.name());
      92              : 
      93          498 :         if (tag == "prev") {
      94          166 :             readEndpointLocation(m_prevLoc);
      95          332 :         } else if (tag == "next") {
      96          166 :             readEndpointLocation(m_nextLoc);
      97              :         } else {
      98          166 :             if (tag == name) {
      99          166 :                 m_connector = Factory::createItemByName(tag, m_connectorReceiver->score()->dummy());
     100              :             } else {
     101            0 :                 LOGW("ConnectorInfoReader::read: element tag (%s) does not match connector type (%s). Is the file corrupted?",
     102              :                      tag.ascii(), name.ascii());
     103              :             }
     104              : 
     105          166 :             if (!m_connector) {
     106            0 :                 e.unknown();
     107            0 :                 return false;
     108              :             }
     109          166 :             m_connector->setTrack(m_currentLoc.track());
     110          166 :             TRead::readItem(m_connector, e, *m_ctx);
     111              :         }
     112              :     }
     113          332 :     return true;
     114              : }
     115              : 
     116              : //---------------------------------------------------------
     117              : //   ConnectorInfoReader::readEndpointLocation
     118              : //---------------------------------------------------------
     119              : 
     120          332 : void ConnectorInfoReader::readEndpointLocation(Location& l)
     121              : {
     122          332 :     XmlReader& e = *m_reader;
     123          664 :     while (e.readNextStartElement()) {
     124          332 :         const AsciiStringView tag(e.name());
     125          332 :         if (tag == "location") {
     126          332 :             l = Location::relative();
     127          332 :             TRead::read(&l, e, *m_ctx);
     128              :         } else {
     129            0 :             e.unknown();
     130              :         }
     131              :     }
     132          332 : }
     133              : 
     134            0 : Fraction ConnectorInfoReader::curTick() const
     135              : {
     136            0 :     return m_ctx->tick();
     137              : }
     138              : 
     139              : //---------------------------------------------------------
     140              : //   ConnectorInfoReader::update
     141              : //---------------------------------------------------------
     142              : 
     143          332 : void ConnectorInfoReader::update()
     144              : {
     145          332 :     if (!currentUpdated()) {
     146          332 :         updateCurrentInfo(m_ctx->pasteMode());
     147              :     }
     148          332 :     if (hasPrevious()) {
     149          166 :         m_prevLoc.toAbsolute(m_currentLoc);
     150              :     }
     151          332 :     if (hasNext()) {
     152          166 :         m_nextLoc.toAbsolute(m_currentLoc);
     153              :     }
     154          332 : }
     155              : 
     156              : //---------------------------------------------------------
     157              : //   ConnectorInfoReader::addToScore
     158              : //---------------------------------------------------------
     159              : 
     160              : class ReadAddConnectorVisitor : public rtti::Visitor<ReadAddConnectorVisitor>
     161              : {
     162              : public:
     163              : 
     164              :     template<typename T>
     165          794 :     static bool doVisit(EngravingObject* item, ConnectorInfoReader* info, bool pasteMode)
     166              :     {
     167          794 :         if (T::classof(item) || (std::is_same<T, ChordRest>::value && item->isChordRest())) {
     168          332 :             ConnectorInfoReader::readAddConnector(static_cast<T*>(item), info, pasteMode);
     169          332 :             return true;
     170              :         }
     171          462 :         return false;
     172              :     }
     173              : };
     174              : 
     175              : using ReadAddConnectorTypes = rtti::TypeList<ChordRest, Measure, Note, Score>;
     176              : 
     177          166 : void ConnectorInfoReader::addToScore(bool pasteMode)
     178              : {
     179          166 :     ConnectorInfoReader* r = this;
     180          190 :     while (r->prev()) {
     181           24 :         r = r->prev();
     182              :     }
     183          498 :     while (r) {
     184          332 :         bool found = ReadAddConnectorVisitor::visit(ReadAddConnectorTypes {}, r->m_connectorReceiver, r, pasteMode);
     185          332 :         DO_ASSERT(found);
     186          332 :         r = r->next();
     187              :     }
     188          166 : }
     189              : 
     190              : //---------------------------------------------------------
     191              : //   ConnectorInfoReader::readConnector
     192              : //---------------------------------------------------------
     193              : 
     194          332 : void ConnectorInfoReader::readConnector(std::shared_ptr<ConnectorInfoReader> info, XmlReader& e, ReadContext& ctx)
     195              : {
     196          332 :     if (!info->read()) {
     197            0 :         e.skipCurrentElement();
     198            0 :         return;
     199              :     }
     200          332 :     ctx.addConnectorInfoLater(info);
     201              : }
     202              : 
     203              : //---------------------------------------------------------
     204              : //   ConnectorInfoReader::connector
     205              : //---------------------------------------------------------
     206              : 
     207          332 : EngravingItem* ConnectorInfoReader::connector()
     208              : {
     209              :     // connector should be contained in the first node normally.
     210          332 :     ConnectorInfo* i = findFirst();
     211          332 :     if (i) {
     212          332 :         return static_cast<ConnectorInfoReader*>(i)->m_connector;
     213              :     }
     214            0 :     return nullptr;
     215              : }
     216              : 
     217              : //---------------------------------------------------------
     218              : //   ConnectorInfoReader::connector
     219              : //---------------------------------------------------------
     220              : 
     221            0 : const EngravingItem* ConnectorInfoReader::connector() const
     222              : {
     223            0 :     return const_cast<ConnectorInfoReader*>(this)->connector();
     224              : }
     225              : 
     226              : //---------------------------------------------------------
     227              : //   ConnectorInfoReader::releaseConnector
     228              : //---------------------------------------------------------
     229              : 
     230            0 : EngravingItem* ConnectorInfoReader::releaseConnector()
     231              : {
     232            0 :     ConnectorInfoReader* i = static_cast<ConnectorInfoReader*>(findFirst());
     233            0 :     if (!i) {
     234              :         // circular connector?
     235            0 :         ConnectorInfoReader* ii = this;
     236            0 :         EngravingItem* c = nullptr;
     237            0 :         while (ii->prev()) {
     238            0 :             if (ii->m_connector) {
     239            0 :                 c = ii->m_connector;
     240            0 :                 ii->m_connector = nullptr;
     241              :             }
     242            0 :             ii = ii->prev();
     243            0 :             if (ii == this) {
     244            0 :                 break;
     245              :             }
     246              :         }
     247            0 :         return c;
     248              :     }
     249            0 :     EngravingItem* c = i->m_connector;
     250            0 :     i->m_connector = nullptr;
     251            0 :     return c;
     252              : }
     253              : 
     254           60 : void ConnectorInfoReader::readAddConnector(ChordRest* item, ConnectorInfoReader* info, bool pasteMode)
     255              : {
     256           60 :     const ElementType type = info->type();
     257           60 :     switch (type) {
     258           60 :     case ElementType::SLUR:
     259              :     case ElementType::HAMMER_ON_PULL_OFF:
     260              :     {
     261           60 :         Spanner* spanner = toSpanner(info->connector());
     262           60 :         const Location& l = info->location();
     263              : 
     264           60 :         if (info->isStart()) {
     265           30 :             spanner->setTrack(l.track());
     266              :             // trillCueNotes have unreliable tick() while reading so use instead tick from readContext
     267           30 :             Fraction startTick = item->isChord() && toChord(item)->isTrillCueNote() ? info->curTick() : item->tick();
     268           30 :             spanner->setTick(startTick);
     269           30 :             spanner->setStartElement(item);
     270           30 :             if (pasteMode) {
     271            0 :                 item->score()->undoAddElement(spanner);
     272            0 :                 for (EngravingObject* linkedSpanner : spanner->linkList()) {
     273            0 :                     if (linkedSpanner == spanner) {
     274            0 :                         continue;
     275              :                     }
     276            0 :                     Spanner* ls = toSpanner(linkedSpanner);
     277            0 :                     ls->setTick(spanner->tick());
     278            0 :                     for (EngravingObject* linkedCR : item->linkList()) {
     279            0 :                         ChordRest* cr = toChordRest(linkedCR);
     280            0 :                         if (cr->score() == linkedSpanner->score() && cr->staffIdx() == ls->staffIdx()) {
     281            0 :                             ls->setTrack(cr->track());
     282            0 :                             if (ls->isSlur()) {
     283            0 :                                 ls->setStartElement(cr);
     284              :                             }
     285            0 :                             break;
     286              :                         }
     287            0 :                     }
     288            0 :                 }
     289              :             } else {
     290           30 :                 item->score()->addSpanner(spanner);
     291              :             }
     292           30 :         } else if (info->isEnd()) {
     293           30 :             spanner->setTrack2(l.track());
     294           30 :             spanner->setTick2(item->tick());
     295           30 :             spanner->setEndElement(item);
     296           30 :             if (pasteMode) {
     297            0 :                 for (EngravingObject* linkedSpanner : spanner->linkList()) {
     298            0 :                     if (linkedSpanner == spanner) {
     299            0 :                         continue;
     300              :                     }
     301            0 :                     Spanner* ls = toSpanner(linkedSpanner);
     302            0 :                     ls->setTick2(spanner->tick2());
     303            0 :                     for (EngravingObject* linkedCR : item->linkList()) {
     304            0 :                         ChordRest* cr = toChordRest(linkedCR);
     305            0 :                         if (cr->score() == linkedSpanner->score() && cr->staffIdx() == ls->staffIdx()) {
     306            0 :                             ls->setTrack2(cr->track());
     307            0 :                             if (ls->isSlur()) {
     308            0 :                                 ls->setEndElement(cr);
     309              :                             }
     310            0 :                             break;
     311              :                         }
     312            0 :                     }
     313            0 :                 }
     314              :             }
     315              :         } else {
     316            0 :             LOGD("ChordRest::readAddConnector(): Slur end is neither start nor end");
     317              :         }
     318              :     }
     319           60 :     break;
     320            0 :     default:
     321            0 :         break;
     322              :     }
     323           60 : }
     324              : 
     325           82 : void ConnectorInfoReader::readAddConnector(Measure* item, ConnectorInfoReader* info, bool pasteMode)
     326              : {
     327           82 :     const ElementType type = info->type();
     328           82 :     switch (type) {
     329           82 :     case ElementType::HAIRPIN:
     330              :     case ElementType::PEDAL:
     331              :     case ElementType::OTTAVA:
     332              :     case ElementType::TRILL:
     333              :     case ElementType::TEXTLINE:
     334              :     case ElementType::LET_RING:
     335              :     case ElementType::GRADUAL_TEMPO_CHANGE:
     336              :     case ElementType::VIBRATO:
     337              :     case ElementType::PALM_MUTE:
     338              :     case ElementType::PARTIAL_LYRICSLINE:
     339              :     case ElementType::WHAMMY_BAR:
     340              :     case ElementType::RASGUEADO:
     341              :     case ElementType::HARMONIC_MARK:
     342              :     case ElementType::PICK_SCRAPE:
     343              :     case ElementType::VOLTA:
     344              :     {
     345           82 :         Spanner* sp = toSpanner(info->connector());
     346           82 :         const Location& l = info->location();
     347           82 :         Fraction lTick    = l.frac();
     348           82 :         Fraction spTick   = pasteMode ? lTick : (item->tick() + lTick);
     349           82 :         if (info->isStart()) {
     350           41 :             sp->setTrack(l.track());
     351           41 :             sp->setTrack2(sp->track());
     352           41 :             sp->setTick(spTick);
     353              :             // Defer computing the end element: the end anchor (tick2/track2) is only known once the
     354              :             // matching end connector is processed (below). Computing it here would cache a wrong value
     355              :             // (tick2 == tick) that nothing recomputes until layout. See readAddConnector end branch.
     356           41 :             item->score()->addSpanner(sp, /*computeStartEnd=*/ false);
     357           41 :             sp->computeStartElement();
     358           41 :         } else if (info->isEnd()) {
     359           41 :             sp->setTrack2(l.track());
     360           41 :             sp->setTick2(spTick);
     361              :             // Now that the end anchor is set, compute the end element once with the final value.
     362           41 :             sp->computeEndElement();
     363              :         }
     364              :     }
     365           82 :     break;
     366            0 :     default:
     367            0 :         break;
     368              :     }
     369           82 : }
     370              : 
     371          190 : void ConnectorInfoReader::readAddConnector(Note* item, ConnectorInfoReader* info, bool pasteMode)
     372              : {
     373          190 :     const ElementType type = info->type();
     374          190 :     const Location& l = info->location();
     375          190 :     switch (type) {
     376          190 :     case ElementType::TIE:
     377              :     case ElementType::TEXTLINE:
     378              :     case ElementType::GLISSANDO:
     379              :     case ElementType::GUITAR_BEND:
     380              :     case ElementType::NOTELINE:
     381              :     {
     382          190 :         Spanner* sp = toSpanner(info->connector());
     383          190 :         if (info->isStart()) {
     384           95 :             sp->setTrack(l.track());
     385           95 :             sp->setTick(item->tick());
     386           95 :             if (sp->isTie()) {
     387           24 :                 Note* n = item;
     388           24 :                 while (n->tieFor()) {
     389            0 :                     n = n->tieFor()->endNote();
     390              :                 }
     391           24 :                 Tie* tie = toTie(sp);
     392           24 :                 tie->setOwnershipParent(n);
     393           24 :                 tie->setStartNote(n);
     394           24 :                 n->setTieFor(tie);
     395              :             } else {
     396           71 :                 sp->setStartElement(item);
     397           71 :                 item->addSpannerFor(sp);
     398           71 :                 sp->setOwnershipParent(item);
     399              :             }
     400           95 :         } else if (info->isEnd()) {
     401           95 :             sp->setTrack2(l.track());
     402           95 :             sp->setTick2(item->tick());
     403           95 :             sp->setEndElement(item);
     404           95 :             if (sp->isTie()) {
     405           24 :                 Tie* tie = toTie(sp);
     406           24 :                 item->setTieBack(tie);
     407           24 :                 if (pasteMode) {
     408            0 :                     tie->updatePossibleJumpPoints();
     409              :                 }
     410              :             } else {
     411           71 :                 bool isNoteAnchoredTextLine = sp->isNoteLine() && toNoteLine(sp)->enforceMinLength();
     412          142 :                 if ((sp->isGlissando() || sp->isGuitarBend() || isNoteAnchoredTextLine) && item->ownershipParent()
     413          142 :                     && item->ownershipParent()->isChord()) {
     414           71 :                     toChord(item->ownershipParent())->setEndsNoteAnchoredLine(true);
     415              :                 }
     416           71 :                 item->addSpannerBack(sp);
     417              :             }
     418              : 
     419              :             // As spanners get added after being fully read, they
     420              :             // do not get cloned with the note when pasting to
     421              :             // linked staves. So add this spanner explicitly.
     422           95 :             if (pasteMode) {
     423            0 :                 item->score()->undoAddElement(sp);
     424              :             }
     425              :         }
     426              :     }
     427              :     default:
     428          190 :         break;
     429              :     }
     430          190 : }
     431              : 
     432            0 : void ConnectorInfoReader::readAddConnector(Score* item, ConnectorInfoReader* info, bool pasteMode)
     433              : {
     434            0 :     if (!pasteMode) {
     435              :         // How did we get there?
     436            0 :         LOGD("Score::readAddConnector is called not in paste mode.");
     437            0 :         return;
     438              :     }
     439              : 
     440            0 :     if (info->connector()->systemFlag()) {
     441            0 :         return;
     442              :     }
     443              : 
     444            0 :     const ElementType type = info->type();
     445            0 :     switch (type) {
     446            0 :     case ElementType::HAIRPIN:
     447              :     case ElementType::PEDAL:
     448              :     case ElementType::OTTAVA:
     449              :     case ElementType::TRILL:
     450              :     case ElementType::TEXTLINE:
     451              :     case ElementType::PALM_MUTE:
     452              :     case ElementType::PARTIAL_LYRICSLINE:
     453              :     case ElementType::WHAMMY_BAR:
     454              :     case ElementType::RASGUEADO:
     455              :     case ElementType::HARMONIC_MARK:
     456              :     case ElementType::PICK_SCRAPE:
     457              :     case ElementType::LET_RING:
     458              :     case ElementType::VIBRATO:
     459              :     {
     460            0 :         Spanner* sp = toSpanner(info->connector());
     461            0 :         const Location& l = info->location();
     462            0 :         if (info->isStart()) {
     463            0 :             sp->setTrack(l.track());
     464            0 :             sp->setTrack2(l.track());
     465            0 :             sp->setTick(l.frac());
     466            0 :         } else if (info->isEnd()) {
     467            0 :             sp->setTick2(l.frac());
     468            0 :             item->undoAddElement(sp);
     469            0 :             if (sp->isOttava()) {
     470            0 :                 sp->staff()->updateOttava();
     471              :             }
     472              :         }
     473              :     }
     474            0 :     break;
     475            0 :     default:
     476            0 :         break;
     477              :     }
     478              : }
        

Generated by: LCOV version 2.0-1