LCOV - code coverage report
Current view: top level - src/engraving/dom - hairpin.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 61.9 % 470 291
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 "hairpin.h"
      24              : 
      25              : #include <cmath>
      26              : 
      27              : #include "draw/types/transform.h"
      28              : 
      29              : #include "../editing/elementeditdata.h"
      30              : #include "types/typesconv.h"
      31              : 
      32              : #include "dynamic.h"
      33              : #include "dynamichairpingroup.h"
      34              : #include "measure.h"
      35              : #include "score.h"
      36              : #include "segment.h"
      37              : #include "system.h"
      38              : #include "text.h"
      39              : 
      40              : using namespace mu;
      41              : using namespace muse::draw;
      42              : using namespace mu::engraving;
      43              : 
      44              : namespace mu::engraving {
      45              : //---------------------------------------------------------
      46              : //   hairpinStyle
      47              : //---------------------------------------------------------
      48              : 
      49              : static const ElementStyle hairpinStyle {
      50              :     { Sid::hairpinFontFace,                    Pid::BEGIN_FONT_FACE },
      51              :     { Sid::hairpinFontSize,                    Pid::BEGIN_FONT_SIZE },
      52              :     { Sid::hairpinFontStyle,                   Pid::BEGIN_FONT_STYLE },
      53              :     { Sid::hairpinText,                        Pid::BEGIN_TEXT },
      54              :     { Sid::hairpinTextAlign,                   Pid::BEGIN_TEXT_ALIGN },
      55              :     { Sid::hairpinPosition,                    Pid::BEGIN_TEXT_POSITION },
      56              :     { Sid::hairpinFontFace,                    Pid::CONTINUE_FONT_FACE },
      57              :     { Sid::hairpinFontSize,                    Pid::CONTINUE_FONT_SIZE },
      58              :     { Sid::hairpinFontStyle,                   Pid::CONTINUE_FONT_STYLE },
      59              :     { Sid::hairpinText,                        Pid::CONTINUE_TEXT },
      60              :     { Sid::hairpinTextAlign,                   Pid::CONTINUE_TEXT_ALIGN },
      61              :     { Sid::hairpinPosition,                    Pid::CONTINUE_TEXT_POSITION },
      62              :     { Sid::hairpinFontFace,                    Pid::END_FONT_FACE },
      63              :     { Sid::hairpinFontSize,                    Pid::END_FONT_SIZE },
      64              :     { Sid::hairpinFontStyle,                   Pid::END_FONT_STYLE },
      65              :     { Sid::hairpinTextAlign,                   Pid::END_TEXT_ALIGN },
      66              :     { Sid::hairpinPosition,                    Pid::END_TEXT_POSITION },
      67              :     { Sid::hairpinLineWidth,                   Pid::LINE_WIDTH },
      68              :     { Sid::hairpinHeight,                      Pid::HAIRPIN_HEIGHT },
      69              :     { Sid::hairpinContHeight,                  Pid::HAIRPIN_CONT_HEIGHT },
      70              :     { Sid::hairpinLineStyle,                   Pid::LINE_STYLE },
      71              :     { Sid::hairpinLineDashLineLen,             Pid::DASH_LINE_LEN },
      72              :     { Sid::hairpinLineDashGapLen,              Pid::DASH_GAP_LEN },
      73              :     { Sid::hairpinFontSpatiumDependent,        Pid::TEXT_SIZE_SPATIUM_DEPENDENT, },
      74              :     { Sid::hairpinEndLineArrowHeight,          Pid::END_LINE_ARROW_HEIGHT },
      75              :     { Sid::hairpinEndLineArrowWidth,           Pid::END_LINE_ARROW_WIDTH },
      76              :     { Sid::hairpinBeginLineArrowHeight,        Pid::BEGIN_LINE_ARROW_HEIGHT },
      77              :     { Sid::hairpinBeginLineArrowWidth,         Pid::BEGIN_LINE_ARROW_WIDTH },
      78              :     { Sid::hairpinEndFilledArrowHeight,        Pid::END_FILLED_ARROW_HEIGHT },
      79              :     { Sid::hairpinEndFilledArrowWidth,         Pid::END_FILLED_ARROW_WIDTH },
      80              :     { Sid::hairpinBeginFilledArrowHeight,      Pid::BEGIN_FILLED_ARROW_HEIGHT },
      81              :     { Sid::hairpinBeginFilledArrowWidth,       Pid::BEGIN_FILLED_ARROW_WIDTH },
      82              :     { Sid::hairpinMusicalSymbolSize,           Pid::BEGIN_TEXT_MUSIC_SYMBOLS_SIZE },
      83              :     { Sid::hairpinMusicalSymbolSize,           Pid::CONTINUE_TEXT_MUSIC_SYMBOLS_SIZE },
      84              :     { Sid::hairpinMusicalSymbolSize,           Pid::END_TEXT_MUSIC_SYMBOLS_SIZE },
      85              :     { Sid::dummyMusicalSymbolsScale,           Pid::BEGIN_TEXT_MUSICAL_SYMBOLS_SCALE },
      86              :     { Sid::dummyMusicalSymbolsScale,           Pid::CONTINUE_TEXT_MUSICAL_SYMBOLS_SCALE },
      87              :     { Sid::dummyMusicalSymbolsScale,           Pid::END_TEXT_MUSICAL_SYMBOLS_SCALE },
      88              : };
      89              : 
      90              : //---------------------------------------------------------
      91              : //   HairpinSegment
      92              : //---------------------------------------------------------
      93              : 
      94          283 : HairpinSegment::HairpinSegment(Hairpin* sp)
      95          283 :     : TextLineBaseSegment(ElementType::HAIRPIN_SEGMENT, sp, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
      96              : {
      97          283 :     m_text->setTextStyleType(propertyDefault(Pid::TEXT_STYLE).value<TextStyleType>());
      98          283 :     m_endText->setTextStyleType(propertyDefault(Pid::TEXT_STYLE).value<TextStyleType>());
      99          283 : }
     100              : 
     101            0 : bool HairpinSegment::acceptDrop(EditData& data) const
     102              : {
     103            0 :     EngravingItem* e = data.dropElement;
     104            0 :     if (e->isDynamic()) {
     105            0 :         return true;
     106              :     }
     107            0 :     return false;
     108              : }
     109              : 
     110            0 : EngravingItem* HairpinSegment::drop(Transaction& tx, EditData& data)
     111              : {
     112            0 :     EngravingItem* e = data.dropElement;
     113            0 :     if (!e->isDynamic()) {
     114            0 :         return nullptr;
     115              :     }
     116              : 
     117            0 :     if (EngravingItem* item = ldata()->itemSnappedAfter()) {
     118            0 :         if (item->isDynamic()) {
     119            0 :             return item->drop(tx, data);
     120              :         }
     121              :     }
     122              : 
     123            0 :     Fraction endTick = hairpin()->tick2();
     124            0 :     Measure* measure = score()->tick2measure(endTick);
     125            0 :     Segment* segment = measure->getChordRestOrTimeTickSegment(endTick);
     126              : 
     127            0 :     Dynamic* d = toDynamic(e->clone());
     128            0 :     d->setTrack(hairpin()->track());
     129            0 :     d->setOwnershipParent(segment);
     130            0 :     d->setVoiceAssignment(hairpin()->voiceAssignment());
     131            0 :     score()->undoAddElement(d);
     132              : 
     133            0 :     return d;
     134              : }
     135              : 
     136            0 : void HairpinSegment::setPropertyFlags(Pid id, PropertyFlags f)
     137              : {
     138            0 :     int i = getPropertyFlagsIdx(id);
     139            0 :     if (i == -1) {
     140              :         // Can happen (for example when edit-dragging the aperture) that we're editing
     141              :         // the HairpinSegment but the relevant property belongs to the Hairpin.
     142            0 :         hairpin()->setPropertyFlags(id, f);
     143            0 :         return;
     144              :     }
     145            0 :     m_propertyFlagsList[i] = f;
     146              : }
     147              : 
     148              : //---------------------------------------------------------
     149              : //   gripsPositions
     150              : //---------------------------------------------------------
     151              : 
     152            0 : int HairpinSegment::gripsCount() const
     153              : {
     154            0 :     return hairpin()->isLineType() ? 3 : 4;
     155              : }
     156              : 
     157            0 : std::vector<PointF> HairpinSegment::gripsPositions(const EditData&) const
     158              : {
     159            0 :     double _spatium = spatium();
     160            0 :     double x = pos2().x();
     161            0 :     if (x < _spatium) {             // minimum size of hairpin
     162            0 :         x = _spatium;
     163              :     }
     164            0 :     double y = pos2().y();
     165            0 :     PointF p(x, y);
     166              : 
     167            0 :     std::vector<PointF> grips(gripsCount());
     168            0 :     PointF pp(pagePos());
     169            0 :     grips[int(Grip::START)] = pp;
     170            0 :     grips[int(Grip::END)] = p + pp;
     171            0 :     grips[int(Grip::MIDDLE)] = p * .5 + pp;
     172              : 
     173            0 :     if (!hairpin()->isLineType()) {
     174              :         // Calc PointF for Grip Aperture
     175            0 :         Transform doRotation;
     176            0 :         PointF gripLineAperturePoint;
     177            0 :         double h1 = hairpin()->hairpinHeight().val() * spatium() * .5;
     178            0 :         double len = sqrt(x * x + y * y);
     179            0 :         doRotation.rotateRadians(asin(y / len));
     180              :         double lineApertureX;
     181            0 :         double offsetX = 10;                                 // Horizontal offset for x Grip
     182            0 :         if (len < offsetX * 3) {                            // For small hairpin, offset = 30% of len
     183            0 :             offsetX = len / 3;                              // else offset is fixed to 10
     184              :         }
     185            0 :         if (hairpin()->hairpinType() == HairpinType::CRESC_HAIRPIN) {
     186            0 :             lineApertureX = len - offsetX;                  // End of CRESCENDO - Offset
     187              :         } else {
     188            0 :             lineApertureX = offsetX;                        // Begin of DIMINUENDO + Offset
     189              :         }
     190            0 :         double lineApertureH = (len - offsetX) * h1 / len;   // Vertical position for y grip
     191            0 :         gripLineAperturePoint.setX(lineApertureX);
     192            0 :         gripLineAperturePoint.setY(lineApertureH);
     193            0 :         gripLineAperturePoint = doRotation.map(gripLineAperturePoint);
     194              : 
     195              :         // End calc position grip aperture
     196            0 :         grips[int(Grip::APERTURE)] = gripLineAperturePoint + pp;
     197              :     }
     198              : 
     199            0 :     return grips;
     200            0 : }
     201              : 
     202              : //---------------------------------------------------------
     203              : //   getDragGroup
     204              : //---------------------------------------------------------
     205              : 
     206            0 : std::unique_ptr<ElementGroup> HairpinSegment::getDragGroup(std::function<bool(const EngravingItem*)> isDragged)
     207              : {
     208            0 :     if (auto g = HairpinWithDynamicsDragGroup::detectFor(this, isDragged)) {
     209            0 :         return g;
     210            0 :     }
     211            0 :     return TextLineBaseSegment::getDragGroup(isDragged);
     212              : }
     213              : 
     214              : //---------------------------------------------------------
     215              : //   startDragGrip
     216              : //---------------------------------------------------------
     217              : 
     218            0 : void HairpinSegment::startDragGrip(EditData& ed)
     219              : {
     220            0 :     TextLineBaseSegment::startDragGrip(ed);
     221            0 :     ElementEditDataPtr eed = ed.getData(this);
     222              : 
     223            0 :     eed->pushProperty(Pid::HAIRPIN_HEIGHT);
     224            0 :     eed->pushProperty(Pid::HAIRPIN_CONT_HEIGHT);
     225            0 : }
     226              : 
     227              : //---------------------------------------------------------
     228              : //   dragGrip
     229              : //---------------------------------------------------------
     230              : 
     231            0 : void HairpinSegment::dragGrip(EditData& ed)
     232              : {
     233            0 :     if (ed.curGrip == Grip::APERTURE) {
     234            0 :         double newHeight = hairpin()->hairpinHeight().val() + ed.delta.y() / spatium() / .5;
     235            0 :         if (newHeight < 0.5) {
     236            0 :             newHeight = 0.5;
     237              :         }
     238            0 :         hairpin()->setHairpinHeight(Spatium(newHeight));
     239            0 :         triggerLayout();
     240              :     }
     241            0 :     TextLineBaseSegment::dragGrip(ed);
     242            0 : }
     243              : 
     244              : //---------------------------------------------------------
     245              : //   propertyDelegate
     246              : //---------------------------------------------------------
     247              : 
     248         2401 : EngravingObject* HairpinSegment::propertyDelegate(Pid pid) const
     249              : {
     250         2401 :     if (pid == Pid::HAIRPIN_TYPE
     251         2401 :         || pid == Pid::VELO_CHANGE
     252         2401 :         || pid == Pid::VELO_CHANGE_METHOD
     253         2401 :         || pid == Pid::SINGLE_NOTE_DYNAMICS
     254         2401 :         || pid == Pid::HAIRPIN_CIRCLEDTIP
     255         2401 :         || pid == Pid::HAIRPIN_HEIGHT
     256         2401 :         || pid == Pid::HAIRPIN_CONT_HEIGHT
     257         2401 :         || pid == Pid::LINE_STYLE
     258         2401 :         || pid == Pid::VOICE_ASSIGNMENT
     259         2241 :         || pid == Pid::DIRECTION
     260         2241 :         || pid == Pid::CENTER_BETWEEN_STAVES
     261              :         ) {
     262          356 :         return spanner();
     263              :     }
     264         2045 :     return TextLineBaseSegment::propertyDelegate(pid);
     265              : }
     266              : 
     267              : //---------------------------------------------------------
     268              : //   getPropertyStyle
     269              : //---------------------------------------------------------
     270              : 
     271          385 : Sid HairpinSegment::getPropertyStyle(Pid pid) const
     272              : {
     273          385 :     switch (pid) {
     274            0 :     case Pid::BEGIN_TEXT:
     275            0 :         switch (hairpin()->hairpinType()) {
     276            0 :         default:
     277            0 :             return Sid::hairpinText;
     278            0 :         case HairpinType::CRESC_LINE:
     279            0 :             return Sid::hairpinCrescText;
     280            0 :         case HairpinType::DIM_LINE:
     281            0 :             return Sid::hairpinDecrescText;
     282              :         }
     283              :         break;
     284            0 :     case Pid::CONTINUE_TEXT:
     285            0 :         switch (hairpin()->hairpinType()) {
     286            0 :         default:
     287            0 :             return Sid::hairpinText;
     288            0 :         case HairpinType::CRESC_LINE:
     289            0 :             return Sid::hairpinCrescContText;
     290            0 :         case HairpinType::DIM_LINE:
     291            0 :             return Sid::hairpinDecrescContText;
     292              :         }
     293              :         break;
     294            0 :     case Pid::LINE_STYLE:
     295            0 :         return hairpin()->isLineType() ? Sid::hairpinLineLineStyle : Sid::hairpinLineStyle;
     296            0 :     case Pid::DASH_LINE_LEN:
     297            0 :         return hairpin()->isLineType() ? Sid::hairpinLineDashLineLen : Sid::hairpinDashLineLen;
     298            0 :     case Pid::DASH_GAP_LEN:
     299            0 :         return hairpin()->isLineType() ? Sid::hairpinLineDashGapLen : Sid::hairpinDashGapLen;
     300          385 :     default:
     301          385 :         break;
     302              :     }
     303          385 :     return TextLineBaseSegment::getPropertyStyle(pid);
     304              : }
     305              : 
     306          467 : EngravingItem* HairpinSegment::findElementToSnapBefore(bool ignoreInvisible) const
     307              : {
     308          467 :     TextBase* startDynOrExpr = findStartDynamicOrExpression(ignoreInvisible);
     309          467 :     if (startDynOrExpr) {
     310          106 :         return startDynOrExpr;
     311              :     }
     312              : 
     313          361 :     Hairpin* thisHairpin = hairpin();
     314          361 :     Fraction startTick = hairpin()->tick();
     315              : 
     316          361 :     auto intervals = score()->spannerMap().findOverlapping(startTick.ticks(), startTick.ticks());
     317         1398 :     for (auto interval : intervals) {
     318         1066 :         Spanner* spanner = interval.value;
     319         1989 :         bool isValidHairpin = spanner->isHairpin() && !spanner->segmentsEmpty() && spanner != thisHairpin
     320         1989 :                               && (spanner->addToSkyline() || !ignoreInvisible);
     321         1066 :         if (!isValidHairpin) {
     322          610 :             continue;
     323              :         }
     324          456 :         Hairpin* precedingHairpin = toHairpin(spanner);
     325          456 :         bool endsMatch = precedingHairpin->track() == thisHairpin->track()
     326           33 :                          && precedingHairpin->tick2() == startTick
     327           31 :                          && precedingHairpin->placeAbove() == thisHairpin->placeAbove()
     328          489 :                          && toHairpin(spanner)->voiceAssignment() == thisHairpin->voiceAssignment();
     329          456 :         if (endsMatch && precedingHairpin->snapToItemAfter()) {
     330           29 :             return precedingHairpin->backSegment();
     331              :         }
     332              :     }
     333              : 
     334          332 :     return nullptr;
     335          361 : }
     336              : 
     337          465 : EngravingItem* HairpinSegment::findElementToSnapAfter(bool ignoreInvisible, bool requirePlayable) const
     338              : {
     339              :     // Note: we don't need to look for a hairpin after.
     340              :     // It is the next hairpin which looks for a hairpin before.
     341          465 :     return findEndDynamicOrExpression(ignoreInvisible, requirePlayable);
     342              : }
     343              : 
     344            0 : void HairpinSegment::endDragGrip(EditData& ed)
     345              : {
     346            0 :     if (ed.isHairpinDragCreatedFromDynamic) {
     347            0 :         undoResetProperty(Pid::OFFSET);
     348            0 :         undoResetProperty(Pid::OFFSET2);
     349              :     }
     350              : 
     351            0 :     LineSegment::endDragGrip(ed);
     352            0 : }
     353              : 
     354          467 : TextBase* HairpinSegment::findStartDynamicOrExpression(bool ignoreInvisible) const
     355              : {
     356          467 :     Fraction refTick = hairpin()->tick();
     357          467 :     Measure* measure = score()->tick2measure(refTick);
     358          467 :     if (!measure) {
     359            0 :         return nullptr;
     360              :     }
     361              : 
     362          467 :     std::vector<TextBase*> dynamicsAndExpr;
     363          467 :     dynamicsAndExpr.reserve(2);
     364              : 
     365         2976 :     for (Segment* segment = measure->last(); segment; segment = segment->prev1()) {
     366         2907 :         Fraction segmentTick = segment->tick();
     367         2907 :         if (segmentTick > refTick) {
     368         1739 :             continue;
     369              :         }
     370         1168 :         if (segmentTick < refTick) {
     371          398 :             break;
     372              :         }
     373         1222 :         for (EngravingItem* item : segment->annotations()) {
     374          346 :             if (!item->isDynamic() && !item->isExpression()) {
     375          103 :                 continue;
     376              :             }
     377          243 :             if (ignoreInvisible && !item->addToSkyline()) {
     378            2 :                 continue;
     379              :             }
     380          241 :             bool endsMatch = item->track() == hairpin()->track()
     381          107 :                              && item->placement() == placement()
     382          589 :                              && item->getProperty(Pid::VOICE_ASSIGNMENT) == hairpin()->getProperty(Pid::VOICE_ASSIGNMENT);
     383          241 :             if (endsMatch) {
     384          106 :                 dynamicsAndExpr.push_back(toTextBase(item));
     385              :             }
     386              :         }
     387          876 :         if (dynamicsAndExpr.size() > 0) {
     388          106 :             break;
     389              :         }
     390              :     }
     391              : 
     392          467 :     if (dynamicsAndExpr.size() == 0) {
     393          361 :         return nullptr;
     394              :     }
     395              : 
     396          106 :     if (dynamicsAndExpr.size() > 1) {
     397            0 :         std::sort(dynamicsAndExpr.begin(), dynamicsAndExpr.end(), [](TextBase* item1, TextBase* item2) {
     398            0 :             bool dynamicBeforeExpression = item1->isDynamic() && item2->isExpression();
     399            0 :             bool oneIsAnchorToPrevious = item1->isDynamic() && toDynamic(item1)->anchorToEndOfPrevious()
     400            0 :                                          && item2->isDynamic() && !toDynamic(item2)->anchorToEndOfPrevious();
     401            0 :             return dynamicBeforeExpression || oneIsAnchorToPrevious;
     402              :         });
     403              :     }
     404              : 
     405          106 :     return dynamicsAndExpr.back();
     406          467 : }
     407              : 
     408          465 : TextBase* HairpinSegment::findEndDynamicOrExpression(bool ignoreInvisible, bool requirePlayable) const
     409              : {
     410          465 :     Fraction refTick = hairpin()->tick2();
     411          465 :     Measure* measure = score()->tick2measure(refTick - Fraction::eps());
     412          465 :     if (!measure) {
     413            0 :         return nullptr;
     414              :     }
     415              : 
     416          465 :     std::vector<TextBase*> dynamicsAndExpr;
     417          465 :     dynamicsAndExpr.reserve(2);
     418              : 
     419         3249 :     for (Segment* segment = measure->first(); segment; segment = segment->next1()) {
     420         3236 :         Fraction segmentTick = segment->tick();
     421         3236 :         if (segmentTick < refTick) {
     422         1968 :             continue;
     423              :         }
     424         1268 :         if (segmentTick > refTick) {
     425          452 :             break;
     426              :         }
     427         1028 :         for (EngravingItem* item : segment->annotations()) {
     428          141 :             if (!item->isDynamic() && !item->isExpression()) {
     429           64 :                 continue;
     430              :             }
     431           77 :             if (ignoreInvisible && !item->addToSkyline()) {
     432            3 :                 continue;
     433              :             }
     434           74 :             if (requirePlayable && (!item->isDynamic() || !toDynamic(item)->playDynamic())) {
     435            0 :                 continue;
     436              :             }
     437           74 :             bool endsMatch = item->track() == hairpin()->track()
     438           74 :                              && item->placement() == placement()
     439          222 :                              && item->getProperty(Pid::VOICE_ASSIGNMENT) == hairpin()->getProperty(Pid::VOICE_ASSIGNMENT);
     440           74 :             if (endsMatch) {
     441           71 :                 dynamicsAndExpr.push_back(toTextBase(item));
     442              :             }
     443              :         }
     444          887 :         if (dynamicsAndExpr.size() > 0) {
     445           71 :             break;
     446              :         }
     447              :     }
     448              : 
     449          465 :     if (dynamicsAndExpr.size() == 0) {
     450          394 :         return nullptr;
     451              :     }
     452              : 
     453           71 :     if (dynamicsAndExpr.size() > 1) {
     454            0 :         std::sort(dynamicsAndExpr.begin(), dynamicsAndExpr.end(), [](TextBase* item1, TextBase* item2) {
     455            0 :             bool dynamicBeforeExpression = item1->isDynamic() && item2->isExpression();
     456            0 :             bool oneIsAnchorToPrevious = item1->isDynamic() && toDynamic(item1)->anchorToEndOfPrevious()
     457            0 :                                          && item2->isDynamic() && !toDynamic(item2)->anchorToEndOfPrevious();
     458            0 :             return dynamicBeforeExpression || oneIsAnchorToPrevious;
     459              :         });
     460              :     }
     461              : 
     462           71 :     return dynamicsAndExpr.front();
     463          465 : }
     464              : 
     465        41926 : Sid Hairpin::getPropertyStyle(Pid pid) const
     466              : {
     467        41926 :     switch (pid) {
     468         1052 :     case Pid::BEGIN_TEXT:
     469         1052 :         switch (hairpinType()) {
     470         1035 :         default:
     471         1035 :             return Sid::hairpinText;
     472           10 :         case HairpinType::CRESC_LINE:
     473           10 :             return Sid::hairpinCrescText;
     474            7 :         case HairpinType::DIM_LINE:
     475            7 :             return Sid::hairpinDecrescText;
     476              :         }
     477              :         break;
     478         1076 :     case Pid::CONTINUE_TEXT:
     479         1076 :         switch (hairpinType()) {
     480         1058 :         default:
     481         1058 :             return Sid::hairpinText;
     482           11 :         case HairpinType::CRESC_LINE:
     483           11 :             return Sid::hairpinCrescContText;
     484            7 :         case HairpinType::DIM_LINE:
     485            7 :             return Sid::hairpinDecrescContText;
     486              :         }
     487              :         break;
     488         1093 :     case Pid::LINE_STYLE:
     489         1093 :         return isLineType() ? Sid::hairpinLineLineStyle : Sid::hairpinLineStyle;
     490         1090 :     case Pid::DASH_LINE_LEN:
     491         1090 :         return isLineType() ? Sid::hairpinLineDashLineLen : Sid::hairpinDashLineLen;
     492         1090 :     case Pid::DASH_GAP_LEN:
     493         1090 :         return isLineType() ? Sid::hairpinLineDashGapLen : Sid::hairpinDashGapLen;
     494            0 :     case Pid::PLACEMENT:
     495            0 :         return Sid::hairpinPlacement;
     496        36525 :     default:
     497        36525 :         break;
     498              :     }
     499        36525 :     return TextLineBase::getPropertyStyle(pid);
     500              : }
     501              : 
     502              : //---------------------------------------------------------
     503              : //   Hairpin
     504              : //---------------------------------------------------------
     505              : 
     506          362 : Hairpin::Hairpin(DummyParentOr<EngravingItem> parent)
     507          362 :     : TextLineBase(ElementType::HAIRPIN, parent)
     508              : {
     509          362 :     initElementStyle(&hairpinStyle);
     510              : 
     511          362 :     resetProperty(Pid::BEGIN_TEXT_PLACE);
     512          362 :     resetProperty(Pid::END_TEXT_PLACE);
     513          362 :     resetProperty(Pid::CONTINUE_TEXT_PLACE);
     514          362 :     resetProperty(Pid::BEGIN_HOOK_HEIGHT);
     515          362 :     resetProperty(Pid::END_HOOK_HEIGHT);
     516          362 :     resetProperty(Pid::HAIRPIN_TYPE);
     517          362 :     resetProperty(Pid::LINE_VISIBLE);
     518              : 
     519          362 :     m_hairpinCircledTip     = false;
     520          362 :     m_veloChange            = 0;
     521          362 :     m_singleNoteDynamics    = true;
     522          362 :     m_veloChangeMethod      = ChangeMethod::NORMAL;
     523          362 : }
     524              : 
     525           38 : DynamicType Hairpin::dynamicTypeFrom() const
     526              : {
     527           38 :     if (m_hairpinType == HairpinType::CRESC_HAIRPIN && hairpinCircledTip()) {
     528            1 :         return DynamicType::N;
     529              :     }
     530              : 
     531           37 :     muse::ByteArray ba = beginText().toAscii();
     532           37 :     return TConv::dynamicType(ba.constChar());
     533           37 : }
     534              : 
     535           38 : DynamicType Hairpin::dynamicTypeTo() const
     536              : {
     537           38 :     if (m_hairpinType == HairpinType::DIM_HAIRPIN && hairpinCircledTip()) {
     538            1 :         return DynamicType::N;
     539              :     }
     540              : 
     541           37 :     muse::ByteArray ba = endText().toAscii();
     542           37 :     return TConv::dynamicType(ba.constChar());
     543           37 : }
     544              : 
     545            0 : const Dynamic* Hairpin::dynamicSnappedBefore() const
     546              : {
     547            0 :     const LineSegment* seg = frontSegment();
     548            0 :     if (!seg) {
     549            0 :         return nullptr;
     550              :     }
     551              : 
     552            0 :     const EngravingItem* item = seg->ldata()->itemSnappedBefore();
     553            0 :     if (!item || !item->isDynamic()) {
     554            0 :         return nullptr;
     555              :     }
     556              : 
     557            0 :     return toDynamic(item);
     558              : }
     559              : 
     560            0 : const Dynamic* Hairpin::dynamicSnappedAfter() const
     561              : {
     562            0 :     const LineSegment* seg = backSegment();
     563            0 :     if (!seg) {
     564            0 :         return nullptr;
     565              :     }
     566              : 
     567            0 :     const EngravingItem* item = seg->ldata()->itemSnappedAfter();
     568            0 :     if (!item || !item->isDynamic()) {
     569            0 :         return nullptr;
     570              :     }
     571              : 
     572            0 :     return toDynamic(item);
     573              : }
     574              : 
     575              : //---------------------------------------------------------
     576              : //   setHairpinType
     577              : //---------------------------------------------------------
     578              : 
     579          687 : void Hairpin::setHairpinType(HairpinType val)
     580              : {
     581          687 :     if (m_hairpinType == val) {
     582          251 :         return;
     583              :     }
     584          436 :     m_hairpinType = val;
     585          436 :     styleChanged();
     586              : }
     587              : 
     588              : //---------------------------------------------------------
     589              : //   createLineSegment
     590              : //---------------------------------------------------------
     591              : 
     592              : static const ElementStyle hairpinSegmentStyle {
     593              :     { Sid::hairpinMinDistance, Pid::MIN_DISTANCE },
     594              : };
     595              : 
     596          283 : LineSegment* Hairpin::createLineSegment()
     597              : {
     598          283 :     HairpinSegment* h = new HairpinSegment(this);
     599          283 :     h->setTrack(track());
     600          283 :     h->initElementStyle(&hairpinSegmentStyle);
     601          283 :     return h;
     602              : }
     603              : 
     604              : //---------------------------------------------------------
     605              : //   getProperty
     606              : //---------------------------------------------------------
     607              : 
     608         7779 : PropertyValue Hairpin::getProperty(Pid id) const
     609              : {
     610         7779 :     switch (id) {
     611          148 :     case Pid::HAIRPIN_CIRCLEDTIP:
     612          148 :         return m_hairpinCircledTip;
     613            0 :     case Pid::HAIRPIN_TYPE:
     614            0 :         return int(m_hairpinType);
     615          148 :     case Pid::VELO_CHANGE:
     616          148 :         return m_veloChange;
     617            6 :     case Pid::HAIRPIN_HEIGHT:
     618            6 :         return m_hairpinHeight;
     619            6 :     case Pid::HAIRPIN_CONT_HEIGHT:
     620            6 :         return m_hairpinContHeight;
     621          148 :     case Pid::SINGLE_NOTE_DYNAMICS:
     622          148 :         return m_singleNoteDynamics;
     623          148 :     case Pid::VELO_CHANGE_METHOD:
     624          148 :         return m_veloChangeMethod;
     625          950 :     case Pid::VOICE_ASSIGNMENT:
     626          950 :         return voiceAssignment();
     627          814 :     case Pid::CENTER_BETWEEN_STAVES:
     628          814 :         return centerBetweenStaves();
     629          619 :     case Pid::DIRECTION:
     630          619 :         return direction();
     631          148 :     case Pid::SNAP_BEFORE:
     632          148 :         return snapToItemBefore();
     633          148 :     case Pid::SNAP_AFTER:
     634          148 :         return snapToItemAfter();
     635              : 
     636         4496 :     default:
     637         4496 :         return TextLineBase::getProperty(id);
     638              :     }
     639              : }
     640              : 
     641              : //---------------------------------------------------------
     642              : //   setProperty
     643              : //---------------------------------------------------------
     644              : 
     645        44473 : bool Hairpin::setProperty(Pid id, const PropertyValue& v)
     646              : {
     647        44473 :     switch (id) {
     648            0 :     case Pid::HAIRPIN_CIRCLEDTIP:
     649            0 :         m_hairpinCircledTip = v.toBool();
     650            0 :         break;
     651          362 :     case Pid::HAIRPIN_TYPE:
     652          362 :         setHairpinType(HairpinType(v.toInt()));
     653          362 :         break;
     654            0 :     case Pid::VELO_CHANGE:
     655            0 :         m_veloChange = v.toInt();
     656            0 :         break;
     657         1091 :     case Pid::HAIRPIN_HEIGHT:
     658         1091 :         m_hairpinHeight = v.value<Spatium>();
     659         1091 :         break;
     660         1091 :     case Pid::HAIRPIN_CONT_HEIGHT:
     661         1091 :         m_hairpinContHeight = v.value<Spatium>();
     662         1091 :         break;
     663            0 :     case Pid::SINGLE_NOTE_DYNAMICS:
     664            0 :         m_singleNoteDynamics = v.toBool();
     665            0 :         break;
     666            0 :     case Pid::VELO_CHANGE_METHOD:
     667            0 :         m_veloChangeMethod = v.value<ChangeMethod>();
     668            0 :         break;
     669            5 :     case Pid::VOICE_ASSIGNMENT:
     670            5 :         setVoiceAssignment(v.value<VoiceAssignment>());
     671            5 :         break;
     672            9 :     case Pid::CENTER_BETWEEN_STAVES:
     673            9 :         setCenterBetweenStaves(v.value<AutoOnOff>());
     674            9 :         break;
     675           55 :     case Pid::DIRECTION:
     676           55 :         setDirection(v.value<DirectionV>());
     677           55 :         break;
     678            0 :     case Pid::SNAP_BEFORE:
     679            0 :         setSnapToItemBefore(v.toBool());
     680            0 :         break;
     681            0 :     case Pid::SNAP_AFTER:
     682            0 :         setSnapToItemAfter(v.toBool());
     683            0 :         break;
     684        41860 :     default:
     685        41860 :         return TextLineBase::setProperty(id, v);
     686              :     }
     687         2613 :     triggerLayout();
     688         2613 :     return true;
     689              : }
     690              : 
     691              : //---------------------------------------------------------
     692              : //   propertyDefault
     693              : //---------------------------------------------------------
     694              : 
     695         8380 : PropertyValue Hairpin::propertyDefault(Pid id) const
     696              : {
     697         8380 :     switch (id) {
     698          148 :     case Pid::HAIRPIN_CIRCLEDTIP:
     699          148 :         return false;
     700              : 
     701          148 :     case Pid::VELO_CHANGE:
     702          148 :         return 0;
     703              : 
     704           19 :     case Pid::BEGIN_TEXT:
     705           19 :         if (m_hairpinType == HairpinType::CRESC_LINE) {
     706            1 :             return style().styleV(Sid::hairpinCrescText);
     707              :         }
     708           18 :         if (m_hairpinType == HairpinType::DIM_LINE) {
     709            2 :             return style().styleV(Sid::hairpinDecrescText);
     710              :         }
     711           32 :         return String();
     712              : 
     713           16 :     case Pid::CONTINUE_TEXT:
     714           16 :         if (m_hairpinType == HairpinType::CRESC_LINE) {
     715            0 :             return style().styleV(Sid::hairpinCrescContText);
     716              :         }
     717           16 :         if (m_hairpinType == HairpinType::DIM_LINE) {
     718            0 :             return style().styleV(Sid::hairpinDecrescContText);
     719              :         }
     720           32 :         return String();
     721              : 
     722          148 :     case Pid::END_TEXT:
     723          296 :         return String();
     724              : 
     725         1530 :     case Pid::BEGIN_TEXT_PLACE:
     726              :     case Pid::CONTINUE_TEXT_PLACE:
     727              :     case Pid::END_TEXT_PLACE:
     728         1530 :         return TextPlace::LEFT;
     729              : 
     730          444 :     case Pid::BEGIN_TEXT_OFFSET:
     731              :     case Pid::CONTINUE_TEXT_OFFSET:
     732              :     case Pid::END_TEXT_OFFSET:
     733          444 :         return PropertyValue::fromValue(PointF());
     734              : 
     735          296 :     case Pid::BEGIN_HOOK_TYPE:
     736              :     case Pid::END_HOOK_TYPE:
     737          296 :         return HookType::NONE;
     738              : 
     739         1020 :     case Pid::BEGIN_HOOK_HEIGHT:
     740              :     case Pid::END_HOOK_HEIGHT:
     741         1020 :         return 1.9_sp;
     742              : 
     743          510 :     case Pid::LINE_VISIBLE:
     744          510 :         return true;
     745              : 
     746          362 :     case Pid::HAIRPIN_TYPE:
     747          362 :         return int(HairpinType::CRESC_HAIRPIN);
     748              : 
     749          148 :     case Pid::SINGLE_NOTE_DYNAMICS:
     750          148 :         return true;
     751              : 
     752          148 :     case Pid::VELO_CHANGE_METHOD:
     753          148 :         return ChangeMethod::NORMAL;
     754              : 
     755            0 :     case Pid::PLACEMENT:
     756            0 :         return style().styleV(Sid::hairpinPlacement);
     757              : 
     758          148 :     case Pid::VOICE_ASSIGNMENT:
     759          296 :         return VoiceAssignment::ALL_VOICE_IN_INSTRUMENT;
     760              : 
     761          148 :     case Pid::CENTER_BETWEEN_STAVES:
     762          296 :         return AutoOnOff::AUTO;
     763              : 
     764          148 :     case Pid::DIRECTION:
     765          148 :         return DirectionV::AUTO;
     766              : 
     767          148 :     case Pid::SNAP_BEFORE:
     768          148 :         return true;
     769          148 :     case Pid::SNAP_AFTER:
     770          148 :         return true;
     771              : 
     772          566 :     case Pid::TEXT_STYLE:
     773          566 :         return TextStyleType::HAIRPIN;
     774              : 
     775         2137 :     default:
     776         2137 :         return TextLineBase::propertyDefault(id);
     777              :     }
     778              : }
     779              : 
     780              : //---------------------------------------------------------
     781              : //   accessibleInfo
     782              : //---------------------------------------------------------
     783              : 
     784            0 : String Hairpin::accessibleInfo() const
     785              : {
     786            0 :     return String(u"%1: %2").arg(TextLineBase::accessibleInfo(), translatedSubtypeUserName());
     787              : }
     788              : 
     789          935 : PointF Hairpin::linePos(Grip grip, System** system) const
     790              : {
     791          935 :     bool start = grip == Grip::START;
     792              : 
     793          935 :     Segment* segment = start ? startSegment() : endSegment();
     794          935 :     if (!segment) {
     795            0 :         return PointF();
     796              :     }
     797              : 
     798          935 :     if (!start) {
     799          467 :         Fraction curTick = segment->tick();
     800          467 :         Segment* prevSeg = segment->prev1(SegmentType::EndBarLine);
     801          467 :         if (prevSeg && prevSeg->tick() == curTick) {
     802          228 :             segment = prevSeg;
     803              :         }
     804              :     }
     805              : 
     806          935 :     *system = segment->measure()->system();
     807          935 :     double x = segment->x() + segment->measure()->x();
     808          935 :     if (!start) {
     809          467 :         x -= spatium();
     810              :     }
     811              : 
     812          935 :     return PointF(x, 0.0);
     813              : }
     814              : 
     815            0 : void Hairpin::reset()
     816              : {
     817            0 :     undoResetProperty(Pid::DIRECTION);
     818            0 :     undoResetProperty(Pid::CENTER_BETWEEN_STAVES);
     819            0 :     TextLineBase::reset();
     820            0 : }
     821              : 
     822            1 : muse::TranslatableString Hairpin::subtypeUserName() const
     823              : {
     824            1 :     switch (hairpinType()) {
     825            1 :     case HairpinType::CRESC_HAIRPIN:
     826            1 :         return TranslatableString("engraving/hairpintype", "Crescendo hairpin");
     827            0 :     case HairpinType::DIM_HAIRPIN:
     828            0 :         return TranslatableString("engraving/hairpintype", "Diminuendo hairpin");
     829            0 :     case HairpinType::CRESC_LINE:
     830            0 :         return TranslatableString("engraving/hairpintype", "Crescendo line");
     831            0 :     case HairpinType::DIM_LINE:
     832            0 :         return TranslatableString("engraving/hairpintype", "Diminuendo line");
     833            0 :     default:
     834            0 :         return TranslatableString("engraving/hairpintype", "Custom");
     835              :     }
     836              : }
     837              : 
     838          489 : Sid Hairpin::defaultPosSid() const
     839              : {
     840          489 :     if (isLineType()) {
     841           26 :         return placeAbove() ? Sid::hairpinLinePosAbove : Sid::hairpinLinePosBelow;
     842              :     }
     843          463 :     return placeAbove() ? Sid::hairpinPosAbove : Sid::hairpinPosBelow;
     844              : }
     845              : }
        

Generated by: LCOV version 2.0-1