LCOV - code coverage report
Current view: top level - src/engraving/dom - line.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 26.2 % 633 166
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              : 
      23              : #include "line.h"
      24              : 
      25              : #include <vector>
      26              : 
      27              : #include "containers.h"
      28              : 
      29              : #include "iengravingconfiguration.h" // IWYU pragma: keep
      30              : 
      31              : #include "../editing/mscoreview.h"
      32              : #include "../editing/navigation.h"
      33              : #include "../rendering/iscorerenderer.h"
      34              : 
      35              : #include "anchors.h"
      36              : #include "barline.h"
      37              : #include "chord.h"
      38              : #include "dynamic.h"
      39              : #include "measure.h"
      40              : #include "note.h"
      41              : #include "page.h"
      42              : #include "part.h"
      43              : #include "score.h"
      44              : #include "segment.h"
      45              : #include "staff.h"
      46              : #include "system.h"
      47              : #include "utils.h"
      48              : 
      49              : #include "log.h"
      50              : 
      51              : using namespace mu;
      52              : using namespace mu::engraving;
      53              : 
      54              : namespace mu::engraving {
      55         1673 : LineSegment::LineSegment(const ElementType& type, SLine* sp, ElementFlags f)
      56         1673 :     : SpannerSegment(type, sp, f)
      57              : {
      58         1673 : }
      59              : 
      60              : //---------------------------------------------------------
      61              : //   LineSegment
      62              : //---------------------------------------------------------
      63              : 
      64          108 : LineSegment::LineSegment(const LineSegment& s)
      65          108 :     : SpannerSegment(s)
      66              : {
      67          108 : }
      68              : 
      69              : //---------------------------------------------------------
      70              : //   gripsPositions
      71              : //---------------------------------------------------------
      72              : 
      73            0 : std::vector<PointF> LineSegment::gripsPositions(const EditData&) const
      74              : {
      75            0 :     std::vector<PointF> grips(gripsCount());
      76            0 :     PointF pp(pagePos());
      77            0 :     grips[int(Grip::START)] = pp;
      78            0 :     grips[int(Grip::END)] = pos2() + pp;
      79            0 :     grips[int(Grip::MIDDLE)] = pos2() * .5 + pp;
      80            0 :     return grips;
      81            0 : }
      82              : 
      83              : //---------------------------------------------------------
      84              : //   leftAnchorPosition
      85              : //---------------------------------------------------------
      86              : 
      87            0 : PointF LineSegment::leftAnchorPosition(const double& systemPositionY) const
      88              : {
      89            0 :     if (isMiddleType() || isEndType()) {
      90            0 :         return PointF(system()->firstMeasure()->pageBoundingRect().left(), systemPositionY);
      91              :     }
      92              : 
      93            0 :     PointF result;
      94              : 
      95            0 :     Segment* startSeg = line()->startSegment();
      96            0 :     if (!startSeg) {
      97            0 :         return result;
      98              :     }
      99              : 
     100            0 :     System* s = startSeg->measure()->system();
     101            0 :     result = startSeg->pos() + startSeg->measure()->pos();
     102            0 :     result.ry() += systemPositionY - system()->pos().y();
     103              : 
     104            0 :     if (s) {
     105            0 :         result += s->pos();      // to page coordinates
     106              :     }
     107            0 :     return result;
     108              : }
     109              : 
     110              : //---------------------------------------------------------
     111              : //   rightAnchorPosition
     112              : //---------------------------------------------------------
     113              : 
     114            0 : PointF LineSegment::rightAnchorPosition(const double& systemPositionY) const
     115              : {
     116            0 :     const System* sys = system();
     117            0 :     IF_ASSERT_FAILED(sys) {
     118            0 :         return PointF();
     119              :     }
     120              : 
     121            0 :     bool endsAtSystemEnd = isSingleEndType() && line()->tick2() == sys->endTick();
     122            0 :     if (isMiddleType() || isBeginType() || endsAtSystemEnd) {
     123            0 :         return PointF(sys->endingXForOpenEndedLines() + sys->x(), systemPositionY);
     124              :     }
     125              : 
     126            0 :     PointF result;
     127              : 
     128            0 :     Segment* endSeg = line()->endSegment();
     129            0 :     if (!endSeg) {
     130            0 :         return result;
     131              :     }
     132              : 
     133            0 :     System* s = endSeg->measure()->system();
     134            0 :     result = endSeg->pos() + endSeg->measure()->pos();
     135            0 :     result.ry() += systemPositionY - system()->pos().y();
     136              : 
     137            0 :     if (s) {
     138            0 :         result += s->pos();    // to page coordinates
     139              :     }
     140            0 :     return result;
     141              : }
     142              : 
     143              : //---------------------------------------------------------
     144              : //   gripAnchorLines
     145              : //    return page coordinates
     146              : //---------------------------------------------------------
     147              : 
     148            0 : std::vector<LineF> LineSegment::gripAnchorLines(Grip grip) const
     149              : {
     150            0 :     std::vector<LineF> result;
     151              : 
     152              :     // Middle or aperture grip have no anchor
     153            0 :     if (!system() || grip == Grip::APERTURE) {
     154            0 :         return result;
     155              :     }
     156              : 
     157            0 :     const staff_idx_t stIdx = staffIdx();
     158            0 :     double y = system()->staffYpage(stIdx);
     159            0 :     if (line()->placement() == PlacementV::BELOW) {
     160            0 :         y += system()->staff(stIdx)->bbox().height();
     161              :     }
     162              :     // adjust Y to staffType offset
     163            0 :     y += staffOffsetY();
     164              : 
     165            0 :     const Page* p = system()->page();
     166            0 :     const PointF pageOffset = p ? p->pos() : PointF();
     167            0 :     switch (grip) {
     168            0 :     case Grip::START:
     169            0 :         result.push_back(LineF(leftAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::START))).translated(pageOffset));
     170            0 :         break;
     171            0 :     case Grip::END:
     172            0 :         result.push_back(LineF(rightAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::END))).translated(pageOffset));
     173            0 :         break;
     174            0 :     case Grip::MIDDLE:
     175            0 :         result.push_back(LineF(leftAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::START))).translated(pageOffset));
     176            0 :         result.push_back(LineF(rightAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::END))).translated(pageOffset));
     177            0 :         break;
     178            0 :     default:
     179            0 :         break;
     180              :     }
     181              : 
     182            0 :     return result;
     183            0 : }
     184              : 
     185              : //---------------------------------------------------------
     186              : //   startDrag
     187              : //---------------------------------------------------------
     188              : 
     189            0 : void LineSegment::startDrag(EditData& ed)
     190              : {
     191            0 :     SpannerSegment::startDrag(ed);
     192            0 :     ElementEditDataPtr eed = ed.getData(this);
     193            0 :     if (!eed) {
     194            0 :         return;
     195              :     }
     196            0 :     eed->pushProperty(Pid::OFFSET2);
     197            0 : }
     198              : 
     199              : //---------------------------------------------------------
     200              : //   startDragGrip
     201              : //---------------------------------------------------------
     202              : 
     203            0 : void LineSegment::startDragGrip(EditData& ed)
     204              : {
     205            0 :     ElementEditDataPtr eed = ed.getData(this);
     206            0 :     if (!eed) {
     207            0 :         return;
     208              :     }
     209            0 :     eed->pushProperty(Pid::OFFSET);
     210            0 :     eed->pushProperty(Pid::OFFSET2);
     211            0 :     eed->pushProperty(Pid::AUTOPLACE);
     212            0 :     if (ed.modifiers & AltModifier) {
     213            0 :         setAutoplace(false);
     214              :     }
     215              : 
     216            0 :     EditTimeTickAnchors::updateAnchors(this);
     217            0 : }
     218              : 
     219            0 : bool LineSegment::isEditAllowed(EditData& ed) const
     220              : {
     221            0 :     return ed.modifiers & ShiftModifier;
     222              : }
     223              : 
     224              : //---------------------------------------------------------
     225              : //   edit
     226              : //    return true if event is accepted
     227              : //---------------------------------------------------------
     228              : 
     229            0 : bool LineSegment::edit(EditData& ed)
     230              : {
     231            0 :     const bool moveStart = ed.curGrip == Grip::START;
     232            0 :     const bool moveEnd = ed.curGrip == Grip::END || ed.curGrip == Grip::MIDDLE;
     233              : 
     234            0 :     LineSegment* ls       = 0;
     235            0 :     SpannerSegmentType st = spannerSegmentType();   // may change later
     236            0 :     SLine* l              = line();
     237              : 
     238            0 :     if (!isEditAllowed(ed)) {
     239            0 :         return false;
     240              :     }
     241              : 
     242            0 :     switch (l->anchor()) {
     243            0 :     case Spanner::Anchor::SEGMENT:
     244              :     {
     245            0 :         Segment* s1 = spanner()->startSegment();
     246            0 :         Segment* s2 = spanner()->endSegment();
     247              :         // check for line going to end of score
     248            0 :         if (spanner()->tick2() >= score()->lastSegment()->tick()) {
     249              :             // endSegment calculated above will be the last chord/rest of score
     250              :             // but that is not correct - it should be an imaginary note *after* the end of the score
     251              :             // best we can do is set s2 to lastSegment (probably the end barline)
     252            0 :             s2 = score()->lastSegment();
     253              :         }
     254            0 :         if (!s1 && !s2) {
     255            0 :             LOGD("LineSegment::edit: no start/end segment");
     256            0 :             return true;
     257              :         }
     258            0 :         if (moveStart) {
     259            0 :             s1 = MoveElementAnchors::findNewAnchorSegmentForLine(this, ed, s1);
     260              :         } else {
     261            0 :             s2 = MoveElementAnchors::findNewAnchorSegmentForLine(this, ed, s2);
     262              :         }
     263            0 :         if (!s1 || !s2 || s1->tick() >= s2->tick()) {
     264            0 :             return true;
     265              :         }
     266              : 
     267            0 :         undoMoveStartEndAndSnappedItems(ed, moveStart, moveEnd, s1, s2);
     268              : 
     269            0 :         EditTimeTickAnchors::updateAnchors(this);
     270              :     }
     271            0 :     break;
     272            0 :     case Spanner::Anchor::NOTE:
     273              :     {
     274            0 :         Note* note1       = toNote(l->startElement());
     275            0 :         Note* note2       = toNote(l->endElement());
     276            0 :         Note* oldNote1    = note1;
     277            0 :         Note* oldNote2    = note2;
     278            0 :         if (!note1 && !note2) {
     279            0 :             LOGD("LineSegment::edit: no start/end note");
     280            0 :             return true;                        // accept the event without doing anything
     281              :         }
     282              : 
     283            0 :         switch (ed.key) {
     284            0 :         case Key_Left:
     285            0 :             if (moveStart) {
     286            0 :                 note1 = prevChordNote(note1);
     287            0 :             } else if (moveEnd) {
     288            0 :                 note2 = prevChordNote(note2);
     289              :             }
     290            0 :             break;
     291            0 :         case Key_Right:
     292            0 :             if (moveStart) {
     293            0 :                 note1 = nextChordNote(note1);
     294            0 :             } else if (moveEnd) {
     295            0 :                 note2 = nextChordNote(note2);
     296              :             }
     297            0 :             break;
     298            0 :         case Key_Up:
     299            0 :             if (moveStart) {
     300            0 :                 note1 = toNote(Navigation::chordNoteAbove(score(), note1));
     301            0 :             } else if (moveEnd) {
     302            0 :                 note2 = toNote(Navigation::chordNoteAbove(score(), note2));
     303              :             }
     304            0 :             break;
     305            0 :         case Key_Down:
     306            0 :             if (moveStart) {
     307            0 :                 note1 = toNote(Navigation::chordNoteBelow(score(), note1));
     308            0 :             } else if (moveEnd) {
     309            0 :                 note2 = toNote(Navigation::chordNoteBelow(score(), note2));
     310              :             }
     311            0 :             break;
     312            0 :         default:
     313            0 :             return true;
     314              :         }
     315              : 
     316              :         // check prevChordNote() and nextchordNote() didn't return null
     317              :         // OR Score::upAlt() and Score::downAlt() didn't return non-Note (notably rests)
     318              :         // OR spanner duration is > 0
     319              :         // OR note1 and note2 didn't end up in different instruments
     320              :         // if this is the case, accepts the event and return without doing nothing
     321            0 :         if (!note1 || !note2
     322            0 :             || !note1->isNote() || !note2->isNote()
     323            0 :             || note1->chord()->tick() >= note2->chord()->tick()
     324            0 :             || note1->chord()->originPart()->instrument(note1->chord()->tick())
     325            0 :             != note2->chord()->originPart()->instrument(note2->chord()->tick())) {
     326            0 :             return true;
     327              :         }
     328            0 :         if (note1 != oldNote1 || note2 != oldNote2) {
     329            0 :             score()->undoChangeSpannerElements(spanner(), note1, note2);
     330              :         }
     331              :     }
     332            0 :     break;
     333            0 :     case Spanner::Anchor::MEASURE:
     334              :     case Spanner::Anchor::CHORDREST:
     335              :     {
     336            0 :         Measure* m1 = l->startMeasure();
     337            0 :         Measure* m2 = l->endMeasure();
     338              : 
     339            0 :         if (ed.key == Key_Left) {
     340            0 :             if (moveStart) {
     341            0 :                 if (m1->prevMeasure()) {
     342            0 :                     m1 = m1->prevMeasure();
     343              :                 }
     344            0 :             } else if (moveEnd) {
     345            0 :                 Measure* m = m2->prevMeasure();
     346            0 :                 if (m) {
     347            0 :                     m2 = m;
     348              :                 }
     349              :             }
     350            0 :         } else if (ed.key == Key_Right) {
     351            0 :             if (moveStart) {
     352            0 :                 if (m1->nextMeasure()) {
     353            0 :                     m1 = m1->nextMeasure();
     354              :                 }
     355            0 :             } else if (moveEnd) {
     356            0 :                 if (m2->nextMeasure()) {
     357            0 :                     m2 = m2->nextMeasure();
     358              :                 }
     359              :             }
     360            0 :         } else if (ed.key == Key_Home) {
     361            0 :             if (moveStart) {
     362            0 :                 m1 = m1->system()->firstMeasure();
     363            0 :             } else if (moveEnd) {
     364            0 :                 m2 = m2->system()->firstMeasure();
     365              :             }
     366            0 :         } else if (ed.key == Key_End) {
     367            0 :             if (moveStart) {
     368            0 :                 m1 = m1->system()->lastMeasure();
     369            0 :             } else if (moveEnd) {
     370            0 :                 m2 = m2->system()->lastMeasure();
     371              :             }
     372              :         }
     373            0 :         if (m1->tick() > m2->tick()) {
     374            0 :             return true;
     375              :         }
     376            0 :         if (l->startElement() != m1) {
     377            0 :             spanner()->undoChangeProperty(Pid::SPANNER_TICK,  m1->tick());
     378            0 :             spanner()->undoChangeProperty(Pid::SPANNER_TICKS, m2->endTick() - m1->tick());
     379            0 :         } else if (l->endElement() != m2) {
     380            0 :             spanner()->undoChangeProperty(Pid::SPANNER_TICKS, m2->endTick() - m1->tick());
     381              :         }
     382              :     }
     383              :     }
     384            0 :     triggerLayout();
     385              :     // recompute segment list, segment type may change
     386            0 :     renderer()->layoutItem(l);
     387              : 
     388            0 :     LineSegment* nls = 0;
     389            0 :     if (st == SpannerSegmentType::SINGLE) {
     390            0 :         if (moveStart) {
     391            0 :             nls = l->frontSegment();
     392            0 :         } else if (moveEnd) {
     393            0 :             nls = l->backSegment();
     394              :         }
     395            0 :     } else if (st == SpannerSegmentType::BEGIN) {
     396            0 :         nls = l->frontSegment();
     397            0 :     } else if (st == SpannerSegmentType::END) {
     398            0 :         nls = l->backSegment();
     399              :     } else {
     400            0 :         LOGD("spannerSegmentType %d", int(spannerSegmentType()));
     401              :     }
     402              : 
     403            0 :     if (nls && (nls != this)) {
     404            0 :         ed.view()->changeEditElement(nls);
     405              :     }
     406            0 :     if (ls) {
     407            0 :         score()->undoRemoveElement(ls);
     408              :     }
     409              : 
     410            0 :     triggerLayout();
     411            0 :     return true;
     412              : }
     413              : 
     414              : //---------------------------------------------------------
     415              : //   findSegmentForGrip
     416              : //---------------------------------------------------------
     417              : 
     418            0 : Segment* LineSegment::findSegmentForGrip(Grip grip, PointF pos) const
     419              : {
     420            0 :     if (grip != Grip::START && grip != Grip::END) {
     421            0 :         return nullptr;
     422              :     }
     423              : 
     424            0 :     SLine* l = line();
     425            0 :     const bool left = (grip == Grip::START);
     426              : 
     427            0 :     const staff_idx_t oldStaffIndex = left ? staffIdx() : track2staff(l->effectiveTrack2());
     428              : 
     429            0 :     const double spacingFactor = 0.5;   // defines the point where canvas is divided between segments, systems etc.
     430              : 
     431            0 :     System* sys = system();
     432            0 :     const std::vector<System*> foundSystems = score()->searchSystem(pos, /*includeMeasurelessSystems*/ false, sys, spacingFactor);
     433              : 
     434            0 :     if (!foundSystems.empty() && !muse::contains(foundSystems, sys) && foundSystems[0]->staves().size()) {
     435            0 :         sys = foundSystems[0];
     436              :     }
     437              : 
     438            0 :     if (!sys) {
     439            0 :         return nullptr;
     440              :     }
     441              : 
     442              :     // Restrict searching segment to the correct staff
     443            0 :     pos.setY(sys->staffCanvasYpage(oldStaffIndex));
     444              : 
     445            0 :     Segment* seg = nullptr;   // don't prefer any segment while searching line position
     446            0 :     staff_idx_t staffIndex = oldStaffIndex;
     447            0 :     score()->dragPosition(pos, &staffIndex, &seg, spacingFactor, allowTimeAnchor());
     448              : 
     449              :     // Lines that don't allow time anchors can anchor to the last segment in the score (see also LineSegment::findNewAnchorSegment)
     450            0 :     if (seg && !allowTimeAnchor() && grip == Grip::END) {
     451            0 :         Segment* last = score()->lastSegment();
     452            0 :         const bool lastSegIsClosest = std::abs(last->canvasX() - pos.x()) < std::abs(seg->canvasX() - pos.x());
     453            0 :         if (lastSegIsClosest) {
     454            0 :             return last;
     455              :         }
     456              :     }
     457              : 
     458            0 :     return seg;
     459            0 : }
     460              : 
     461              : //---------------------------------------------------------
     462              : //   deltaRebaseLeft
     463              : ///   Helper function for anchors rebasing when dragging.
     464              : //---------------------------------------------------------
     465              : 
     466            0 : PointF LineSegment::deltaRebaseLeft(const Segment* oldSeg, const Segment* newSeg)
     467              : {
     468            0 :     if (oldSeg == newSeg) {
     469            0 :         return PointF();
     470              :     }
     471            0 :     return oldSeg->canvasPos() - newSeg->canvasPos();
     472              : }
     473              : 
     474              : //---------------------------------------------------------
     475              : //   deltaRebaseRight
     476              : ///   Helper function for anchors rebasing when dragging.
     477              : //---------------------------------------------------------
     478              : 
     479            0 : PointF LineSegment::deltaRebaseRight(const Segment* oldSeg, const Segment* newSeg)
     480              : {
     481            0 :     if (oldSeg == newSeg) {
     482            0 :         return PointF();
     483              :     }
     484              : 
     485            0 :     const PointF oldBase = oldSeg->canvasPos();
     486            0 :     const PointF newBase = newSeg->canvasPos();
     487            0 :     return oldBase - newBase;
     488              : }
     489              : 
     490              : //---------------------------------------------------------
     491              : //   lastSegmentEndTick
     492              : ///   Helper function for anchors rebasing when dragging.
     493              : //---------------------------------------------------------
     494              : 
     495            0 : Fraction LineSegment::lastSegmentEndTick(const Segment* seg, const Spanner* s)
     496              : {
     497            0 :     return seg->tick() + seg->ticksInStaff(track2staff(s->effectiveTrack2()));
     498              : }
     499              : 
     500              : //---------------------------------------------------------
     501              : //   rebaseAnchor
     502              : ///   If system has changed, returns the new line segment
     503              : ///   (may appear to be reused \c this segment), otherwise
     504              : ///   returns nullptr.
     505              : //---------------------------------------------------------
     506              : 
     507            0 : LineSegment* LineSegment::rebaseAnchor(Grip grip, Segment* newSeg)
     508              : {
     509            0 :     switch (grip) {
     510            0 :     case Grip::START:
     511            0 :         if (!isSingleBeginType()) {
     512            0 :             return nullptr;
     513              :         }
     514            0 :         break;
     515            0 :     case Grip::END:
     516            0 :         if (!isSingleEndType()) {
     517            0 :             return nullptr;
     518              :         }
     519            0 :         break;
     520            0 :     default:
     521            0 :         return nullptr;
     522              :     }
     523              : 
     524            0 :     SLine* l = line();
     525            0 :     const bool left = (grip == Grip::START);
     526            0 :     Segment* const oldSeg = left ? l->startSegment() : l->endSegment();
     527            0 :     System* const oldLineSegSystem = system();
     528            0 :     System* const oldSpannerSystem = oldSeg->measure()->system();
     529              : 
     530            0 :     if (!newSeg || oldSeg == newSeg) {
     531            0 :         return nullptr;
     532              :     }
     533              : 
     534            0 :     Fraction newSegTick = newSeg->tick();
     535            0 :     Fraction startTick = left ? newSegTick : l->tick();
     536            0 :     Fraction endTick = left ? l->tick2() : (newSegTick == startTick ? newSegTick + newSeg->ticks() : newSegTick);
     537              : 
     538            0 :     if (endTick <= startTick) {
     539            0 :         if (left) {
     540            0 :             endTick = lastSegmentEndTick(newSeg, l);
     541              :         } else {
     542            0 :             startTick = newSeg->tick();
     543              :         }
     544              :     }
     545              : 
     546            0 :     bool anchorChanged = false;
     547              : 
     548            0 :     System* lineSegSys = system();
     549            0 :     PointF oldPos = line()->linePos(grip, &lineSegSys);
     550              : 
     551            0 :     if (l->tick() != startTick) {
     552            0 :         l->undoChangeProperty(Pid::SPANNER_TICK, startTick);
     553            0 :         anchorChanged = true;
     554              :     }
     555              : 
     556            0 :     const Fraction ticksLength = endTick - startTick;
     557            0 :     if (ticksLength != l->ticks()) {
     558            0 :         l->undoChangeProperty(Pid::SPANNER_TICKS, ticksLength);
     559            0 :         anchorChanged = true;
     560              :     }
     561              : 
     562            0 :     const Segment* newTickSeg = left ? l->startSegment() : l->endSegment();
     563            0 :     const System* newSpannerSystem = newTickSeg->measure()->system();
     564              : 
     565            0 :     if ((oldLineSegSystem && newSeg->system() != oldLineSegSystem) || oldSpannerSystem != newSpannerSystem) {
     566            0 :         renderer()->layoutItem(l);
     567            0 :         return l->segmentsEmpty() ? nullptr : left ? l->frontSegment() : l->backSegment();
     568            0 :     } else if (anchorChanged) {
     569            0 :         rebaseOffsetsOnAnchorChanged(grip, oldPos, oldLineSegSystem);
     570              :     }
     571              : 
     572            0 :     return nullptr;
     573              : }
     574              : 
     575            0 : void LineSegment::rebaseOffsetsOnAnchorChanged(Grip grip, const PointF& oldPos, System* sys)
     576              : {
     577            0 :     PointF newPos = line()->linePos(grip, &sys);
     578            0 :     PointF delta = oldPos - newPos;
     579            0 :     if (grip == Grip::START) {
     580            0 :         setOffset(offset() + delta);
     581            0 :         m_offset2 -= delta;
     582              :     } else {
     583            0 :         m_offset2 += delta;
     584              :     }
     585            0 : }
     586              : 
     587              : //---------------------------------------------------------
     588              : //   rebaseAnchors
     589              : //---------------------------------------------------------
     590              : 
     591            0 : void LineSegment::rebaseAnchors(EditData& ed, Grip grip)
     592              : {
     593            0 :     if (line()->anchor() != Spanner::Anchor::SEGMENT) {
     594            0 :         return;
     595              :     }
     596              : 
     597              :     // don't change anchors on keyboard adjustment or if Ctrl is pressed
     598              :     // (Ctrl+Left/Right is handled elsewhere!)
     599            0 :     if (ed.key == Key_Left || ed.key == Key_Right || ed.key == Key_Up || ed.key == Key_Down || ed.modifiers & ControlModifier) {
     600            0 :         return;
     601              :     }
     602              : 
     603            0 :     const Segment* startSeg = line()->startSegment();
     604            0 :     const Segment* endSeg = line()->endSegment();
     605            0 :     const double xDistanceFromStartSegment = startSeg && startSeg->system() == system()
     606            0 :                                              ? (startSeg->x() + startSeg->measure()->x()) - ldata()->pos().x() : 0.0;
     607            0 :     const double xDistanceFromEndSegment = endSeg && endSeg->system() == system()
     608            0 :                                            ? (endSeg->x() + endSeg->measure()->x()) - (ldata()->pos().x() + ipos2().x()) : 0.0;
     609              : 
     610            0 :     switch (grip) {
     611            0 :     case Grip::START:
     612              :     case Grip::END: {
     613            0 :         const bool left = (grip == Grip::START);
     614              : 
     615            0 :         if (left && !isSingleBeginType()) {
     616            0 :             return;
     617              :         }
     618            0 :         if (!left && !isSingleEndType()) {
     619            0 :             return;
     620              :         }
     621              : 
     622              :         // Find an appropriate segment to bind from actual mouse
     623              :         // position. This allows changing systems while dragging
     624              :         // while not setting line position to something
     625              :         // inappropriate.
     626            0 :         Segment* seg = findSegmentForGrip(grip, ed.pos
     627            0 :                                           + (left ? PointF(xDistanceFromStartSegment, 0.0) : PointF(xDistanceFromEndSegment, 0.0)));
     628            0 :         LineSegment* newLineSegment = rebaseAnchor(grip, seg);
     629              : 
     630            0 :         if (newLineSegment) {
     631              :             // If new line segment is not the same as this one,
     632              :             // switch to dragging that new segment.
     633            0 :             if (newLineSegment != this) {
     634              :                 // Reset offset for the old line segment
     635            0 :                 if (left) {
     636            0 :                     m_offset2.rx() -= offset().x();
     637            0 :                     setOffset(PointF());
     638              :                 } else {
     639            0 :                     setUserOff2(PointF());
     640              :                 }
     641              : 
     642              :                 // Switch to dragging the new line segment
     643            0 :                 ed.view()->changeEditElement(newLineSegment);
     644              :             }
     645              : 
     646              :             // Set offset for the new line segment for grip to appear under the mouse cursor
     647              :             System* sys;
     648            0 :             const PointF lp = line()->linePos(grip, &sys);
     649            0 :             const double xoff = sys->mapFromCanvas(ed.pos).x() - lp.x();
     650              : 
     651            0 :             if (left) {
     652            0 :                 newLineSegment->rxoffset() = xoff;
     653            0 :                 newLineSegment->setUserOff2(PointF(-xoff, 0.0));
     654              :             } else {
     655            0 :                 newLineSegment->setUserXoffset2(xoff);
     656              :             }
     657              :         }
     658              :     }
     659            0 :     break;
     660            0 :     case Grip::MIDDLE: {
     661            0 :         if (!isSingleType()) {
     662            0 :             return;
     663              :         }
     664              : 
     665            0 :         System* sys = system();
     666            0 :         if (!sys) {
     667            0 :             return;
     668              :         }
     669              : 
     670            0 :         SLine* l = line();
     671              : 
     672              :         // If dragging middle grip (or the entire hairpin), mouse position
     673              :         // does not directly correspond to any sensible position, so use
     674              :         // actual line coordinates instead. This method doesn't allow for
     675              :         // system changes, but that seems OK when dragging the entire line:
     676              :         // the line will just push away other systems according to autoplacement
     677              :         // rules if necessary.
     678            0 :         PointF cpos = canvasPos();
     679            0 :         cpos.setY(system()->staffCanvasYpage(l->staffIdx()));           // prevent cross-system move
     680              : 
     681            0 :         Segment* seg1 = findSegmentForGrip(Grip::START, cpos + PointF(xDistanceFromStartSegment, 0.0));
     682            0 :         Segment* seg2 = findSegmentForGrip(Grip::END, cpos + pos2() + PointF(xDistanceFromEndSegment, 0.0));
     683              : 
     684            0 :         if (!(seg1 && seg2 && seg1->system() == seg2->system() && seg1->system() == system())) {
     685            0 :             return;
     686              :         }
     687              : 
     688            0 :         PointF oldStartPos = line()->linePos(Grip::START, &sys);
     689            0 :         PointF oldEndPos = line()->linePos(Grip::END, &sys);
     690              : 
     691            0 :         undoMoveStartEndAndSnappedItems(ed, true, true, seg1, seg2);
     692              : 
     693            0 :         rebaseOffsetsOnAnchorChanged(Grip::START, oldStartPos, sys);
     694            0 :         rebaseOffsetsOnAnchorChanged(Grip::END, oldEndPos, sys);
     695              :     }
     696              :     default:
     697            0 :         break;
     698              :     }
     699              : }
     700              : 
     701              : //---------------------------------------------------------
     702              : //   dragGrip
     703              : //---------------------------------------------------------
     704              : 
     705            0 : void LineSegment::dragGrip(EditData& ed)
     706              : {
     707              :     // Only for resizing according to the diagonal properties
     708            0 :     const PointF deltaResize(ed.evtDelta.x(), line()->diagonal() ? ed.evtDelta.y() : 0.0);
     709              : 
     710            0 :     switch (ed.curGrip) {
     711            0 :     case Grip::START:         // Resize the begin of element (left grip)
     712            0 :         setOffset(offset() + deltaResize);
     713            0 :         m_offset2 -= deltaResize;
     714            0 :         rebaseAnchors(ed, ed.curGrip);
     715            0 :         break;
     716            0 :     case Grip::END:         // Resize the end of element (right grip)
     717            0 :         m_offset2 += deltaResize;
     718            0 :         rebaseAnchors(ed, ed.curGrip);
     719            0 :         break;
     720            0 :     case Grip::MIDDLE: {         // Move the element (middle grip)
     721              :         // Only for moving, no y limitation
     722            0 :         const PointF deltaMove(ed.evtDelta);
     723            0 :         setOffset(offset() + deltaMove);
     724            0 :         rebaseAnchors(ed, ed.curGrip);
     725              :     }
     726            0 :     break;
     727            0 :     default:
     728            0 :         UNREACHABLE;
     729              :         return;
     730              :     }
     731            0 :     if (line()->anchor() == Spanner::Anchor::NOTE && ed.isStartEndGrip()) {
     732              :         //
     733              :         // if we touch a different note, change anchor
     734              :         //
     735            0 :         EngravingItem* e = ed.view()->elementNear(ed.pos);
     736            0 :         if (e && e->isNote()) {
     737            0 :             SLine* l = line();
     738            0 :             if (ed.curGrip == Grip::END && e != line()->endElement()) {
     739            0 :                 LOGD("LineSegment: move end anchor");
     740            0 :                 Note* noteOld = toNote(l->endElement());
     741            0 :                 Note* noteNew = toNote(e);
     742            0 :                 Note* sNote   = toNote(l->startElement());
     743              :                 // do not change anchor if new note is before start note
     744            0 :                 if (sNote && sNote->chord() && noteNew->chord() && sNote->chord()->tick() < noteNew->chord()->tick()) {
     745            0 :                     score()->undoChangeSpannerElements(l, sNote, noteNew);
     746              : 
     747            0 :                     m_offset2 += noteOld->canvasPos() - noteNew->canvasPos();
     748              :                 }
     749            0 :             } else if (ed.curGrip == Grip::START && e != l->startElement()) {
     750            0 :                 LOGD("LineSegment: move start anchor (not impl.)");
     751              :             }
     752              :         }
     753              :     }
     754              : 
     755            0 :     EditTimeTickAnchors::updateAnchors(this);
     756              : 
     757            0 :     triggerLayout();
     758            0 : }
     759              : 
     760              : //---------------------------------------------------------
     761              : //   spatiumChanged
     762              : //---------------------------------------------------------
     763              : 
     764           12 : void LineSegment::spatiumChanged(double ov, double nv)
     765              : {
     766           12 :     EngravingItem::spatiumChanged(ov, nv);
     767           12 :     double scale = nv / ov;
     768           12 :     m_offset2 *= scale;
     769           12 : }
     770              : 
     771              : //---------------------------------------------------------
     772              : //   localSpatiumChanged
     773              : //---------------------------------------------------------
     774              : 
     775            0 : void LineSegment::localSpatiumChanged(double ov, double nv)
     776              : {
     777            0 :     EngravingItem::localSpatiumChanged(ov, nv);
     778            0 :     m_offset2 *= nv / ov;
     779            0 : }
     780              : 
     781              : //---------------------------------------------------------
     782              : //   propertyDelegate
     783              : //---------------------------------------------------------
     784              : 
     785         6596 : EngravingObject* LineSegment::propertyDelegate(Pid pid) const
     786              : {
     787         6596 :     if (pid == Pid::DIAGONAL
     788         6596 :         || pid == Pid::COLOR
     789         6584 :         || pid == Pid::LINE_WIDTH
     790         6584 :         || pid == Pid::LINE_STYLE
     791         6584 :         || pid == Pid::DASH_LINE_LEN
     792         6584 :         || pid == Pid::DASH_GAP_LEN) {
     793           12 :         return spanner();
     794              :     }
     795         6584 :     return SpannerSegment::propertyDelegate(pid);
     796              : }
     797              : 
     798              : //---------------------------------------------------------
     799              : //   dragAnchorLines
     800              : //---------------------------------------------------------
     801              : 
     802            0 : std::vector<LineF> LineSegment::dragAnchorLines() const
     803              : {
     804            0 :     return gripAnchorLines(Grip::MIDDLE);
     805              : }
     806              : 
     807            0 : RectF LineSegment::drag(EditData& ed)
     808              : {
     809            0 :     setOffset(offset() + ed.evtDelta);
     810            0 :     rebaseAnchors(ed, Grip::MIDDLE);
     811              : 
     812            0 :     return canvasBoundingRect();
     813              : }
     814              : 
     815         1096 : Spatium LineSegment::lineWidth() const
     816              : {
     817         1096 :     if (!line()) {
     818            0 :         return 0.0_sp;
     819              :     }
     820              : 
     821         1096 :     return line()->lineWidth();
     822              : }
     823              : 
     824         1667 : double LineSegment::absoluteFromSpatium(const Spatium& sp) const
     825              : {
     826         1667 :     if (!line()) {
     827            0 :         return EngravingItem::absoluteFromSpatium(sp);
     828              :     }
     829              : 
     830         1667 :     return line()->absoluteFromSpatium(sp);
     831              : }
     832              : 
     833            0 : void LineSegment::undoMoveStartEndAndSnappedItems(EditData& ed, bool moveStart, bool moveEnd, Segment* s1, Segment* s2)
     834              : {
     835            0 :     bool moveSnapped = !(ed.modifiers & AltModifier);
     836            0 :     SLine* thisLine = line();
     837            0 :     if (moveStart) {
     838            0 :         Fraction tickDiff = s1->tick() - thisLine->tick();
     839            0 :         if (EngravingItem* itemSnappedBefore = ldata()->itemSnappedBefore(); itemSnappedBefore && moveSnapped) {
     840            0 :             if (itemSnappedBefore->isTextBase()) {
     841            0 :                 MoveElementAnchors::moveSegment(itemSnappedBefore, s1, tickDiff);
     842            0 :             } else if (itemSnappedBefore->isLineSegment()) {
     843            0 :                 toLineSegment(itemSnappedBefore)->line()->undoMoveEnd(tickDiff);
     844            0 :                 thisLine->undoMoveStart(tickDiff);
     845              :             }
     846              :         } else {
     847            0 :             thisLine->undoMoveStart(tickDiff);
     848              :         }
     849              :     }
     850            0 :     if (moveEnd) {
     851            0 :         Fraction tickDiff = s2->tick() - thisLine->tick2();
     852            0 :         if (EngravingItem* itemSnappedAfter = thisLine->backSegment()->ldata()->itemSnappedAfter(); itemSnappedAfter && moveSnapped) {
     853            0 :             if (itemSnappedAfter->isTextBase()) {
     854            0 :                 MoveElementAnchors::moveSegment(itemSnappedAfter, s2, tickDiff);
     855            0 :             } else if (itemSnappedAfter->isLineSegment()) {
     856            0 :                 toLineSegment(itemSnappedAfter)->line()->undoMoveStart(tickDiff);
     857            0 :                 thisLine->undoMoveEnd(tickDiff);
     858              :             }
     859              :         } else {
     860            0 :             thisLine->undoMoveEnd(tickDiff);
     861              :         }
     862              :     }
     863            0 : }
     864              : 
     865              : //---------------------------------------------------------
     866              : //   SLine
     867              : //---------------------------------------------------------
     868              : 
     869         2414 : SLine::SLine(const ElementType& type, EngravingObject* parent, ElementFlags f)
     870         2414 :     : Spanner(type, parent, f)
     871              : {
     872         2414 :     setTrack(0);
     873         2414 :     m_lineColor = configuration()->defaultColor();
     874         2414 :     m_lineWidth = 0.15_sp;
     875         2414 : }
     876              : 
     877          281 : SLine::SLine(const SLine& s)
     878          281 :     : Spanner(s)
     879              : {
     880          281 :     m_diagonal    = s.m_diagonal;
     881          281 :     m_lineWidth   = s.m_lineWidth;
     882          281 :     m_lineColor   = s.m_lineColor;
     883          281 :     m_lineStyle   = s.m_lineStyle;
     884          281 :     m_dashLineLen = s.m_dashLineLen;
     885          281 :     m_dashGapLen  = s.m_dashGapLen;
     886          281 : }
     887              : 
     888         1401 : PointF SLine::linePos(Grip grip, System** system) const
     889              : {
     890         1401 :     bool start = grip == Grip::START;
     891         1401 :     bool mmRest = style().styleB(Sid::createMultiMeasureRests);
     892              : 
     893         1401 :     switch (anchor()) {
     894          920 :     case Spanner::Anchor::NOTE:
     895              :     {
     896          920 :         EngravingItem* item = start ? startElement() : endElement();
     897          920 :         Note* note = item && item->isNote() ? toNote(item) : nullptr;
     898          920 :         if (!note) {
     899            0 :             return PointF();
     900              :         }
     901          920 :         *system = note->chord()->segment()->measure()->system();
     902          920 :         if (!(*system)) {
     903            0 :             return PointF();
     904              :         }
     905          920 :         return note->pagePos() - (*system)->pagePos();
     906              :     }
     907          481 :     case Spanner::Anchor::CHORDREST:
     908              :     case Spanner::Anchor::SEGMENT:
     909              :     {
     910          481 :         Segment* segment = start ? startSegment() : endSegment();
     911          481 :         if (!segment) {
     912            0 :             return PointF();
     913              :         }
     914          481 :         if (anchor() == Spanner::Anchor::CHORDREST && !segment->isChordRestType()) {
     915            0 :             return PointF();
     916              :         }
     917          481 :         if (!start) {
     918          163 :             Fraction curTick = segment->tick();
     919              :             while (true) {
     920          239 :                 Segment* prevSeg = mmRest ? segment->prev1MM() : segment->prev1();
     921          239 :                 if (prevSeg && prevSeg->tick() == curTick) {
     922           76 :                     segment = prevSeg;
     923              :                 } else {
     924          163 :                     break;
     925              :                 }
     926           76 :             }
     927              :         }
     928          481 :         *system = segment->measure()->system();
     929          481 :         double x = segment->x() + segment->measure()->x() - (start ? 0.0 : spatium());
     930          481 :         return PointF(x, 0.0);
     931              :     }
     932            0 :     case Spanner::Anchor::MEASURE:
     933              :     {
     934            0 :         Measure* measure = score()->tick2measureMM(start ? tick() : tick2());
     935            0 :         if (!measure) {
     936            0 :             return PointF();
     937              :         }
     938            0 :         *system = measure->system();
     939            0 :         double x = measure->x();
     940            0 :         return PointF(x, 0.0);
     941              :     }
     942            0 :     default:
     943            0 :         return PointF();
     944              :     }
     945              : }
     946              : 
     947              : //---------------------------------------------------------
     948              : //   setLen
     949              : //    used to create an element suitable for palette
     950              : //---------------------------------------------------------
     951              : 
     952            0 : void SLine::setLen(double l)
     953              : {
     954            0 :     if (spannerSegments().empty()) {
     955            0 :         add(createLineSegment());
     956              :     }
     957            0 :     LineSegment* s = frontSegment();
     958            0 :     s->setPos(PointF());
     959            0 :     s->setPos2(PointF(l, 0));
     960            0 : }
     961              : 
     962              : //---------------------------------------------------------
     963              : //   getProperty
     964              : //---------------------------------------------------------
     965              : 
     966        13710 : PropertyValue SLine::getProperty(Pid id) const
     967              : {
     968        13710 :     switch (id) {
     969            0 :     case Pid::DIAGONAL:
     970            0 :         return m_diagonal;
     971         1089 :     case Pid::COLOR:
     972         1089 :         return PropertyValue::fromValue(m_lineColor);
     973          424 :     case Pid::LINE_WIDTH:
     974          424 :         return m_lineWidth;
     975          429 :     case Pid::LINE_STYLE:
     976          429 :         return m_lineStyle;
     977          416 :     case Pid::DASH_LINE_LEN:
     978          416 :         return dashLineLen();
     979          416 :     case Pid::DASH_GAP_LEN:
     980          416 :         return dashGapLen();
     981        10936 :     default:
     982        10936 :         return Spanner::getProperty(id);
     983              :     }
     984              : }
     985              : 
     986              : //---------------------------------------------------------
     987              : //   setProperty
     988              : //---------------------------------------------------------
     989              : 
     990        13360 : bool SLine::setProperty(Pid id, const PropertyValue& v)
     991              : {
     992        13360 :     switch (id) {
     993          269 :     case Pid::DIAGONAL:
     994          269 :         m_diagonal = v.toBool();
     995          269 :         break;
     996          105 :     case Pid::COLOR:
     997          105 :         setLineColor(v.value<Color>());
     998          105 :         break;
     999         3341 :     case Pid::LINE_WIDTH:
    1000         3341 :         if (v.type() == P_TYPE::ABSOLUTE) {
    1001            0 :             m_lineWidth = Spatium::fromAbsolute(v.value<double>(), spatium());
    1002         3341 :         } else if (v.type() == P_TYPE::SPATIUM) {
    1003         3341 :             m_lineWidth = v.value<Spatium>();
    1004              :         }
    1005         3341 :         break;
    1006         2697 :     case Pid::LINE_STYLE:
    1007         2697 :         m_lineStyle = v.value<LineType>();
    1008         2697 :         break;
    1009         2536 :     case Pid::DASH_LINE_LEN:
    1010         2536 :         setDashLineLen(v.toDouble());
    1011         2536 :         break;
    1012         2536 :     case Pid::DASH_GAP_LEN:
    1013         2536 :         setDashGapLen(v.toDouble());
    1014         2536 :         break;
    1015         1876 :     default:
    1016         1876 :         return Spanner::setProperty(id, v);
    1017              :     }
    1018        11484 :     triggerLayout();
    1019        11484 :     return true;
    1020              : }
    1021              : 
    1022              : //---------------------------------------------------------
    1023              : //   propertyDefault
    1024              : //---------------------------------------------------------
    1025              : 
    1026        14325 : PropertyValue SLine::propertyDefault(Pid pid) const
    1027              : {
    1028        14325 :     switch (pid) {
    1029            0 :     case Pid::DIAGONAL:
    1030            0 :         return false;
    1031         1085 :     case Pid::COLOR:
    1032         1085 :         return PropertyValue::fromValue(configuration()->defaultColor());
    1033          357 :     case Pid::LINE_WIDTH:
    1034          357 :         if (propertyFlags(pid) != PropertyFlags::NOSTYLE) {
    1035            9 :             return Spanner::propertyDefault(pid);
    1036              :         }
    1037          348 :         return 0.15_sp;
    1038          374 :     case Pid::LINE_STYLE:
    1039          374 :         if (propertyFlags(pid) != PropertyFlags::NOSTYLE) {
    1040           18 :             return Spanner::propertyDefault(pid);
    1041              :         }
    1042          356 :         return LineType::SOLID;
    1043          752 :     case Pid::DASH_LINE_LEN:
    1044              :     case Pid::DASH_GAP_LEN:
    1045          752 :         if (propertyFlags(pid) != PropertyFlags::NOSTYLE) {
    1046           40 :             return Spanner::propertyDefault(pid);
    1047              :         }
    1048          712 :         return 5.0;
    1049        11757 :     default:
    1050        11757 :         return Spanner::propertyDefault(pid);
    1051              :     }
    1052              : }
    1053              : 
    1054            0 : void SLine::undoMoveStart(Fraction tickDiff)
    1055              : {
    1056            0 :     Fraction newTick = tick() + tickDiff;
    1057            0 :     if (newTick >= Fraction(0, 1)) {
    1058            0 :         undoChangeProperty(Pid::SPANNER_TICK, newTick);
    1059              :     }
    1060            0 :     Fraction newDuration = ticks() - tickDiff;
    1061            0 :     if (newDuration > Fraction(0, 1)) {
    1062            0 :         undoChangeProperty(Pid::SPANNER_TICKS, newDuration);
    1063              :     }
    1064            0 : }
    1065              : 
    1066            0 : void SLine::undoMoveEnd(Fraction tickDiff)
    1067              : {
    1068            0 :     Fraction newDuration = ticks() + tickDiff;
    1069            0 :     if (newDuration > Fraction(0, 1)) {
    1070            0 :         undoChangeProperty(Pid::SPANNER_TICKS, newDuration);
    1071              :     } else {
    1072            0 :         Fraction newTick = tick() + tickDiff;
    1073            0 :         if (newTick >= Fraction(0, 1)) {
    1074            0 :             undoChangeProperty(Pid::SPANNER_TICK, tick() + tickDiff);
    1075              :         }
    1076              :     }
    1077            0 : }
    1078              : 
    1079           11 : Note* SLine::guessFinalNote(Note* startNote)
    1080              : {
    1081           11 :     Chord* chord = startNote->chord();
    1082           11 :     if (chord->isGraceBefore()) {
    1083            1 :         Chord* parentChord = toChord(chord->parent());
    1084            1 :         GraceNotesGroup& gracesBefore = parentChord->graceNotesBefore();
    1085            1 :         auto positionOfThis = std::find(gracesBefore.begin(), gracesBefore.end(), chord);
    1086            1 :         if (positionOfThis != gracesBefore.end()) {
    1087            1 :             auto nextPosition = ++positionOfThis;
    1088            1 :             if (nextPosition != gracesBefore.end()) {
    1089            0 :                 return (*nextPosition)->upNote();
    1090              :             }
    1091              :         }
    1092            1 :         return parentChord->upNote();
    1093           10 :     } else if (chord->isGraceAfter()) {
    1094            1 :         Chord* parentChord = toChord(chord->parent());
    1095            1 :         GraceNotesGroup& gracesAfter = parentChord->graceNotesAfter();
    1096            1 :         auto positionOfThis = std::find(gracesAfter.begin(), gracesAfter.end(), chord);
    1097            1 :         if (positionOfThis != gracesAfter.end()) {
    1098            1 :             auto nextPosition = ++positionOfThis;
    1099            1 :             if (nextPosition != gracesAfter.end()) {
    1100            0 :                 return (*nextPosition)->upNote();
    1101              :             }
    1102              :         }
    1103            1 :         chord = toChord(chord->parent());
    1104              :     } else {
    1105            9 :         std::vector<Chord*> graces = chord->graceNotesAfter();
    1106            9 :         if (graces.size() > 0) {
    1107            1 :             return graces.front()->upNote();
    1108              :         }
    1109            9 :     }
    1110              : 
    1111            9 :     if (!chord->ownershipParent()->isSegment()) {
    1112            0 :         return 0;
    1113              :     }
    1114              : 
    1115            9 :     Segment* segm = chord->score()->tick2rightSegment(chord->endTick());
    1116            9 :     while (segm && !segm->isChordRestType()) {
    1117            0 :         segm = segm->next1();
    1118              :     }
    1119              : 
    1120            9 :     if (!segm) {
    1121            0 :         return nullptr;
    1122              :     }
    1123              : 
    1124            9 :     track_idx_t chordTrack = chord->track();
    1125            9 :     Part* part = chord->part();
    1126              : 
    1127            9 :     Chord* target = nullptr;
    1128            9 :     if (segm->element(chordTrack) && segm->element(chordTrack)->isChord()) {
    1129            7 :         target = toChord(segm->element(chordTrack));
    1130              :     } else {
    1131            6 :         for (EngravingItem* currChord : segm->elist()) {
    1132            6 :             if (currChord && currChord->isChord() && toChord(currChord)->part() == part) {
    1133            2 :                 target = toChord(currChord);
    1134            2 :                 break;
    1135              :             }
    1136              :         }
    1137              :     }
    1138              : 
    1139            9 :     if (target && target->notes().size() > 0) {
    1140            9 :         const std::vector<Chord*>& graces = target->graceNotesBefore();
    1141            9 :         if (graces.size() > 0) {
    1142            2 :             return graces.front()->upNote();
    1143              :         }
    1144              :         // normal case: try to return the note in the next chord that is in the
    1145              :         // same position as the start note relative to the end chord
    1146            7 :         size_t startNoteIdx = muse::indexOf(chord->notes(), startNote);
    1147            7 :         size_t endNoteIdx = std::min(startNoteIdx, target->notes().size() - 1);
    1148            7 :         return target->notes().at(endNoteIdx);
    1149              :     }
    1150              : 
    1151            0 :     LOGD("no second note for note anchored line found");
    1152            0 :     return nullptr;
    1153              : }
    1154              : }
        

Generated by: LCOV version 2.0-1