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

Generated by: LCOV version 2.0-1