LCOV - code coverage report
Current view: top level - src/engraving/dom - tie.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 67.5 % 295 199
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 "tie.h"
      23              : 
      24              : #include "../editing/mscoreview.h"
      25              : #include "../rendering/iscorerenderer.h"
      26              : 
      27              : #include "accidental.h"
      28              : #include "barline.h"
      29              : #include "chord.h"
      30              : #include "factory.h"
      31              : #include "hook.h"
      32              : #include "ledgerline.h"
      33              : #include "measure.h"
      34              : #include "note.h"
      35              : #include "notedot.h"
      36              : #include "part.h"
      37              : #include "partialtie.h"
      38              : #include "repeatlist.h"
      39              : #include "score.h"
      40              : #include "staff.h"
      41              : #include "stafftype.h"
      42              : #include "stem.h"
      43              : #include "system.h"
      44              : #include "tiejumppointlist.h"
      45              : #include "utils.h"
      46              : #include "volta.h"
      47              : 
      48              : #include "log.h"
      49              : 
      50              : using namespace mu;
      51              : using namespace muse::draw;
      52              : 
      53              : namespace mu::engraving {
      54              : //---------------------------------------------------------
      55              : //   TieSegment
      56              : //---------------------------------------------------------
      57              : 
      58         1907 : TieSegment::TieSegment(Tie* sp)
      59         1907 :     : SlurTieSegment(ElementType::TIE_SEGMENT, sp)
      60              : {
      61         1907 : }
      62              : 
      63           80 : TieSegment::TieSegment(const ElementType& type, Tie* sp)
      64           80 :     : SlurTieSegment(type, sp)
      65              : {
      66           80 : }
      67              : 
      68          447 : TieSegment::TieSegment(const TieSegment& s)
      69          447 :     : SlurTieSegment(s)
      70              : {
      71          447 : }
      72              : 
      73            0 : void TieSegment::changeAnchor(EditData& ed, EngravingItem* element)
      74              : {
      75            0 :     if (ed.curGrip == Grip::START) {
      76            0 :         spanner()->setStartElement(element);
      77            0 :         Note* note = toNote(element);
      78            0 :         if (note->chord()->tick() <= tie()->endNote()->chord()->tick()) {
      79            0 :             tie()->startNote()->setTieFor(0);
      80            0 :             tie()->setStartNote(note);
      81            0 :             note->setTieFor(tie());
      82              :         }
      83              :     } else {
      84            0 :         spanner()->setEndElement(element);
      85            0 :         Note* note = toNote(element);
      86              :         // do not allow backward ties
      87            0 :         if (note->chord()->tick() >= tie()->startNote()->chord()->tick()) {
      88            0 :             tie()->endNote()->setTieBack(0);
      89            0 :             tie()->setEndNote(note);
      90            0 :             note->setTieBack(tie());
      91              :         }
      92              :     }
      93              : 
      94            0 :     const size_t segments  = spanner()->spannerSegments().size();
      95            0 :     ups(ed.curGrip).off = PointF();
      96            0 :     renderer()->layoutItem(spanner());
      97            0 :     if (spanner()->spannerSegments().size() != segments) {
      98            0 :         const std::vector<SpannerSegment*>& ss = spanner()->spannerSegments();
      99              : 
     100            0 :         TieSegment* newSegment = toTieSegment(ed.curGrip == Grip::END ? ss.back() : ss.front());
     101            0 :         ed.view()->changeEditElement(newSegment);
     102            0 :         triggerLayout();
     103              :     }
     104            0 : }
     105              : 
     106            0 : RectF TieSegment::drag(EditData& ed)
     107              : {
     108            0 :     consolidateAdjustmentOffsetIntoUserOffset();
     109            0 :     return SlurTieSegment::drag(ed);
     110              : }
     111              : 
     112            0 : void TieSegment::dragGrip(EditData& ed)
     113              : {
     114            0 :     consolidateAdjustmentOffsetIntoUserOffset();
     115            0 :     Grip g = ed.curGrip;
     116              : 
     117            0 :     switch (g) {
     118            0 :     case Grip::START:
     119              :     case Grip::END:
     120            0 :         ups(g).off += ed.delta;
     121            0 :         if (!isPartialTieSegment()) {
     122              :             //
     123              :             // move anchor for slurs/ties
     124              :             //
     125            0 :             if ((g == Grip::START && isSingleBeginType())
     126            0 :                 || (g == Grip::END && isSingleEndType())) {
     127            0 :                 Spanner* spanner = tie();
     128            0 :                 EngravingItem* e = ed.view()->elementNear(ed.pos);
     129            0 :                 Note* note = (e && e->isNote()) ? toNote(e) : nullptr;
     130            0 :                 if (note && ((g == Grip::END && note->tick() > tie()->tick())
     131            0 :                              || (g == Grip::START && note->tick() < tie()->tick2()))) {
     132            0 :                     if (g == Grip::END) {
     133            0 :                         Tie* tie = toTie(spanner);
     134            0 :                         if (tie->startNote()->pitch() == note->pitch()
     135            0 :                             && tie->startNote()->chord()->tick() < note->chord()->tick()) {
     136            0 :                             ed.view()->setDropTarget(note);
     137            0 :                             if (note != tie->endNote()) {
     138            0 :                                 changeAnchor(ed, note);
     139              :                             }
     140              :                         }
     141              :                     }
     142              :                 } else {
     143            0 :                     ed.view()->setDropTarget(0);
     144              :                 }
     145              :             }
     146              :         }
     147            0 :         renderer()->computeBezier(this);
     148            0 :         break;
     149            0 :     case Grip::BEZIER1:
     150              :     case Grip::BEZIER2:
     151            0 :         ups(g).off += ed.delta;
     152            0 :         renderer()->computeBezier(this);
     153            0 :         break;
     154            0 :     case Grip::SHOULDER:
     155            0 :         ups(g).off = PointF();
     156            0 :         ups(Grip::BEZIER1).off += ed.delta;
     157            0 :         ups(Grip::BEZIER2).off += ed.delta;
     158            0 :         renderer()->computeBezier(this);
     159            0 :         break;
     160            0 :     case Grip::DRAG:
     161            0 :         ups(Grip::DRAG).off = PointF();
     162            0 :         roffset() += ed.delta;
     163            0 :         break;
     164            0 :     default:
     165            0 :         UNREACHABLE;
     166              :         return;
     167              :     }
     168              : 
     169            0 :     triggerLayout();
     170            0 : }
     171              : 
     172            0 : void TieSegment::consolidateAdjustmentOffsetIntoUserOffset()
     173              : {
     174            0 :     for (size_t i = 0; i < m_adjustmentOffsets.size(); ++i) {
     175            0 :         Grip grip = static_cast<Grip>(i);
     176            0 :         PointF adjustOffset = m_adjustmentOffsets[i];
     177            0 :         if (!adjustOffset.isNull()) {
     178            0 :             ups(grip).p -= adjustOffset;
     179            0 :             ups(grip).off = adjustOffset;
     180              :         }
     181              :     }
     182            0 :     resetAdjustmentOffset();
     183            0 : }
     184              : 
     185        10702 : bool TieSegment::isEdited() const
     186              : {
     187        74914 :     for (int i = 0; i < int(Grip::GRIPS); ++i) {
     188        64212 :         if (!m_ups[i].off.isNull()) {
     189            0 :             return true;
     190              :         }
     191              :     }
     192        10702 :     return false;
     193              : }
     194              : 
     195         5170 : double TieSegment::minShoulderHeight() const
     196              : {
     197         5170 :     return style().styleAbsolute(Sid::tieMinShoulderHeight);
     198              : }
     199              : 
     200         5170 : double TieSegment::maxShoulderHeight() const
     201              : {
     202         5170 :     return style().styleAbsolute(Sid::tieMaxShoulderHeight);
     203              : }
     204              : 
     205         5142 : double TieSegment::endWidth() const
     206              : {
     207         5142 :     return style().styleAbsolute(Sid::tieEndWidth);
     208              : }
     209              : 
     210         5142 : double TieSegment::midWidth() const
     211              : {
     212         5142 :     return style().styleAbsolute(Sid::tieMidWidth);
     213              : }
     214              : 
     215           28 : double TieSegment::dottedWidth() const
     216              : {
     217           28 :     return style().styleAbsolute(Sid::tieDottedWidth);
     218              : }
     219              : 
     220              : //---------------------------------------------------------
     221              : //   Tie
     222              : //---------------------------------------------------------
     223              : 
     224           84 : Tie::Tie(const ElementType& type, EngravingObject* parent)
     225           84 :     : SlurTie(type, parent)
     226              : {
     227           84 : }
     228              : 
     229        15800 : TieJumpPointList* Tie::startTieJumpPoints() const
     230              : {
     231        15800 :     return m_jumpPoint ? m_jumpPoint->jumpPointList() : nullptr;
     232              : }
     233              : 
     234        16071 : void Tie::updatePossibleJumpPoints()
     235              : {
     236        16071 :     if (!tieJumpPoints()) {
     237          120 :         return;
     238              :     }
     239              : 
     240        15951 :     tieJumpPoints()->clear();
     241              : 
     242        15951 :     const Note* note = toNote(ownershipParent());
     243        15951 :     const Chord* chord = note ? note->chord() : nullptr;
     244        15951 :     const Measure* measure = chord ? chord->measure() : nullptr;
     245        15951 :     if (!measure) {
     246           30 :         return;
     247              :     }
     248              : 
     249        15921 :     const Segment* segment = chord ? chord->segment() : nullptr;
     250              : 
     251              :     // Check ties starting in this measure and ending in another
     252              :     // If they cross a repeat, add jump points
     253        15921 :     const bool hasFollowingJumpItem = chord->hasFollowingJumpItem();
     254              : 
     255        15921 :     if (!hasFollowingJumpItem) {
     256        15818 :         const Note* tieEndNote = endNote();
     257        15818 :         const Chord* endChord = tieEndNote ? tieEndNote->chord() : nullptr;
     258        15818 :         if (!endChord) {
     259         1404 :             return;
     260              :         }
     261        14414 :         const Segment* endNoteSegment = endChord ? endChord->segment() : nullptr;
     262        14414 :         const ChordRest* finalCROfMeasure = measure->lastChordRest(track());
     263        14414 :         const bool finalCRHasFollowingJump = finalCROfMeasure ? finalCROfMeasure->hasFollowingJumpItem() : false;
     264        14414 :         const bool segsAreAdjacent = segmentsAreAdjacent(segment, endNoteSegment);
     265        14414 :         const bool segsAreInDifferentRepeatSegments = segmentsAreInDifferentRepeatSegments(segment, endNoteSegment);
     266              : 
     267        14414 :         if (!(finalCRHasFollowingJump && segsAreAdjacent) || !segsAreInDifferentRepeatSegments) {
     268        14404 :             return;
     269              :         }
     270              :     }
     271              : 
     272          113 :     int jumpPointIdx = 0;
     273              : 
     274          113 :     Note* nextNote = searchTieNote(note);
     275          113 :     nextNote = nextNote ? nextNote : endNote();
     276              : 
     277          113 :     if (nextNote) {
     278           59 :         const bool hasTie = nextNote->tieBack();
     279           59 :         TieJumpPoint* jumpPoint = new TieJumpPoint(nextNote, hasTie, jumpPointIdx, true);
     280           59 :         tieJumpPoints()->add(jumpPoint);
     281           59 :         jumpPointIdx++;
     282              :     }
     283              : 
     284          242 :     for (Measure* jumpMeasure : findFollowingRepeatMeasures(measure)) {
     285          129 :         const Segment* firstCrSeg = jumpMeasure ? jumpMeasure->first(SegmentType::ChordRest) : nullptr;
     286          129 :         if (!firstCrSeg) {
     287            0 :             continue;
     288              :         }
     289              : 
     290          129 :         nextNote = searchTieNote(note, firstCrSeg, false);
     291              : 
     292          129 :         if (nextNote) {
     293          124 :             bool hasIncomingTie = nextNote->tieBack();
     294          124 :             TieJumpPoint* jumpPoint = new TieJumpPoint(nextNote, hasIncomingTie, jumpPointIdx, false);
     295          124 :             tieJumpPoints()->add(jumpPoint);
     296          124 :             jumpPointIdx++;
     297              :         }
     298          113 :     }
     299              : 
     300          113 :     if (jumpPointIdx < 2 && !isPartialTie()) {
     301            6 :         tieJumpPoints()->clear();
     302              :     }
     303              : }
     304              : 
     305           11 : void Tie::addTiesToJumpPoints()
     306              : {
     307           11 :     updatePossibleJumpPoints();
     308           11 :     TieJumpPointList* jumpPoints = tieJumpPoints();
     309           11 :     if (!jumpPoints) {
     310            0 :         return;
     311              :     }
     312              : 
     313           27 :     for (TieJumpPoint* jumpPoint : *jumpPoints) {
     314           16 :         if (jumpPoint->followingNote()) {
     315            6 :             jumpPoint->undoSetActive(true);
     316            6 :             continue;
     317              :         }
     318           10 :         jumpPoints->undoAddTieToScore(jumpPoint);
     319              :     }
     320              : 
     321              :     // Update jump points for linked ties
     322           22 :     for (EngravingObject* linkedTie : linkList()) {
     323           11 :         if (!linkedTie || !linkedTie->isTie() || linkedTie == this) {
     324           11 :             continue;
     325              :         }
     326            0 :         toTie(linkedTie)->updatePossibleJumpPoints();
     327           11 :     }
     328              : }
     329              : 
     330            6 : void Tie::undoRemoveTiesFromJumpPoints()
     331              : {
     332            6 :     TieJumpPointList* jumpPoints = tieJumpPoints();
     333            6 :     if (!jumpPoints) {
     334            0 :         return;
     335              :     }
     336           18 :     for (TieJumpPoint* jumpPoint : *jumpPoints) {
     337           12 :         if (jumpPoint->followingNote() || !jumpPoint->active()) {
     338            4 :             jumpPoint->undoSetActive(false);
     339            4 :             continue;
     340              :         }
     341              : 
     342            8 :         jumpPoints->undoRemoveTieFromScore(jumpPoint);
     343              :     }
     344              : }
     345              : 
     346         4494 : bool Tie::allJumpPointsInactive() const
     347              : {
     348         4494 :     if (endNote()) {
     349         4356 :         return false;
     350              :     }
     351          138 :     if (!tieJumpPoints()) {
     352            0 :         return true;
     353              :     }
     354              : 
     355          153 :     for (const TieJumpPoint* jumpPoint : *tieJumpPoints()) {
     356          148 :         if (jumpPoint->active()) {
     357          133 :             return false;
     358              :         }
     359              :     }
     360              : 
     361            5 :     return true;
     362              : }
     363              : 
     364        34750 : TieJumpPointList* Tie::tieJumpPoints()
     365              : {
     366        34750 :     return startNote() ? startNote()->tieJumpPoints() : nullptr;
     367              : }
     368              : 
     369          276 : const TieJumpPointList* Tie::tieJumpPoints() const
     370              : {
     371          276 :     return startNote() ? startNote()->tieJumpPoints() : nullptr;
     372              : }
     373              : 
     374         1746 : Tie::Tie(EngravingObject* parent)
     375         1746 :     : SlurTie(ElementType::TIE, parent)
     376              : {
     377         1746 : }
     378              : 
     379          467 : Tie::Tie(const Tie& t)
     380          467 :     : SlurTie(t)
     381              : {
     382          467 :     m_isInside = t.m_isInside;
     383          467 :     m_tiePlacement = t.m_tiePlacement;
     384              :     // Jump points must be recalculated for this tie
     385          467 :     m_jumpPoint = nullptr;
     386          467 : }
     387              : 
     388        11728 : PropertyValue Tie::getProperty(Pid propertyId) const
     389              : {
     390        11728 :     switch (propertyId) {
     391          663 :     case Pid::TIE_PLACEMENT:
     392          663 :         return tiePlacement();
     393        11065 :     default:
     394        11065 :         return SlurTie::getProperty(propertyId);
     395              :     }
     396              : }
     397              : 
     398         9282 : PropertyValue Tie::propertyDefault(Pid id) const
     399              : {
     400         9282 :     switch (id) {
     401          663 :     case Pid::TIE_PLACEMENT:
     402          663 :         return TiePlacement::AUTO;
     403         8619 :     default:
     404         8619 :         return SlurTie::propertyDefault(id);
     405              :     }
     406              : }
     407              : 
     408           23 : bool Tie::setProperty(Pid propertyId, const PropertyValue& v)
     409              : {
     410           23 :     switch (propertyId) {
     411            0 :     case Pid::TIE_PLACEMENT:
     412            0 :         setTiePlacement(v.value<TiePlacement>());
     413            0 :         break;
     414           23 :     default:
     415           23 :         return SlurTie::setProperty(propertyId, v);
     416              :     }
     417            0 :     triggerLayout();
     418            0 :     return true;
     419              : }
     420              : 
     421         5170 : double Tie::scalingFactor() const
     422              : {
     423         5170 :     const bool hasBothNotes = startNote() && endNote();
     424              : 
     425         5170 :     const Note* primaryNote = startNote() ? startNote() : endNote();
     426         5170 :     const Note* secondaryNote = hasBothNotes ? endNote() : nullptr;
     427              : 
     428         5170 :     if (!primaryNote) {
     429            0 :         return 1.0;
     430              :     }
     431              : 
     432         5170 :     if (primaryNote->isGrace()) {
     433           25 :         return style().styleD(Sid::graceNoteMag);
     434              :     }
     435              : 
     436         5145 :     if (hasBothNotes) {
     437         4865 :         return 0.5 * (primaryNote->chord()->intrinsicMag() + secondaryNote->chord()->intrinsicMag());
     438              :     }
     439              : 
     440          280 :     return primaryNote->chord()->intrinsicMag();
     441              : }
     442              : 
     443         2926 : void Tie::setStartNote(Note* note)
     444              : {
     445         2926 :     setStartElement(note);
     446         2926 :     setOwnershipParent(note);
     447         2926 : }
     448              : 
     449       197024 : Note* Tie::startNote() const
     450              : {
     451       197024 :     assert(!startElement() || startElement()->isNote());
     452       197024 :     return toNote(startElement());
     453              : }
     454              : 
     455       120073 : Note* Tie::endNote() const
     456              : {
     457       120073 :     return toNote(endElement());
     458              : }
     459              : 
     460        12854 : bool Tie::isOuterTieOfChord(Grip startOrEnd) const
     461              : {
     462        12854 :     if (m_isInside) {
     463            0 :         return false;
     464              :     }
     465              : 
     466        12854 :     const bool start = startOrEnd == Grip::START;
     467        12854 :     const Note* note = start ? startNote() : endNote();
     468        12854 :     if (!note) {
     469            9 :         return false;
     470              :     }
     471              : 
     472        12845 :     const Chord* chord = note->chord();
     473              : 
     474        12845 :     return (note == chord->upNote() && up()) || (note == chord->downNote() && !up());
     475              : }
     476              : 
     477         2354 : bool Tie::hasTiedSecondInside() const
     478              : {
     479         2354 :     const Note* note = startNote();
     480         2354 :     if (!note) {
     481            7 :         return false;
     482              :     }
     483              : 
     484         2347 :     const Chord* chord = note->chord();
     485         2347 :     const int line = note->line();
     486         2347 :     const int secondInsideLine = up() ? line + 1 : line - 1;
     487              : 
     488         5751 :     for (const Note* otherNote : chord->notes()) {
     489         3404 :         if (otherNote->line() == secondInsideLine && otherNote->tieFor() && otherNote->tieFor()->up() == up()) {
     490            0 :             return true;
     491              :         }
     492              :     }
     493              : 
     494         2347 :     return false;
     495              : }
     496              : 
     497         2331 : bool Tie::isCrossStaff() const
     498              : {
     499         2331 :     const Note* startN = startNote();
     500         2331 :     const Note* endN = endNote();
     501         2331 :     const Chord* startChord = startN ? startN->chord() : nullptr;
     502         2331 :     const Chord* endChord = endN ? endN->chord() : nullptr;
     503         2331 :     const staff_idx_t staff = staffIdx();
     504              : 
     505         2331 :     return (startChord && (startChord->staffMove() != 0 || startChord->vStaffIdx() != staff))
     506         4662 :            || (endChord && (endChord->staffMove() != 0 || endChord->vStaffIdx() != staff));
     507              : }
     508              : 
     509            5 : void Tie::changeTieType(Tie* oldTie, Note* endNote)
     510              : {
     511              :     // Replaces oldTie with an outgoing partial tie if no endNote is specified.  Otherwise replaces oldTie with a regular tie
     512            5 :     Note* startNote = oldTie->startNote();
     513            5 :     bool addPartialTie = !endNote;
     514            5 :     Score* score = startNote ? startNote->score() : nullptr;
     515            5 :     if (!score) {
     516            0 :         return;
     517              :     }
     518              : 
     519            5 :     Tie* newTie = addPartialTie ? Factory::createPartialTie(startNote) : Factory::createTie(startNote);
     520              : 
     521            5 :     score->undoRemoveElement(oldTie);
     522              : 
     523            5 :     newTie->setStartNote(startNote);
     524            5 :     newTie->setTick(startNote->tick());
     525            5 :     newTie->setTrack(startNote->track());
     526            5 :     startNote->setTieFor(newTie);
     527            5 :     if (!addPartialTie) {
     528            0 :         newTie->setEndNote(endNote);
     529            0 :         endNote->setTieBack(newTie);
     530              :     }
     531              : 
     532            5 :     newTie->setStyleType(oldTie->styleType());
     533            5 :     newTie->setTiePlacement(oldTie->tiePlacement());
     534            5 :     newTie->setSlurDirection(oldTie->slurDirection());
     535              : 
     536            5 :     newTie->setVisible(oldTie->visible());
     537            5 :     newTie->setOffset(oldTie->offset());
     538              : 
     539            5 :     score->undoAddElement(newTie);
     540              : }
     541              : 
     542            8 : void Tie::updateStartTieOnRemoval()
     543              : {
     544            8 :     if (!jumpPoint() || !startTie() || !startTieJumpPoints()) {
     545            0 :         return;
     546              :     }
     547            8 :     jumpPoint()->undoSetActive(false);
     548            8 :     Tie* _startTie = startTie();
     549            8 :     if (startTieJumpPoints()->size() <= 1 || _startTie->allJumpPointsInactive()) {
     550            0 :         score()->undoRemoveElement(_startTie);
     551              :     }
     552              : }
     553              : 
     554         7998 : Tie* Tie::startTie() const
     555              : {
     556         7998 :     return startTieJumpPoints() ? startTieJumpPoints()->startTie() : nullptr;
     557              : }
     558              : }
        

Generated by: LCOV version 2.0-1