LCOV - code coverage report
Current view: top level - src/engraving/dom - dynamic.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 48.5 % 334 162
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : #include "dynamic.h"
      23              : 
      24              : #include "../editing/edithairpin.h"
      25              : #include "../editing/textedit.h"
      26              : #include "../editing/transaction/transaction.h"
      27              : #include "../types/typesconv.h"
      28              : 
      29              : #include "dynamichairpingroup.h"
      30              : #include "expression.h"
      31              : #include "hairpin.h"
      32              : #include "measure.h"
      33              : #include "score.h"
      34              : #include "segment.h"
      35              : #include "staff.h"
      36              : #include "system.h"
      37              : 
      38              : #include "log.h"
      39              : 
      40              : using namespace muse::draw;
      41              : using namespace mu::engraving;
      42              : 
      43              : namespace mu::engraving {
      44              : //-----------------------------------------------------------------------------
      45              : //   Dyn
      46              : //    see: http://en.wikipedia.org/wiki/File:Dynamic's_Note_Velocity.svg
      47              : //-----------------------------------------------------------------------------
      48              : 
      49              : // variant with ligatures, works for both emmentaler and bravura:
      50              : const std::vector<Dyn> Dynamic::DYN_LIST = {
      51              :     // dynamic:
      52              :     { DynamicType::OTHER,  -1, 0,   true, "" },
      53              :     { DynamicType::PPPPPP,  1, 0,   false,
      54              :       "<sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym>" },
      55              :     { DynamicType::PPPPP,   5, 0,   false,
      56              :       "<sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym>" },
      57              :     { DynamicType::PPPP,    10, 0,  false,
      58              :       "<sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym>" },
      59              :     { DynamicType::PPP,     16, 0,  false,
      60              :       "<sym>dynamicPiano</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym>" },
      61              :     { DynamicType::PP,      33, 0,  false,  "<sym>dynamicPiano</sym><sym>dynamicPiano</sym>" },
      62              :     { DynamicType::P,       49, 0,  false,  "<sym>dynamicPiano</sym>" },
      63              : 
      64              :     { DynamicType::MP,      64, 0,   false, "<sym>dynamicMezzo</sym><sym>dynamicPiano</sym>" },
      65              :     { DynamicType::MF,      80, 0,   false, "<sym>dynamicMezzo</sym><sym>dynamicForte</sym>" },
      66              : 
      67              :     { DynamicType::F,       96, 0,   false, "<sym>dynamicForte</sym>" },
      68              :     { DynamicType::FF,      112, 0,  false, "<sym>dynamicForte</sym><sym>dynamicForte</sym>" },
      69              :     { DynamicType::FFF,     126, 0,  false, "<sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym>" },
      70              :     { DynamicType::FFFF,    127, 0,  false,
      71              :       "<sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym>" },
      72              :     { DynamicType::FFFFF,   127, 0,  false,
      73              :       "<sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym>" },
      74              :     { DynamicType::FFFFFF,  127, 0,  false,
      75              :       "<sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym>" },
      76              : 
      77              :     { DynamicType::FP,      96, -47,  true, "<sym>dynamicForte</sym><sym>dynamicPiano</sym>" },
      78              :     { DynamicType::PF,      49, 47,   true, "<sym>dynamicPiano</sym><sym>dynamicForte</sym>" },
      79              : 
      80              :     { DynamicType::SF,      112, -18, true, "<sym>dynamicSforzando</sym><sym>dynamicForte</sym>" },
      81              :     { DynamicType::SFZ,     112, -18, true, "<sym>dynamicSforzando</sym><sym>dynamicForte</sym><sym>dynamicZ</sym>" },
      82              :     { DynamicType::SFF,     126, -18, true, "<sym>dynamicSforzando</sym><sym>dynamicForte</sym><sym>dynamicForte</sym>" },
      83              :     { DynamicType::SFFZ,    126, -18, true,
      84              :       "<sym>dynamicSforzando</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicZ</sym>" },
      85              :     { DynamicType::SFFF,    127, -18, true,
      86              :       "<sym>dynamicSforzando</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym>" },
      87              :     { DynamicType::SFFFZ,   127, -18, true,
      88              :       "<sym>dynamicSforzando</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicForte</sym><sym>dynamicZ</sym>" },
      89              :     { DynamicType::SFP,     112, -47, true, "<sym>dynamicSforzando</sym><sym>dynamicForte</sym><sym>dynamicPiano</sym>" },
      90              :     { DynamicType::SFPP,    112, -79, true,
      91              :       "<sym>dynamicSforzando</sym><sym>dynamicForte</sym><sym>dynamicPiano</sym><sym>dynamicPiano</sym>" },
      92              : 
      93              :     { DynamicType::RFZ,     112, -18, true, "<sym>dynamicRinforzando</sym><sym>dynamicForte</sym><sym>dynamicZ</sym>" },
      94              :     { DynamicType::RF,      112, -18, true, "<sym>dynamicRinforzando</sym><sym>dynamicForte</sym>" },
      95              :     { DynamicType::FZ,      112, -18, true, "<sym>dynamicForte</sym><sym>dynamicZ</sym>" },
      96              : 
      97              :     { DynamicType::M,       96, -16,  true, "<sym>dynamicMezzo</sym>" },
      98              :     { DynamicType::R,       112, -18, true, "<sym>dynamicRinforzando</sym>" },
      99              :     { DynamicType::S,       112, -18, true, "<sym>dynamicSforzando</sym>" },
     100              :     { DynamicType::Z,       80, 0,    true, "<sym>dynamicZ</sym>" },
     101              :     { DynamicType::N,       49, -48,  true, "<sym>dynamicNiente</sym>" }
     102              : };
     103              : 
     104              : //---------------------------------------------------------
     105              : //   dynamicsStyle
     106              : //---------------------------------------------------------
     107              : 
     108              : static const ElementStyle dynamicsStyle {
     109              :     { Sid::dynamicsMinDistance, Pid::MIN_DISTANCE },
     110              :     { Sid::avoidBarLines, Pid::AVOID_BARLINES },
     111              :     { Sid::centerOnNotehead, Pid::CENTER_ON_NOTEHEAD },
     112              : };
     113              : 
     114              : //---------------------------------------------------------
     115              : //   Dynamic
     116              : //---------------------------------------------------------
     117              : 
     118          884 : Dynamic::Dynamic(DummyParentOr<Segment> parent)
     119          884 :     : TextBase(ElementType::DYNAMIC, parent, TextStyleType::DYNAMICS, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
     120              : {
     121          884 :     m_velocity    = -1;
     122          884 :     m_dynamicType = DynamicType::OTHER;
     123          884 :     m_changeInVelocity = 128;
     124          884 :     m_velChangeSpeed = DynamicSpeed::NORMAL;
     125          884 :     initElementStyle(&dynamicsStyle);
     126          884 : }
     127              : 
     128           93 : Dynamic::Dynamic(const Dynamic& d)
     129           93 :     : TextBase(d)
     130              : {
     131           93 :     m_dynamicType = d.m_dynamicType;
     132           93 :     m_velocity    = d.m_velocity;
     133           93 :     m_changeInVelocity = d.m_changeInVelocity;
     134           93 :     m_velChangeSpeed = d.m_velChangeSpeed;
     135           93 :     _avoidBarLines = d._avoidBarLines;
     136           93 :     _centerOnNotehead = d._centerOnNotehead;
     137           93 : }
     138              : 
     139              : //---------------------------------------------------------
     140              : //   velocity
     141              : //---------------------------------------------------------
     142              : 
     143          788 : int Dynamic::velocity() const
     144              : {
     145          788 :     return m_velocity <= 0 ? DYN_LIST[int(dynamicType())].velocity : m_velocity;
     146              : }
     147              : 
     148              : //---------------------------------------------------------
     149              : //   changeInVelocity
     150              : //---------------------------------------------------------
     151              : 
     152          124 : int Dynamic::changeInVelocity() const
     153              : {
     154          124 :     return m_changeInVelocity >= 128 ? DYN_LIST[int(dynamicType())].changeInVelocity : m_changeInVelocity;
     155              : }
     156              : 
     157              : //---------------------------------------------------------
     158              : //   setChangeInVelocity
     159              : //---------------------------------------------------------
     160              : 
     161            0 : void Dynamic::setChangeInVelocity(int val)
     162              : {
     163            0 :     if (DYN_LIST[int(dynamicType())].changeInVelocity == val) {
     164            0 :         m_changeInVelocity = 128;
     165              :     } else {
     166            0 :         m_changeInVelocity = val;
     167              :     }
     168            0 : }
     169              : 
     170              : //---------------------------------------------------------
     171              : //   velocityChangeLength
     172              : //    the time over which the velocity change occurs
     173              : //---------------------------------------------------------
     174              : 
     175           32 : Fraction Dynamic::velocityChangeLength(BeatsPerSecond tempo) const
     176              : {
     177           32 :     if (changeInVelocity() == 0) {
     178            0 :         return Fraction::fromTicks(0);
     179              :     }
     180              : 
     181           32 :     double ratio = tempo.val / Constants::DEFAULT_TEMPO.val;
     182              :     double speedMult;
     183           32 :     switch (velChangeSpeed()) {
     184            0 :     case DynamicSpeed::SLOW:
     185            0 :         speedMult = 1.3;
     186            0 :         break;
     187            0 :     case DynamicSpeed::FAST:
     188            0 :         speedMult = 0.5;
     189            0 :         break;
     190           32 :     case DynamicSpeed::NORMAL:
     191              :     default:
     192           32 :         speedMult = 0.8;
     193           32 :         break;
     194              :     }
     195              : 
     196           32 :     return Fraction::fromTicks(int(ratio * (speedMult * double(Constants::DIVISION))));
     197              : }
     198              : 
     199              : //---------------------------------------------------------
     200              : //   isVelocityChangeAvailable
     201              : //---------------------------------------------------------
     202              : 
     203          622 : bool Dynamic::isVelocityChangeAvailable() const
     204              : {
     205          622 :     switch (dynamicType()) {
     206           48 :     case DynamicType::FP:
     207              :     case DynamicType::SF:
     208              :     case DynamicType::SFZ:
     209              :     case DynamicType::SFF:
     210              :     case DynamicType::SFFZ:
     211              :     case DynamicType::SFFF:
     212              :     case DynamicType::SFFFZ:
     213              :     case DynamicType::SFP:
     214              :     case DynamicType::SFPP:
     215              :     case DynamicType::RFZ:
     216              :     case DynamicType::RF:
     217              :     case DynamicType::FZ:
     218              :     case DynamicType::M:
     219              :     case DynamicType::R:
     220              :     case DynamicType::S:
     221           48 :         return true;
     222          574 :     default:
     223          574 :         return false;
     224              :     }
     225              : }
     226              : 
     227              : //---------------------------------------------------------
     228              : //   setDynamicType
     229              : //---------------------------------------------------------
     230              : 
     231          874 : void Dynamic::setDynamicType(const String& tag)
     232              : {
     233          874 :     const auto dynamicInfo = parseDynamicText(tag);
     234              : 
     235          874 :     if (dynamicInfo.first == DynamicType::OTHER) {
     236           32 :         LOGD("setDynamicType: other <%s>", muPrintable(tag));
     237              :     }
     238              : 
     239          874 :     setDynamicType(dynamicInfo.first);
     240          874 :     setXmlText(dynamicInfo.second);
     241          874 : }
     242              : 
     243          874 : std::pair<DynamicType, String> Dynamic::parseDynamicText(const String& tag) const
     244              : {
     245          874 :     std::string utf8Tag = tag.toStdString();
     246          874 :     const std::regex dynamicRegex(R"((?:<sym>.*?</sym>)+|(?:\b)[fmnprsz]+(?:\b(?=[^>]|$)))");
     247          874 :     auto begin = std::sregex_iterator(utf8Tag.begin(), utf8Tag.end(), dynamicRegex);
     248          934 :     for (auto it = begin; it != std::sregex_iterator(); ++it) {
     249          872 :         const std::smatch match = *it;
     250          872 :         const std::string matchStr = match.str();
     251          872 :         size_t n = DYN_LIST.size();
     252         9056 :         for (size_t i = 0; i < n; ++i) {
     253         9026 :             if (TConv::toXml(DynamicType(i)).ascii() == matchStr || DYN_LIST[i].text == matchStr) {
     254          842 :                 utf8Tag.replace(match.position(0), match.length(0), DYN_LIST[i].text);
     255          842 :                 return { DynamicType(i), String::fromStdString(utf8Tag) };
     256              :             }
     257              :         }
     258         2588 :     }
     259           32 :     return { DynamicType::OTHER, tag };
     260          874 : }
     261              : 
     262          949 : String Dynamic::dynamicText(DynamicType t)
     263              : {
     264          949 :     return String::fromUtf8(DYN_LIST[int(t)].text);
     265              : }
     266              : 
     267            0 : Expression* Dynamic::snappedExpression() const
     268              : {
     269            0 :     EngravingItem* item = ldata()->itemSnappedAfter();
     270            0 :     return item && item->isExpression() ? toExpression(item) : nullptr;
     271              : }
     272              : 
     273         1216 : HairpinSegment* Dynamic::findSnapBeforeHairpinAcrossSystemBreak() const
     274              : {
     275              :     /* Normally it is the hairpin which looks for a snappable dynamic. Except if this dynamic
     276              :      * is on the first beat of next system, in which case it needs to look back for a hairpin. */
     277         1216 :     Segment* seg = segment();
     278         1216 :     Measure* measure = seg ? seg->measure() : nullptr;
     279         1216 :     System* system = measure ? measure->system() : nullptr;
     280         1216 :     bool isOnFirstBeatOfSystem = system && system->firstMeasure() == measure && seg->rtick().isZero();
     281         1216 :     if (!isOnFirstBeatOfSystem) {
     282          605 :         return nullptr;
     283              :     }
     284              : 
     285          611 :     Measure* prevMeasure = measure->prevMeasure();
     286          611 :     System* prevSystem = prevMeasure ? prevMeasure->system() : nullptr;
     287          611 :     if (!prevSystem) {
     288          595 :         return nullptr;
     289              :     }
     290              : 
     291           44 :     for (SpannerSegment* spannerSeg : prevSystem->spannerSegments()) {
     292           28 :         if (!spannerSeg->isHairpinSegment() || spannerSeg->track() != track() || spannerSeg->spanner()->tick2() != tick()) {
     293           28 :             continue;
     294              :         }
     295            0 :         HairpinSegment* hairpinSeg = toHairpinSegment(spannerSeg);
     296            0 :         if (hairpinSeg->findElementToSnapAfter() == this) {
     297            0 :             return hairpinSeg;
     298              :         }
     299              :     }
     300              : 
     301           16 :     return nullptr;
     302              : }
     303              : 
     304            0 : bool Dynamic::acceptDrop(EditData& ed) const
     305              : {
     306            0 :     ElementType droppedType = ed.dropElement->type();
     307            0 :     return droppedType == ElementType::DYNAMIC || droppedType == ElementType::EXPRESSION || droppedType == ElementType::HAIRPIN;
     308              : }
     309              : 
     310            0 : EngravingItem* Dynamic::drop(Transaction& tx, EditData& ed)
     311              : {
     312            0 :     EngravingItem* item = ed.dropElement;
     313              : 
     314            0 :     if (item->isHairpin()) {
     315            0 :         EditHairpin::addHairpinToDynamic(tx, score(), toHairpin(item), this);
     316            0 :         return item;
     317              :     }
     318              : 
     319            0 :     if (item->isDynamic()) {
     320            0 :         Dynamic* dynamic = toDynamic(item);
     321            0 :         undoChangeProperty(Pid::DYNAMIC_TYPE, dynamic->dynamicType());
     322            0 :         undoChangeProperty(Pid::TEXT, dynamic->xmlText());
     323            0 :         delete dynamic;
     324            0 :         ed.dropElement = this;
     325            0 :         return this;
     326              :     }
     327              : 
     328            0 :     if (item->isExpression()) {
     329            0 :         item->setTrack(track());
     330            0 :         item->setOwnershipParent(segment());
     331            0 :         toExpression(item)->setVoiceAssignment(voiceAssignment());
     332            0 :         score()->undoAddElement(item);
     333            0 :         return item;
     334              :     }
     335              : 
     336            0 :     return nullptr;
     337              : }
     338              : 
     339           14 : int Dynamic::dynamicVelocity(DynamicType t)
     340              : {
     341           14 :     return DYN_LIST[int(t)].velocity;
     342              : }
     343              : 
     344            3 : void Dynamic::startEdit(EditData& ed)
     345              : {
     346            3 :     if (ed.curGrip != Grip::NO_GRIP) {
     347            0 :         EngravingItem::startEdit(ed);
     348              :     } else {
     349            3 :         TextBase::startEdit(ed);
     350              :     }
     351            3 : }
     352              : 
     353            3 : void Dynamic::endEdit(EditData& ed)
     354              : {
     355            3 :     if (cursor() && cursor()->editing()) {
     356            3 :         TextBase::endEdit(ed);
     357              :     } else {
     358            0 :         EngravingItem::endEdit(ed);
     359              :     }
     360            3 : }
     361              : 
     362            0 : TranslatableString Dynamic::subtypeUserName() const
     363              : {
     364            0 :     if (dynamicType() == DynamicType::OTHER) {
     365            0 :         String s = plainText().simplified();
     366            0 :         if (s.size() > 20) {
     367            0 :             s.truncate(20);
     368            0 :             s += u"…";
     369              :         }
     370            0 :         return TranslatableString::untranslatable(s);
     371            0 :     } else {
     372            0 :         return TConv::userName(dynamicType());
     373              :     }
     374              : }
     375              : 
     376            0 : void Dynamic::dragGrip(EditData& ed)
     377              : {
     378            0 :     const bool hasLeftGrip = this->hasLeftGrip();
     379            0 :     const bool hasRightGrip = this->hasRightGrip();
     380              : 
     381              :     // Right grip (when two grips/when single grip)
     382            0 :     if ((int(ed.curGrip) == 1 && hasLeftGrip && hasRightGrip) || (int(ed.curGrip) == 0 && !hasLeftGrip && hasRightGrip)) {
     383            0 :         m_rightDragOffset += ed.evtDelta.x();
     384            0 :         if (m_rightDragOffset < 0) {
     385            0 :             m_rightDragOffset = 0;
     386              :         }
     387            0 :         return;
     388              :     }
     389              : 
     390              :     // Left grip (when two grips or single grip)
     391            0 :     if (int(ed.curGrip) == 0 && hasLeftGrip) {
     392            0 :         m_leftDragOffset += ed.evtDelta.x();
     393            0 :         if (m_leftDragOffset > 0) {
     394            0 :             m_leftDragOffset = 0;
     395              :         }
     396            0 :         return;
     397              :     }
     398              : 
     399            0 :     UNREACHABLE;
     400              : }
     401              : 
     402            0 : void Dynamic::endDragGrip(EditData& ed)
     403              : {
     404            0 :     m_leftDragOffset = m_rightDragOffset = 0.0;
     405              : 
     406            0 :     TextBase::endDragGrip(ed);
     407            0 : }
     408              : 
     409              : //---------------------------------------------------------
     410              : //   reset
     411              : //---------------------------------------------------------
     412              : 
     413            0 : void Dynamic::reset()
     414              : {
     415            0 :     undoResetProperty(Pid::DIRECTION);
     416            0 :     undoResetProperty(Pid::CENTER_BETWEEN_STAVES);
     417            0 :     TextBase::reset();
     418            0 :     Expression* snappedExp = snappedExpression();
     419            0 :     if (snappedExp && !snappedExp->offset().isNull()) {
     420            0 :         snappedExp->reset();
     421              :     }
     422            0 : }
     423              : 
     424              : //---------------------------------------------------------
     425              : //   getDragGroup
     426              : //---------------------------------------------------------
     427              : 
     428            0 : std::unique_ptr<ElementGroup> Dynamic::getDragGroup(std::function<bool(const EngravingItem*)> isDragged)
     429              : {
     430            0 :     if (auto g = HairpinWithDynamicsDragGroup::detectFor(this, isDragged)) {
     431            0 :         return g;
     432            0 :     }
     433            0 :     if (auto g = DynamicNearHairpinsDragGroup::detectFor(this, isDragged)) {
     434            0 :         return g;
     435            0 :     }
     436            0 :     if (auto g = DynamicExpressionDragGroup::detectFor(this, isDragged)) {
     437            0 :         return g;
     438            0 :     }
     439            0 :     return TextBase::getDragGroup(isDragged);
     440              : }
     441              : 
     442              : //---------------------------------------------------------
     443              : //   getProperty
     444              : //---------------------------------------------------------
     445              : 
     446        18439 : PropertyValue Dynamic::getProperty(Pid propertyId) const
     447              : {
     448        18439 :     switch (propertyId) {
     449          599 :     case Pid::DYNAMIC_TYPE:
     450          599 :         return m_dynamicType;
     451          590 :     case Pid::VELOCITY:
     452          590 :         return velocity();
     453           18 :     case Pid::VELO_CHANGE:
     454           18 :         if (isVelocityChangeAvailable()) {
     455           16 :             return changeInVelocity();
     456              :         } else {
     457            2 :             return PropertyValue();
     458              :         }
     459           18 :     case Pid::VELO_CHANGE_SPEED:
     460           18 :         return m_velChangeSpeed;
     461            8 :     case Pid::AVOID_BARLINES:
     462            8 :         return avoidBarLines();
     463            4 :     case Pid::CENTER_ON_NOTEHEAD:
     464            4 :         return _centerOnNotehead;
     465          588 :     case Pid::PLAY:
     466          588 :         return playDynamic();
     467          617 :     case Pid::ANCHOR_TO_END_OF_PREVIOUS:
     468          617 :         return anchorToEndOfPrevious();
     469        15997 :     default:
     470        15997 :         return TextBase::getProperty(propertyId);
     471              :     }
     472              : }
     473              : 
     474              : //---------------------------------------------------------
     475              : //   setProperty
     476              : //---------------------------------------------------------
     477              : 
     478        17758 : bool Dynamic::setProperty(Pid propertyId, const PropertyValue& v)
     479              : {
     480        17758 :     switch (propertyId) {
     481            3 :     case Pid::DYNAMIC_TYPE:
     482            3 :         if (v.type() == P_TYPE::DYNAMIC_TYPE) {
     483            0 :             setDynamicType(v.value<DynamicType>());
     484            0 :             break;
     485              :         }
     486            3 :         setDynamicType(v.value<String>());
     487            3 :         break;
     488            2 :     case Pid::VELOCITY:
     489            2 :         m_velocity = v.toInt();
     490            2 :         break;
     491            0 :     case Pid::VELO_CHANGE:
     492            0 :         if (isVelocityChangeAvailable()) {
     493            0 :             setChangeInVelocity(v.toInt());
     494              :         }
     495            0 :         break;
     496            0 :     case Pid::VELO_CHANGE_SPEED:
     497            0 :         m_velChangeSpeed = v.value<DynamicSpeed>();
     498            0 :         break;
     499          929 :     case Pid::AVOID_BARLINES:
     500          929 :         setAvoidBarLines(v.toBool());
     501          929 :         break;
     502          923 :     case Pid::CENTER_ON_NOTEHEAD:
     503          923 :         _centerOnNotehead = v.toBool();
     504          923 :         break;
     505            0 :     case Pid::PLAY:
     506            0 :         setPlayDynamic(v.toBool());
     507            0 :         break;
     508            1 :     case Pid::ANCHOR_TO_END_OF_PREVIOUS:
     509            1 :         setAnchorToEndOfPrevious(v.toBool());
     510            1 :         break;
     511        15900 :     default:
     512        15900 :         if (!TextBase::setProperty(propertyId, v)) {
     513            0 :             return false;
     514              :         }
     515        15900 :         break;
     516              :     }
     517        17758 :     triggerLayout();
     518        17758 :     return true;
     519              : }
     520              : 
     521              : //---------------------------------------------------------
     522              : //   propertyDefault
     523              : //---------------------------------------------------------
     524              : 
     525         9632 : PropertyValue Dynamic::propertyDefault(Pid id) const
     526              : {
     527         9632 :     switch (id) {
     528          588 :     case Pid::TEXT_STYLE:
     529          588 :         return TextStyleType::DYNAMICS;
     530          588 :     case Pid::VELOCITY:
     531          588 :         return -1;
     532           16 :     case Pid::VELO_CHANGE:
     533           16 :         if (isVelocityChangeAvailable()) {
     534           16 :             return DYN_LIST[int(dynamicType())].changeInVelocity;
     535              :         } else {
     536            0 :             return PropertyValue();
     537              :         }
     538           16 :     case Pid::VELO_CHANGE_SPEED:
     539           16 :         return DynamicSpeed::NORMAL;
     540          588 :     case Pid::PLAY:
     541          588 :         return true;
     542          588 :     case Pid::ANCHOR_TO_END_OF_PREVIOUS:
     543          588 :         return false;
     544         7248 :     default:
     545         7248 :         return TextBase::propertyDefault(id);
     546              :     }
     547              : }
     548              : 
     549         7970 : Sid Dynamic::getPropertyStyle(Pid pid) const
     550              : {
     551         7970 :     switch (pid) {
     552            0 :     case Pid::PLACEMENT:
     553            0 :         return Sid::dynamicsPlacement;
     554         7970 :     default:
     555         7970 :         return TextBase::getPropertyStyle(pid);
     556              :     }
     557              : }
     558              : 
     559              : //---------------------------------------------------------
     560              : //   accessibleInfo
     561              : //---------------------------------------------------------
     562              : 
     563            0 : String Dynamic::accessibleInfo() const
     564              : {
     565            0 :     return String(u"%1: %2").arg(EngravingItem::accessibleInfo(), translatedSubtypeUserName());
     566              : }
     567              : 
     568              : //---------------------------------------------------------
     569              : //   screenReaderInfo
     570              : //---------------------------------------------------------
     571              : 
     572            0 : String Dynamic::screenReaderInfo() const
     573              : {
     574            0 :     String s;
     575              : 
     576            0 :     if (dynamicType() == DynamicType::OTHER) {
     577            0 :         s = plainText().simplified();
     578              :     } else {
     579            0 :         s = TConv::translatedUserName(dynamicType());
     580              :     }
     581            0 :     return String(u"%1: %2").arg(EngravingItem::accessibleInfo(), s);
     582            0 : }
     583              : }
     584              : 
     585            1 : bool Dynamic::isEditAllowed(EditData& ed) const
     586              : {
     587            1 :     if (!dynamic_cast<TextEditData*>(ed.getData(this).get())) {
     588            0 :         return EngravingItem::isEditAllowed(ed);
     589              :     }
     590              : 
     591            1 :     return TextBase::isEditAllowed(ed);
     592              : }
     593              : 
     594              : //---------------------------------------------------------
     595              : //   hasLeftHairpin
     596              : //---------------------------------------------------------
     597              : 
     598            0 : bool Dynamic::hasLeftGrip() const
     599              : {
     600            0 :     if (segment()->tick().isZero()) {
     601            0 :         return false; // Don't show the left grip for the leftmost dynamic with tick zero
     602              :     }
     603            0 :     return m_leftHairpin == nullptr;
     604              : }
     605              : 
     606              : //---------------------------------------------------------
     607              : //   hasRightHairpin
     608              : //---------------------------------------------------------
     609              : 
     610            0 : bool Dynamic::hasRightGrip() const
     611              : {
     612            0 :     return m_rightHairpin == nullptr;
     613              : }
     614              : 
     615              : //---------------------------------------------------------
     616              : //   findAdjacentHairpins
     617              : //---------------------------------------------------------
     618              : 
     619            0 : void Dynamic::findAdjacentHairpins()
     620              : {
     621            0 :     m_leftHairpin = nullptr;
     622            0 :     m_rightHairpin = nullptr;
     623              : 
     624            0 :     const Fraction tick = segment()->tick();
     625            0 :     const int intTick = tick.ticks();
     626              : 
     627            0 :     const auto& spanners = score()->spannerMap().findOverlapping(intTick - 1, intTick + 1);
     628            0 :     for (auto i : spanners) {
     629            0 :         Spanner* sp = i.value;
     630            0 :         if (sp->track() == track() && sp->isHairpin()) {
     631            0 :             Hairpin* hp = toHairpin(sp);
     632            0 :             if (hp->tick() == tick) {
     633            0 :                 m_rightHairpin = hp;
     634            0 :             } else if (hp->tick2() == tick) {
     635            0 :                 m_leftHairpin = hp;
     636              :             }
     637              :         }
     638              :     }
     639            0 : }
     640              : 
     641         1262 : Shape Dynamic::symShapeWithCutouts(SymId id) const
     642              : {
     643         1262 :     Staff* stf = staff();
     644         1262 :     double staffMag = stf ? stf->staffMag(tick()) : 1.0;
     645         1262 :     Shape shape = score()->engravingFont()->shapeWithCutouts(id, magS() * staffMag * symbolScale());
     646         4865 :     for (ShapeElement& element : shape.elements()) {
     647         3603 :         element.setItem(this);
     648              :     }
     649              : 
     650         1262 :     return shape;
     651              : }
     652              : 
     653         1125 : Dyn Dynamic::dynInfo(DynamicType type)
     654              : {
     655         1125 :     return DYN_LIST[static_cast<int>(type)];
     656              : }
     657              : 
     658              : //---------------------------------------------------------
     659              : //   gripsCount
     660              : //---------------------------------------------------------
     661              : 
     662            0 : int Dynamic::gripsCount() const
     663              : {
     664            0 :     if (empty()) {
     665            0 :         return 0;
     666              :     }
     667              : 
     668            0 :     const bool hasLeftGrip = this->hasLeftGrip();
     669            0 :     const bool hasRightGrip = this->hasRightGrip();
     670              : 
     671            0 :     if (hasLeftGrip && hasRightGrip) {
     672            0 :         return 2;
     673            0 :     } else if (hasLeftGrip || hasRightGrip) {
     674            0 :         return 1;
     675              :     } else {
     676            0 :         return 0;
     677              :     }
     678              : }
     679              : 
     680              : //---------------------------------------------------------
     681              : //   gripsPositions
     682              : //---------------------------------------------------------
     683              : 
     684            0 : std::vector<PointF> Dynamic::gripsPositions(const EditData&) const
     685              : {
     686            0 :     const RectF bbox = ldata()->bbox();
     687            0 :     const PointF pagePos = this->pagePos();
     688            0 :     const double horizontalPadding = absoluteFromSpatium(score()->style().styleS(Sid::hairpinMinDistance));
     689              : 
     690              :     // Based on rendering::score::AlignmentLayout::yOpticalCenter
     691            0 :     const double yOpticalCenter = [&] {
     692            0 :         switch (align().vertical) {
     693            0 :         case AlignV::TOP:
     694            0 :             return 0.5 * bbox.height();
     695            0 :         case AlignV::VCENTER:
     696            0 :             return 0.0;
     697            0 :         case AlignV::BOTTOM:
     698            0 :             return -0.5 * bbox.height();
     699            0 :         case AlignV::BASELINE:
     700            0 :             return -0.46 * spatium() * symbolScale(); // approximated half x-height of dynamic
     701              :         }
     702            0 :         return 0.0;
     703            0 :     }();
     704              : 
     705            0 :     PointF leftOffset(bbox.left() - horizontalPadding + m_leftDragOffset, yOpticalCenter);
     706            0 :     PointF rightOffset(bbox.right() + horizontalPadding + m_rightDragOffset, yOpticalCenter);
     707              : 
     708            0 :     const bool hasLeftGrip = this->hasLeftGrip();
     709            0 :     const bool hasRightGrip = this->hasRightGrip();
     710              : 
     711            0 :     if (hasLeftGrip && hasRightGrip) {
     712            0 :         return { pagePos + leftOffset, pagePos + rightOffset };
     713            0 :     } else if (hasLeftGrip) {
     714            0 :         return { pagePos + leftOffset };
     715            0 :     } else if (hasRightGrip) {
     716            0 :         return { pagePos + rightOffset };
     717              :     } else {
     718            0 :         return {};
     719              :     }
     720              : }
        

Generated by: LCOV version 2.0-1