LCOV - code coverage report
Current view: top level - src/engraving/dom - lyricsline.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 89.1 % 202 180
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 "lyrics.h"
      24              : 
      25              : #include "../editing/navigation.h"
      26              : 
      27              : #include "chord.h"
      28              : #include "chordrest.h"
      29              : #include "factory.h"
      30              : #include "measure.h"
      31              : #include "note.h"
      32              : #include "score.h"
      33              : #include "segment.h"
      34              : #include "system.h"
      35              : #include "utils.h"
      36              : 
      37              : using namespace mu;
      38              : 
      39              : namespace mu::engraving {
      40              : //---------------------------------------------------------
      41              : //   LyricsLine
      42              : //---------------------------------------------------------
      43              : 
      44              : static const ElementStyle lyricsLineElementStyle {
      45              :     { Sid::lyricsDashLineThickness, Pid::LINE_WIDTH }
      46              : };
      47              : 
      48          247 : LyricsLine::LyricsLine(DummyParentOr<EngravingItem> parent)
      49          247 :     : SLine(ElementType::LYRICSLINE, parent)
      50              : {
      51          247 :     setDiagonal(false);
      52          247 :     initElementStyle(&lyricsLineElementStyle);
      53          247 :     m_nextLyrics = 0;
      54          247 : }
      55              : 
      56           11 : LyricsLine::LyricsLine(const ElementType& type, DummyParentOr<EngravingItem> parent, ElementFlags f)
      57           11 :     : SLine(type, parent, f)
      58              : {
      59           11 :     setDiagonal(false);
      60           11 :     initElementStyle(&lyricsLineElementStyle);
      61           11 :     m_nextLyrics = 0;
      62           11 : }
      63              : 
      64            0 : LyricsLine::LyricsLine(const LyricsLine& g)
      65            0 :     : SLine(g)
      66              : {
      67            0 :     m_nextLyrics = 0;
      68            0 : }
      69              : 
      70              : //---------------------------------------------------------
      71              : //   createLineSegment
      72              : //---------------------------------------------------------
      73              : 
      74          250 : LineSegment* LyricsLine::createLineSegment()
      75              : {
      76          250 :     LyricsLineSegment* seg = new LyricsLineSegment(this);
      77          250 :     seg->setTrack(track());
      78          250 :     seg->setColor(color());
      79          250 :     return seg;
      80              : }
      81              : 
      82              : //---------------------------------------------------------
      83              : //   removeUnmanaged
      84              : //    same as Spanner::removeUnmanaged(), but in addition, remove from hosting Lyrics
      85              : //---------------------------------------------------------
      86              : 
      87          235 : void LyricsLine::removeUnmanaged()
      88              : {
      89          235 :     Spanner::removeUnmanaged();
      90          235 :     if (lyrics()) {
      91          235 :         lyrics()->remove(this);
      92              :     }
      93          235 : }
      94              : 
      95              : //---------------------------------------------------------
      96              : //   setProperty
      97              : //---------------------------------------------------------
      98              : 
      99          625 : bool LyricsLine::setProperty(Pid propertyId, const engraving::PropertyValue& v)
     100              : {
     101          625 :     switch (propertyId) {
     102            2 :     case Pid::SPANNER_TICKS:
     103              :     {
     104              :         // if parent lyrics has a melisma, change its length too
     105            4 :         if (ownershipParent() && ownershipParent()->isLyrics()
     106            4 :             && isEndMelisma()) {
     107            2 :             Fraction newTicks   = toLyrics(ownershipParent())->ticks() + v.value<Fraction>() - ticks();
     108            2 :             ownershipParent()->undoChangeProperty(Pid::LYRIC_TICKS, newTicks);
     109              :         }
     110            2 :         setTicks(v.value<Fraction>());
     111              :     }
     112            2 :     break;
     113          623 :     default:
     114          623 :         if (!SLine::setProperty(propertyId, v)) {
     115            0 :             return false;
     116              :         }
     117          623 :         break;
     118              :     }
     119          625 :     triggerLayout();
     120          625 :     return true;
     121              : }
     122              : 
     123          105 : PropertyValue LyricsLine::propertyDefault(Pid id) const
     124              : {
     125          105 :     switch (id) {
     126            4 :     case Pid::LINE_WIDTH:
     127            4 :         return styleValue(Pid::LINE_WIDTH, getPropertyStyle(Pid::LINE_WIDTH));
     128          101 :     default:
     129          101 :         return SLine::propertyDefault(id);
     130              :     }
     131              : }
     132              : 
     133          607 : Sid LyricsLine::getPropertyStyle(Pid propertyId) const
     134              : {
     135          607 :     if (propertyId == Pid::LINE_WIDTH) {
     136          605 :         return isEndMelisma() ? Sid::lyricsLineThickness : Sid::lyricsDashLineThickness;
     137              :     }
     138              : 
     139            2 :     return SLine::getPropertyStyle(propertyId);
     140              : }
     141              : 
     142          336 : void LyricsLine::doComputeEndElement()
     143              : {
     144          336 :     if (!isEndMelisma()) {
     145          232 :         Spanner::doComputeEndElement();
     146          232 :         return;
     147              :     }
     148              : 
     149          104 :     setEndElement(score()->findChordRestEndingBeforeTickInTrack(tick2(), track()));
     150              : 
     151          104 :     if (!endElement()) {
     152            1 :         setEndElement(score()->findChordRestEndingBeforeTickInStaff(tick2(), track2staff(track())));
     153              :     }
     154              : }
     155              : 
     156              : //=========================================================
     157              : //   LyricsLineSegment
     158              : //=========================================================
     159              : 
     160            0 : void LyricsLineSegment::rebaseAnchors(EditData&, Grip)
     161              : {
     162              :     // Don't rebase lyric line anchors on drag
     163            0 :     return;
     164              : }
     165              : 
     166          250 : LyricsLineSegment::LyricsLineSegment(LyricsLine* sp)
     167          250 :     : LineSegment(ElementType::LYRICSLINE_SEGMENT, sp, ElementFlag::ON_STAFF)
     168              : {
     169          250 : }
     170              : 
     171            3 : LyricsLineSegment::LyricsLineSegment(const ElementType& type, LyricsLine* sp, ElementFlags f)
     172            3 :     : LineSegment(type, sp, f)
     173              : {
     174            3 : }
     175              : 
     176          339 : double LyricsLineSegment::baseLineShift() const
     177              : {
     178          339 :     if (lyricsLine()->isEndMelisma()) {
     179          111 :         return -0.5 * absoluteFromSpatium(lineWidth());
     180              :     }
     181              : 
     182          228 :     Lyrics* segLyrics = lyrics();
     183          228 :     return -style().styleD(Sid::lyricsDashYposRatio) * segLyrics->fontMetrics().xHeight();
     184              : }
     185              : 
     186            3 : PropertyValue LyricsLineSegment::getProperty(Pid propertyId) const
     187              : {
     188            3 :     if (EngravingObject* delegate = propertyDelegate(propertyId)) {
     189            1 :         return delegate->getProperty(propertyId);
     190              :     }
     191              : 
     192            2 :     return LineSegment::getProperty(propertyId);
     193              : }
     194              : 
     195            4 : bool LyricsLineSegment::setProperty(Pid propertyId, const PropertyValue& val)
     196              : {
     197            4 :     if (EngravingObject* delegate = propertyDelegate(propertyId)) {
     198            1 :         return delegate->setProperty(propertyId, val);
     199              :     }
     200              : 
     201            3 :     return LineSegment::setProperty(propertyId, val);
     202              : }
     203              : 
     204            3 : PropertyValue LyricsLineSegment::propertyDefault(Pid propertyId) const
     205              : {
     206            3 :     if (EngravingObject* delegate = propertyDelegate(propertyId)) {
     207            1 :         return delegate->propertyDefault(propertyId);
     208              :     }
     209              : 
     210            2 :     return LineSegment::propertyDefault(propertyId);
     211              : }
     212              : 
     213           21 : EngravingObject* LyricsLineSegment::propertyDelegate(Pid propertyId) const
     214              : {
     215           21 :     if (propertyId == Pid::GENERATED) {
     216            0 :         return lyricsLine();
     217              :     }
     218              : 
     219           21 :     return LineSegment::propertyDelegate(propertyId);
     220              : }
     221              : 
     222              : //=========================================================
     223              : //   PartialLyricsLine
     224              : //=========================================================
     225              : 
     226              : static const ElementStyle partialLyricsLineElementStyle {
     227              :     { Sid::lyricsDashLineThickness, Pid::LINE_WIDTH },
     228              :     { Sid::lyricsPlacement,         Pid::PLACEMENT },
     229              : };
     230              : 
     231           11 : PartialLyricsLine::PartialLyricsLine(DummyParentOr<EngravingItem> parent)
     232           11 :     : LyricsLine(ElementType::PARTIAL_LYRICSLINE, parent)
     233              : {
     234           11 :     setGenerated(false);
     235           11 :     initElementStyle(&partialLyricsLineElementStyle);
     236           11 : }
     237              : 
     238            0 : PartialLyricsLine::PartialLyricsLine(const PartialLyricsLine& other)
     239            0 :     : LyricsLine(other)
     240              : {
     241            0 :     m_isEndMelisma = other.m_isEndMelisma;
     242            0 :     m_centerBetweenStaves = other.m_centerBetweenStaves;
     243            0 : }
     244              : 
     245            3 : LineSegment* PartialLyricsLine::createLineSegment()
     246              : {
     247            3 :     PartialLyricsLineSegment* seg = new PartialLyricsLineSegment(this);
     248            3 :     seg->setTrack(track());
     249            3 :     seg->setColor(color());
     250            3 :     return seg;
     251              : }
     252              : 
     253          107 : PropertyValue PartialLyricsLine::getProperty(Pid propertyId) const
     254              : {
     255          107 :     switch (propertyId) {
     256           12 :     case Pid::VERSE:
     257           12 :         return m_verse;
     258            8 :     case Pid::CENTER_BETWEEN_STAVES:
     259            8 :         return centerBetweenStaves();
     260           87 :     default:
     261           87 :         return LyricsLine::getProperty(propertyId);
     262              :     }
     263              : }
     264              : 
     265           36 : bool PartialLyricsLine::setProperty(Pid propertyId, const PropertyValue& val)
     266              : {
     267           36 :     switch (propertyId) {
     268            2 :     case Pid::VERSE:
     269            2 :         setVerse(val.toInt());
     270            2 :         break;
     271            4 :     case Pid::CENTER_BETWEEN_STAVES:
     272            4 :         setCenterBetweenStaves(val.value<AutoOnOff>());
     273            4 :         break;
     274           30 :     default:
     275           30 :         return LyricsLine::setProperty(propertyId, val);
     276              :     }
     277              : 
     278            6 :     triggerLayout();
     279              : 
     280            6 :     return true;
     281              : }
     282              : 
     283           90 : PropertyValue PartialLyricsLine::propertyDefault(Pid propertyId) const
     284              : {
     285           90 :     switch (propertyId) {
     286            6 :     case Pid::VERSE:
     287            6 :         return 0;
     288            7 :     case Pid::CENTER_BETWEEN_STAVES:
     289           14 :         return AutoOnOff::AUTO;
     290           77 :     default:
     291           77 :         return LyricsLine::propertyDefault(propertyId);
     292              :     }
     293              : }
     294              : 
     295            0 : void PartialLyricsLine::reset()
     296              : {
     297            0 :     undoResetProperty(Pid::CENTER_BETWEEN_STAVES);
     298            0 :     LyricsLine::reset();
     299            0 : }
     300              : 
     301           27 : Sid PartialLyricsLine::getPropertyStyle(Pid propertyId) const
     302              : {
     303           27 :     switch (propertyId) {
     304           14 :     case Pid::PLACEMENT:
     305           14 :         return Sid::lyricsPlacement;
     306           13 :     default:
     307           13 :         return LyricsLine::getPropertyStyle(propertyId);
     308              :     }
     309              : }
     310              : 
     311            1 : void PartialLyricsLine::undoChangeProperty(Pid id, const PropertyValue& v, PropertyFlags ps)
     312              : {
     313            1 :     if (id == Pid::VERSE && verse() != v.toInt()) {
     314            1 :         ChordRest* endCR = endElement()
     315            1 :                            && endElement()->isChordRest() ? toChordRest(endElement()) : nullptr;
     316            1 :         Lyrics* endLyrics = nullptr;
     317            1 :         if (endCR) {
     318            1 :             for (Lyrics* lyr : endCR->lyrics()) {
     319            1 :                 if (lyr->verse() == verse()) {
     320            1 :                     endLyrics = lyr;
     321            1 :                     break;
     322              :                 }
     323              :             }
     324              :         }
     325              : 
     326            1 :         LyricsLine::undoChangeProperty(id, v, ps);
     327            1 :         if (endLyrics && endLyrics->verse() != v.toInt()) {
     328            1 :             endLyrics->undoChangeProperty(id, v, ps);
     329              :         }
     330            1 :         return;
     331              :     }
     332              : 
     333            0 :     LyricsLine::undoChangeProperty(id, v, ps);
     334              : }
     335              : 
     336            8 : void PartialLyricsLine::doComputeEndElement()
     337              : {
     338            8 :     LyricsLine::doComputeEndElement();
     339              : 
     340            8 :     if (startElement() && endElement() && startElement()->tick() > endElement()->tick()) {
     341            0 :         setEndElement(startElement());
     342              :     }
     343            8 : }
     344              : 
     345              : //=========================================================
     346              : //   PartialLyricsLineSegment
     347              : //=========================================================
     348              : 
     349              : static const ElementStyle partialLyricsLineSegmentElementStyle {
     350              :     { Sid::lyricsMinTopDistance, Pid::MIN_DISTANCE },
     351              : };
     352              : 
     353            3 : PartialLyricsLineSegment::PartialLyricsLineSegment(PartialLyricsLine* line)
     354            3 :     : LyricsLineSegment(ElementType::PARTIAL_LYRICSLINE_SEGMENT, line, ElementFlag::ON_STAFF)
     355              : {
     356            3 :     setGenerated(false);
     357            3 :     initElementStyle(&partialLyricsLineSegmentElementStyle);
     358            3 : }
     359              : 
     360            6 : double PartialLyricsLineSegment::lineSpacing() const
     361              : {
     362            6 :     const Lyrics* lyrics = lyricsLine()->findAdjacentLyricsOrDefault();
     363              : 
     364            6 :     if (!lyrics) {
     365            0 :         return 0.0;
     366              :     }
     367              : 
     368            6 :     return lyrics->lineSpacing();
     369              : }
     370              : 
     371            6 : double PartialLyricsLineSegment::baseLineShift() const
     372              : {
     373            6 :     if (lyricsLine()->isEndMelisma()) {
     374            3 :         return -0.5 * absoluteFromSpatium(lineWidth());
     375              :     }
     376              : 
     377            3 :     const Lyrics* lyrics = lyricsLine()->findAdjacentLyricsOrDefault();
     378            3 :     if (!lyrics) {
     379            0 :         return 0.0;
     380              :     }
     381              : 
     382            3 :     return -style().styleD(Sid::lyricsDashYposRatio) * lyrics->fontMetrics().xHeight();
     383              : }
     384              : 
     385           24 : EngravingObject* PartialLyricsLineSegment::propertyDelegate(Pid pid) const
     386              : {
     387           24 :     switch (pid) {
     388            3 :     case Pid::VERSE:
     389              :     case Pid::CENTER_BETWEEN_STAVES:
     390            3 :         return lyricsLine();
     391           21 :     default:
     392           21 :         return LyricsLineSegment::propertyDelegate(pid);
     393              :     }
     394              : }
     395              : 
     396            9 : Lyrics* PartialLyricsLine::findLyricsInPreviousRepeatSeg() const
     397              : {
     398            9 :     const std::vector<Measure*> measures = findPreviousRepeatMeasures(findStartMeasure());
     399              : 
     400           15 :     for (const Measure* measure : measures) {
     401            9 :         Lyrics* prev = Navigation::lastLyricsInMeasure(measure->last(SegmentType::ChordRest), staffIdx(), verse(), placement());
     402              : 
     403            9 :         if (!prev) {
     404            6 :             continue;
     405              :         }
     406              : 
     407            3 :         return prev;
     408              :     }
     409              : 
     410            6 :     return nullptr;
     411            9 : }
     412              : 
     413            9 : Lyrics* PartialLyricsLine::findAdjacentLyricsOrDefault() const
     414              : {
     415            9 :     Lyrics* prev = findLyricsInPreviousRepeatSeg();
     416            9 :     if (prev) {
     417            3 :         return prev;
     418              :     }
     419              : 
     420            6 :     Lyrics* next = nextLyrics();
     421            6 :     if (next) {
     422            0 :         return next;
     423              :     }
     424              : 
     425              :     // If there are no adjacent lyrics, create dummy lyrics using the odd lyrics text style to get font information
     426            6 :     Lyrics* dummyLyr = Factory::createLyrics(score()->dummy());
     427            6 :     dummyLyr->setTextStyleType(TextStyleType::LYRICS_ODD);
     428            6 :     return dummyLyr;
     429              : }
     430              : }
        

Generated by: LCOV version 2.0-1