LCOV - code coverage report
Current view: top level - src/engraving/dom - harmony.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 55.4 % 840 465
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 "harmony.h"
      24              : 
      25              : #include "containers.h"
      26              : #include "translation.h"
      27              : 
      28              : #include "draw/fontmetrics.h"
      29              : 
      30              : #include "iengravingconfiguration.h" // IWYU pragma: keep
      31              : 
      32              : #include "../editing/transpose.h"
      33              : #include "../editing/transaction/transaction.h"
      34              : #include "../editing/transaction/undostack.h"
      35              : #include "../rendering/iscorerenderer.h"
      36              : 
      37              : #include "chordlist.h"
      38              : #include "fret.h"
      39              : #include "linkedobjects.h"
      40              : #include "measure.h"
      41              : #include "mscore.h"
      42              : #include "part.h"
      43              : #include "parenthesis.h"
      44              : #include "pitchspelling.h"
      45              : #include "repeatlist.h"
      46              : #include "score.h"
      47              : #include "segment.h"
      48              : #include "staff.h"
      49              : #include "textbase.h"
      50              : #include "utils.h"
      51              : 
      52              : #include "log.h"
      53              : 
      54              : using namespace mu;
      55              : using namespace muse::draw;
      56              : using namespace mu::engraving;
      57              : 
      58              : namespace mu::engraving {
      59              : static const char* FALLBACK_SYMBOLTEXT_FONT = "Bravura Text";
      60              : 
      61              : //---------------------------------------------------------
      62              : //   descr
      63              : //    look up id in chord list
      64              : //    return chord description if found, or null
      65              : //---------------------------------------------------------
      66              : 
      67         6141 : const ChordDescription* HarmonyInfo::descr() const
      68              : {
      69         6141 :     if (!chordList()) {
      70            0 :         return nullptr;
      71              :     }
      72         6141 :     return chordList()->description(id());
      73              : }
      74              : 
      75              : //---------------------------------------------------------
      76              : //   descr
      77              : //    look up name in chord list
      78              : //    optionally look up by parsed chord as fallback
      79              : //    return chord description if found, or null
      80              : //---------------------------------------------------------
      81              : 
      82          853 : const ChordDescription* HarmonyInfo::descr(const String& name, const ParsedChord* pc) const
      83              : {
      84          853 :     const ChordDescription* match = nullptr;
      85          853 :     if (!chordList()) {
      86            0 :         return nullptr;
      87              :     }
      88         2828 :     for (const auto& p : *chordList()) {
      89         2592 :         const ChordDescription& cd = p.second;
      90         4567 :         for (const String& s : cd.names) {
      91         2592 :             if (s == name) {
      92          617 :                 return &cd;
      93              :             }
      94         1975 :             if (!pc) {
      95         1494 :                 continue;
      96              :             }
      97          481 :             for (const ParsedChord& sParsed : cd.parsedChords) {
      98            0 :                 if (sParsed == *pc) {
      99            0 :                     match = &cd;
     100              :                 }
     101              :             }
     102              :         }
     103              :     }
     104              :     // exact match failed, so fall back on parsed match if one was found
     105          236 :     return match;
     106              : }
     107              : 
     108              : //---------------------------------------------------------
     109              : //   getDescription
     110              : //    look up id in chord list
     111              : //    return chord description if found
     112              : //    if not found, and chord is parseable,
     113              : //    generate a new chord description
     114              : //    and add to chord list
     115              : //---------------------------------------------------------
     116              : 
     117         3630 : const ChordDescription* HarmonyInfo::getDescription()
     118              : {
     119         3630 :     const ChordDescription* cd = descr();
     120         3630 :     if (cd && !cd->names.empty()) {
     121         3621 :         m_textName = cd->names.front();
     122            9 :     } else if (!m_textName.empty()) {
     123            9 :         cd = generateDescription();
     124            9 :         m_id = cd->id;
     125              :     }
     126         3630 :     return cd;
     127              : }
     128              : 
     129              : //---------------------------------------------------------
     130              : //   getDescription
     131              : //    same but lookup by name and optionally parsed chord
     132              : //---------------------------------------------------------
     133              : 
     134          782 : const ChordDescription* HarmonyInfo::getDescription(const String& name, const ParsedChord* pc)
     135              : {
     136          782 :     const ChordDescription* cd = descr(name, pc);
     137          782 :     if (cd) {
     138          578 :         m_id = cd->id;
     139              :     } else {
     140          204 :         cd = generateDescription();
     141          204 :         m_id = cd->id;
     142              :     }
     143          782 :     return cd;
     144              : }
     145              : 
     146              : //---------------------------------------------------------
     147              : //   generateDescription
     148              : //    generate new chord description from _textName
     149              : //    add to chord list using private id
     150              : //---------------------------------------------------------
     151              : 
     152          262 : const ChordDescription* HarmonyInfo::generateDescription()
     153              : {
     154          262 :     ChordDescription cd(m_textName);
     155          262 :     cd.complete(getParsedChord(), chordList());
     156              :     // remove parsed chord from description
     157              :     // so we will only match it literally in the future
     158          262 :     cd.parsedChords.clear();
     159          262 :     chordList()->insert({ cd.id, cd });
     160          524 :     return &chordList()->at(cd.id);
     161          262 : }
     162              : 
     163          882 : ParsedChord* HarmonyInfo::getParsedChord()
     164              : {
     165          882 :     if (!m_parsedChord) {
     166          608 :         m_parsedChord = new ParsedChord();
     167          608 :         m_parsedChord->parse(m_textName, chordList(), false);
     168              :     }
     169          882 :     return m_parsedChord;
     170              : }
     171              : 
     172            0 : bool HarmonyInfo::hasModifiers() const
     173              : {
     174            0 :     return m_parsedChord ? m_parsedChord->modifiers().size() > 0 : false;
     175              : }
     176              : 
     177          212 : String Harmony::displayText() const
     178              : {
     179              :     // String representation of what the user sees on the score
     180              :     // Used in fretbox
     181          212 :     String name;
     182          386 :     for (const HarmonyRenderItem* segment : ldata()->renderItemList()) {
     183          174 :         if (const TextSegment* textSeg = dynamic_cast<const TextSegment*>(segment)) {
     184          174 :             String t = textSeg->text();
     185          174 :             t.replace(u"\uE87C", u"/");
     186          174 :             name += t;
     187          174 :         } else if (const ChordSymbolParen* parenSeg = dynamic_cast<const ChordSymbolParen*>(segment)) {
     188            0 :             name += parenSeg->parenItem->direction() == DirectionH::LEFT ? u"(" : u")";
     189              :         }
     190              :     }
     191              : 
     192          212 :     return name;
     193            0 : }
     194              : 
     195              : //---------------------------------------------------------
     196              : //   harmonyName
     197              : //---------------------------------------------------------
     198              : 
     199          986 : String Harmony::harmonyName() const
     200              : {
     201          986 :     String name;
     202              : 
     203          986 :     if (leftParen()) {
     204            1 :         name = u"(";
     205              :     }
     206              : 
     207         1948 :     for (size_t i = 0; i < m_chords.size(); i++) {
     208          962 :         const HarmonyInfo* info = m_chords.at(i);
     209          962 :         HChord hc = info->descr() ? info->descr()->chord : HChord();
     210          962 :         String s, r, e, b;
     211              : 
     212          962 :         if (i != 0) {
     213            4 :             name += u"|";
     214              :         }
     215              : 
     216          962 :         if (m_harmonyType == HarmonyType::STANDARD && tpcIsValid(info->rootTpc())) {
     217          913 :             NoteSpellingType spelling = style().styleV(Sid::chordSymbolSpelling).value<NoteSpellingType>();
     218          913 :             r = tpc2name(info->rootTpc(), spelling, m_rootCase);
     219           49 :         } else if (m_harmonyType == HarmonyType::NASHVILLE && tpcIsValid(info->rootTpc())) {
     220           24 :             const Staff* st = staff();
     221           24 :             Key key = st ? st->key(tick()) : Key::INVALID;
     222           24 :             r = tpc2Function(info->rootTpc(), key);
     223              :         }
     224              : 
     225          962 :         if (!info->textName().empty()) {
     226          594 :             e = info->textName();
     227          594 :             if (m_harmonyType != HarmonyType::ROMAN) {
     228          575 :                 e.remove(u'=');
     229              :             }
     230          368 :         } else if (!m_degreeList.empty()) {
     231            0 :             hc.add(m_degreeList);
     232              :             // try to find the chord in chordList
     233            0 :             const ChordDescription* newExtension = nullptr;
     234            0 :             const ChordList* cl = score()->chordList();
     235            0 :             for (const auto& p : *cl) {
     236            0 :                 const ChordDescription& cd = p.second;
     237            0 :                 if (cd.chord == hc && !cd.names.empty()) {
     238            0 :                     newExtension = &cd;
     239            0 :                     break;
     240              :                 }
     241              :             }
     242              :             // now determine the chord name
     243            0 :             if (newExtension) {
     244            0 :                 e = newExtension->names.front();
     245              :             } else {
     246              :                 // not in table, fallback to using HChord.name()
     247            0 :                 r = hc.name(info->rootTpc());
     248            0 :                 e = u"";
     249              :             }
     250              :         }
     251              : 
     252          962 :         if (tpcIsValid(info->bassTpc())) {
     253           13 :             NoteSpellingType spelling = style().styleV(Sid::chordSymbolSpelling).value<NoteSpellingType>();
     254           13 :             b = u"/" + tpc2name(info->bassTpc(), spelling, m_bassCase);
     255              :         }
     256              : 
     257          962 :         s += r + e + b;
     258              : 
     259          962 :         name += s;
     260          962 :     }
     261              : 
     262          986 :     if (rightParen()) {
     263            1 :         name += u")";
     264              :     }
     265              : 
     266          986 :     return name;
     267            0 : }
     268              : 
     269          492 : bool Harmony::isRealizable() const
     270              : {
     271          492 :     if (m_chords.empty()) {
     272            0 :         return false;
     273              :     }
     274          984 :     for (const HarmonyInfo* info : m_chords) {
     275          492 :         if (!tpcIsValid(info->rootTpc())) {
     276            0 :             return false;
     277              :         }
     278              :     }
     279          492 :     return true;
     280              : }
     281              : 
     282            7 : int Harmony::bassTpc() const
     283              : {
     284            7 :     if (m_chords.empty()) {
     285            0 :         return Tpc::TPC_INVALID;
     286              :     }
     287            7 :     return m_chords.front()->bassTpc();
     288              : }
     289              : 
     290          132 : int Harmony::rootTpc() const
     291              : {
     292          132 :     if (m_chords.empty()) {
     293            0 :         return Tpc::TPC_INVALID;
     294              :     }
     295          132 :     return m_chords.front()->rootTpc();
     296              : }
     297              : 
     298            0 : bool Harmony::isInFretBox() const
     299              : {
     300            0 :     EngravingObject* parent = ownershipParent();
     301            0 :     if (!parent) {
     302            0 :         return false;
     303              :     }
     304              : 
     305            0 :     if (parent->isFretDiagram()) {
     306            0 :         return toFretDiagram(parent)->isInFretBox();
     307              :     }
     308              : 
     309            0 :     return parent->isFBox();
     310              : }
     311              : 
     312              : //---------------------------------------------------------
     313              : //   chordSymbolStyle
     314              : //---------------------------------------------------------
     315              : 
     316              : const ElementStyle chordSymbolStyle {
     317              :     { Sid::harmonyPlacement, Pid::PLACEMENT },
     318              :     { Sid::minHarmonyDistance, Pid::MIN_DISTANCE },
     319              :     { Sid::chordBassNoteScale, Pid::HARMONY_BASS_SCALE },
     320              :     { Sid::harmonyVoiceLiteral, Pid::HARMONY_VOICE_LITERAL },
     321              :     { Sid::harmonyVoicing, Pid::HARMONY_VOICING },
     322              :     { Sid::harmonyDuration, Pid::HARMONY_DURATION },
     323              : };
     324              : 
     325              : //---------------------------------------------------------
     326              : //   Harmony
     327              : //---------------------------------------------------------
     328              : 
     329         1071 : Harmony::Harmony(EngravingObject* parent)
     330         1071 :     : TextBase(ElementType::HARMONY, parent, TextStyleType::HARMONY_A, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
     331              : {
     332         1071 :     assert(!parent || parent->isSegment() || parent->isFretDiagram() || parent->isType(ElementType::DUMMY));
     333              : 
     334         1071 :     m_rootCase   = NoteCaseType::CAPITAL;
     335         1071 :     m_bassCase   = NoteCaseType::CAPITAL;
     336         1071 :     m_harmonyType = HarmonyType::STANDARD;
     337         1071 :     m_play = true;
     338         1071 :     m_realizedHarmony = RealizedHarmony(this);
     339         1071 :     initElementStyle(&chordSymbolStyle);
     340         1071 : }
     341              : 
     342           66 : Harmony::Harmony(const Harmony& h)
     343           66 :     : TextBase(h)
     344              : {
     345           66 :     m_rootCase   = h.m_rootCase;
     346           66 :     m_bassCase   = h.m_bassCase;
     347           66 :     m_bassScale = h.m_bassScale;
     348           66 :     m_degreeList = h.m_degreeList;
     349           66 :     m_harmonyType = h.m_harmonyType;
     350           66 :     m_play       = h.m_play;
     351           66 :     m_realizedHarmony = h.m_realizedHarmony;
     352           66 :     m_realizedHarmony.setHarmony(this);
     353          132 :     for (const HarmonyInfo* hi : h.m_chords) {
     354           66 :         HarmonyInfo* newInfo = new HarmonyInfo(*hi);
     355           66 :         m_chords.push_back(newInfo);
     356              :     }
     357              : 
     358           66 :     m_fontFamily = h.m_fontFamily;
     359           66 :     m_fontStyle  = h.m_fontStyle;
     360           66 :     m_fontSize   = h.m_fontSize;
     361           66 : }
     362              : 
     363              : //---------------------------------------------------------
     364              : //   ~Harmony
     365              : //---------------------------------------------------------
     366              : 
     367         2176 : Harmony::~Harmony()
     368              : {
     369         4466 :     for (const HarmonyRenderItem* renderItem : mutldata()->renderItemList()) {
     370         3370 :         delete renderItem;
     371              :     }
     372         2199 :     for (const HarmonyInfo* info : m_chords) {
     373         1103 :         delete info;
     374              :     }
     375         2176 : }
     376              : 
     377            0 : int Harmony::id() const
     378              : {
     379            0 :     if (m_chords.empty()) {
     380            0 :         return -1;
     381              :     }
     382            0 :     return m_chords.front()->id();
     383              : }
     384              : 
     385              : //---------------------------------------------------------
     386              : //   getParentSeg
     387              : ///   gets the parent segment of this harmony
     388              : //---------------------------------------------------------
     389              : 
     390         1713 : Segment* Harmony::getParentSeg() const
     391              : {
     392         1713 :     Segment* seg = nullptr;
     393         1713 :     if (!ownershipParent()) {
     394            0 :         return nullptr;
     395              :     }
     396              : 
     397         1713 :     if (ownershipParent()->isFretDiagram()) {
     398              :         // When this harmony is the child of a fret diagram, we need to go up twice
     399              :         // to get to the parent seg.
     400           34 :         seg = toFretDiagram(ownershipParent())->segment();
     401              :     } else {
     402         1679 :         seg = toSegment(ownershipParent());
     403              :     }
     404         1713 :     return seg;
     405              : }
     406              : 
     407          227 : FretDiagram* Harmony::getParentFretDiagram() const
     408              : {
     409          227 :     if (ownershipParent() && ownershipParent()->isFretDiagram()) {
     410          223 :         return toFretDiagram(ownershipParent());
     411              :     }
     412              : 
     413            4 :     return nullptr;
     414              : }
     415              : 
     416          708 : void Harmony::afterRead()
     417              : {
     418              :     // TODO: now that we can render arbitrary chords,
     419              :     // we could try to construct a full representation from a degree list.
     420              :     // These will typically only exist for chords imported from MusicXML prior to MuseScore 2.0
     421              :     // or constructed in the Chord Symbol Properties dialog.
     422              : 
     423         1419 :     for (HarmonyInfo* info : m_chords) {
     424          711 :         if (tpcIsValid(info->rootTpc())) {
     425          703 :             if (info->id() > 0) {
     426              :                 // positive id will happen only for scores that were created with explicit chord lists
     427              :                 // lookup id in chord list and generate new description if necessary
     428            0 :                 info->getDescription();
     429              :             } else {
     430              :                 // default case: look up by name
     431              :                 // description will be found for any chord already read in this score
     432              :                 // and we will generate a new one if necessary
     433          703 :                 info->getDescription(info->textName());
     434              :             }
     435            8 :         } else if (info->textName().empty()) {
     436              :             // unrecognized chords prior to 2.0 were stored as text with markup
     437              :             // we need to strip away the markup
     438              :             // this removes any user-applied formatting,
     439              :             // but we no longer support user-applied formatting for chord symbols anyhow
     440              :             // with any luck, the resulting text will be parseable now, so give it a shot
     441            0 :             createBlocks();
     442            0 :             String s = plainText();
     443            0 :             if (!s.isEmpty()) {
     444            0 :                 setHarmony(s);
     445            0 :                 return;
     446              :             }
     447              :             // empty text could also indicate a root-less slash chord ("/E")
     448              :             // we'll fall through and render it normally
     449            0 :         }
     450              :     }
     451              : 
     452              :     // render chord from description (or _textName)
     453          708 :     setPlainText(harmonyName());
     454              : }
     455              : 
     456            0 : bool Harmony::hasModifiers() const
     457              : {
     458            0 :     for (const HarmonyInfo* info : m_chords) {
     459            0 :         if (info->hasModifiers()) {
     460            0 :             return true;
     461              :         }
     462              :     }
     463              : 
     464            0 :     return false;
     465              : }
     466              : 
     467              : //---------------------------------------------------------
     468              : //   parseHarmony
     469              : //    determine root and bass tpc & case
     470              : //    compare body of chordname against chord list
     471              : //    return true if chord is recognized
     472              : //---------------------------------------------------------
     473              : 
     474           87 : const std::vector<const ChordDescription*> Harmony::parseHarmony(const String& ss, bool syntaxOnly)
     475              : {
     476              :     // pre-process for parentheses
     477           87 :     String s = ss.simplified();
     478           87 :     if (s.startsWith('(')) {
     479            0 :         setParenthesesMode(rightParen() ? ParenthesesMode::BOTH : ParenthesesMode::LEFT, true, false);
     480            0 :         s.remove(0, 1);
     481              :     } else {
     482           87 :         setParenthesesMode(rightParen() ? ParenthesesMode::RIGHT : ParenthesesMode::NONE, true, false);
     483              :     }
     484              : 
     485           87 :     if (s.endsWith(')') && s.count('(') < s.count(')')) {
     486            0 :         setParenthesesMode(leftParen() ? ParenthesesMode::BOTH : ParenthesesMode::RIGHT, true, false);
     487            0 :         s.remove(s.size() - 1, 1);
     488              :     } else {
     489           87 :         setParenthesesMode(leftParen() ? ParenthesesMode::LEFT : ParenthesesMode::NONE, true, false);
     490              :     }
     491              : 
     492           87 :     if (parenthesesMode() == ParenthesesMode::BOTH) {
     493            0 :         s = s.simplified();         // in case of spaces inside parentheses
     494              :     }
     495              : 
     496           87 :     muse::DeleteAll(m_chords);
     497           87 :     m_chords.clear();
     498              : 
     499           87 :     std::vector<const ChordDescription*> descriptions;
     500           87 :     if (s.isEmpty()) {
     501            0 :         return descriptions;
     502              :     }
     503              : 
     504           87 :     StringList chords = s.split('|');
     505              : 
     506          175 :     for (const String& subChord : chords) {
     507           88 :         if (subChord.empty()) {
     508            0 :             continue;
     509              :         }
     510           88 :         HarmonyInfo* info = new HarmonyInfo(score());
     511           88 :         const ChordDescription* cd = parseSingleHarmony(subChord, info, syntaxOnly);
     512           88 :         descriptions.push_back(cd);
     513           88 :         m_chords.push_back(info);
     514              :     }
     515              : 
     516           87 :     return descriptions;
     517           87 : }
     518              : 
     519           88 : const ChordDescription* Harmony::parseSingleHarmony(const String& ss, HarmonyInfo* info, bool syntaxOnly)
     520              : {
     521           88 :     String s = ss.simplified();
     522              : 
     523           88 :     if (m_harmonyType == HarmonyType::ROMAN) {
     524           16 :         info->setTextName(s);
     525           16 :         info->setRootTpc(Tpc::TPC_INVALID);
     526           16 :         info->setBassTpc(Tpc::TPC_INVALID);
     527           16 :         return nullptr;
     528              :     }
     529              : 
     530           72 :     bool useLiteral = false;
     531           72 :     if (ss.endsWith(' ')) {
     532            0 :         useLiteral = true;
     533              :     }
     534              : 
     535              :     // pre-process for lower case minor chords
     536              :     bool preferMinor;
     537           72 :     if (style().styleB(Sid::lowerCaseMinorChords) && s.at(0).isLower()) {
     538            0 :         preferMinor = true;
     539              :     } else {
     540           72 :         preferMinor = false;
     541              :     }
     542              : 
     543           72 :     size_t idx = 0;
     544           72 :     const Staff* st = staff();
     545           72 :     Key key = st ? st->key(tick()) : Key::INVALID;
     546           72 :     NoteSpellingType spelling = style().styleV(Sid::chordSymbolSpelling).value<NoteSpellingType>();
     547              : 
     548           72 :     int r = Tpc::TPC_INVALID;
     549           72 :     if (m_harmonyType == HarmonyType::STANDARD) {
     550           72 :         r = convertNote(s, spelling, m_rootCase, idx);
     551            0 :     } else if (m_harmonyType == HarmonyType::NASHVILLE) {
     552            0 :         r = function2Tpc(s, key, idx);
     553              :     }
     554           72 :     if (!tpcIsValid(r)) {
     555            1 :         if (s.at(0) == '/') {
     556            0 :             idx = 0;
     557              :         } else {
     558            1 :             LOGD("failed <%s>", muPrintable(ss));
     559            1 :             info->setTextName(s);
     560            1 :             return 0;
     561              :         }
     562              :     }
     563           71 :     info->setRootTpc(r);
     564           71 :     info->setBassTpc(Tpc::TPC_INVALID);
     565           71 :     size_t slash = s.lastIndexOf(u'/');
     566           71 :     if (slash != muse::nidx) {
     567            8 :         String bs = s.mid(slash + 1).simplified();
     568            8 :         s = s.mid(idx, slash - idx).simplified();
     569            8 :         size_t idx2 = 0;
     570            8 :         if (m_harmonyType == HarmonyType::STANDARD) {
     571            8 :             info->setBassTpc(convertNote(bs, spelling, m_bassCase, idx2));
     572            0 :         } else if (m_harmonyType == HarmonyType::NASHVILLE) {
     573            0 :             info->setBassTpc(function2Tpc(bs, key, idx2));
     574              :         }
     575              : 
     576            8 :         if (idx2 != bs.size()) {
     577            0 :             info->setBassTpc(Tpc::TPC_INVALID);
     578              :         }
     579            8 :         if (!tpcIsValid(info->bassTpc())) {
     580              :             // if what follows after slash is not (just) a TPC
     581              :             // then reassemble chord and try to parse with the slash
     582            0 :             s = s + u"/" + bs;
     583              :         }
     584            8 :     } else {
     585           63 :         s = s.mid(idx);             // don't simplify; keep leading space before extension if present
     586              :     }
     587              : 
     588           71 :     const ChordList* cl = score()->chordList();
     589           71 :     const ChordDescription* cd = 0;
     590           71 :     if (useLiteral) {
     591            0 :         cd = info->descr(s);
     592              :     } else {
     593           71 :         ParsedChord* pc = new ParsedChord();
     594           71 :         pc->parse(s, cl, syntaxOnly, preferMinor);
     595              :         // parser prepends "=" to name of implied minor chords
     596              :         // use this here as well
     597           71 :         if (preferMinor) {
     598            0 :             s = pc->name();
     599              :         }
     600              :         // look up to see if we already have a descriptor (chord has been used before)
     601           71 :         cd = info->descr(s, pc);
     602           71 :         info->setParsedChord(pc);
     603              :     }
     604           71 :     if (cd) {
     605              :         // descriptor found; use its information
     606           39 :         info->setId(cd->id);
     607           39 :         if (!cd->names.empty()) {
     608           39 :             info->setTextName(cd->names.front());
     609              :         }
     610              :     } else {
     611              :         // no descriptor yet; just set textname
     612              :         // we will generate descriptor later if necessary (when we are done editing this chord)
     613           32 :         info->setTextName(s);
     614              :     }
     615           71 :     return cd;
     616           88 : }
     617              : 
     618         3630 : NoteCaseType Harmony::rootRenderCase(HarmonyInfo* info) const
     619              : {
     620              :     // case
     621              :     // always use case as typed if automatic capitalization is off
     622         3630 :     NoteCaseType noteCase = m_rootCase;
     623         3630 :     if (!style().styleB(Sid::automaticCapitalization)) {
     624            0 :         return noteCase;
     625              :     }
     626              : 
     627              :     // set default
     628         3630 :     if (style().styleB(Sid::allCapsNoteNames)) {
     629            0 :         noteCase = NoteCaseType::UPPER;
     630              :     } else {
     631         3630 :         noteCase = NoteCaseType::CAPITAL;
     632              :     }
     633              : 
     634              :     // override for minor chords
     635         3630 :     if (style().styleB(Sid::lowerCaseMinorChords)) {
     636            0 :         const ChordDescription* cd = info->descr();
     637            0 :         String quality;
     638            0 :         if (cd) {
     639              :             // use chord description if possible
     640              :             // this is the usual case
     641            0 :             quality = cd->quality();
     642            0 :         } else if (info->getParsedChord()) {
     643              :             // this happens on load of new chord list
     644              :             // for chord symbols that were added/edited since the score was loaded
     645              :             // or read aloud with screenreader
     646              :             // parsed form is usable even if out of date with respect to chord list
     647            0 :             quality = info->getParsedChord()->quality();
     648              :         } else {
     649              :             // this happens on load of new chord list
     650              :             // for chord symbols that have not been edited since the score was loaded
     651              :             // we need to parse this chord for now to determine quality
     652              :             // but don't keep the parsed form around as we're not ready for it yet
     653            0 :             quality = info->getParsedChord()->quality();
     654            0 :             delete info->parsedChord();
     655            0 :             info->setParsedChord(nullptr);
     656              :         }
     657            0 :         if (quality == "minor" || quality == "diminished" || quality == "half-diminished") {
     658            0 :             noteCase = NoteCaseType::LOWER;
     659              :         }
     660            0 :     }
     661              : 
     662         3630 :     return noteCase;
     663              : }
     664              : 
     665         3630 : NoteCaseType Harmony::bassRenderCase() const
     666              : {
     667              :     // case
     668              :     // always use case as typed if automatic capitalization is off
     669         3630 :     NoteCaseType noteCase = m_bassCase;
     670         3630 :     if (!style().styleB(Sid::automaticCapitalization)) {
     671            0 :         return noteCase;
     672              :     }
     673              : 
     674              :     // set default
     675         3630 :     if (style().styleB(Sid::allCapsNoteNames)) {
     676            0 :         noteCase = NoteCaseType::UPPER;
     677              :     } else {
     678         3630 :         noteCase = NoteCaseType::CAPITAL;
     679              :     }
     680              : 
     681              :     // override for bass note
     682         3630 :     if (style().styleB(Sid::lowerCaseBassNotes)) {
     683            0 :         noteCase = NoteCaseType::LOWER;
     684              :     }
     685              : 
     686         3630 :     return noteCase;
     687              : }
     688              : 
     689              : //---------------------------------------------------------
     690              : //   startEdit
     691              : //---------------------------------------------------------
     692              : 
     693            0 : void Harmony::startEdit(EditData& ed)
     694              : {
     695            0 :     if (!ldata()->renderItemList().empty()) {
     696              :         // convert chord symbol to plain text
     697            0 :         setPlainText(harmonyName());
     698              :         // clear rendering
     699            0 :         for (const HarmonyRenderItem* renderItem : mutldata()->renderItemList()) {
     700            0 :             delete renderItem;
     701              :         }
     702            0 :         mutldata()->renderItemList.mut_value().clear();
     703              :     }
     704              : 
     705            0 :     if (leftParen()) {
     706            0 :         leftParen()->mutldata()->reset();
     707              :     }
     708            0 :     if (rightParen()) {
     709            0 :         rightParen()->mutldata()->reset();
     710              :     }
     711              : 
     712              :     // layout as text, without position reset
     713            0 :     renderer()->layoutText1(this, true);
     714            0 :     triggerLayout();
     715              : 
     716            0 :     TextBase::startEdit(ed);
     717            0 : }
     718              : 
     719            0 : bool Harmony::isEditAllowed(EditData& ed) const
     720              : {
     721            0 :     if (isTextNavigationKey(ed.key, ed.modifiers)) {
     722            0 :         return false;
     723              :     }
     724              : 
     725            0 :     if (ed.key == Key_Semicolon || ed.key == Key_Colon) {
     726            0 :         return false;
     727              :     }
     728              : 
     729            0 :     if ((ed.key == Key_Left || ed.key == Key_Right) && (ed.modifiers & ControlModifier)) {
     730            0 :         return false;
     731              :     }
     732              : 
     733            0 :     if (ed.key == Key_Return || ed.key == Key_Enter) {
     734              :         // This "edit" is actually handled in NotationInteraction::editElement
     735            0 :         return true;
     736              :     }
     737              : 
     738            0 :     return TextBase::isEditAllowed(ed);
     739              : }
     740              : 
     741              : //---------------------------------------------------------
     742              : //   edit
     743              : //---------------------------------------------------------
     744              : 
     745            0 : bool Harmony::edit(EditData& ed)
     746              : {
     747            0 :     if (!isEditAllowed(ed)) {
     748            0 :         return false;
     749              :     }
     750              : 
     751            0 :     bool rv = TextBase::edit(ed);
     752              : 
     753              :     // layout as text, without position reset
     754            0 :     renderer()->layoutText1(this, true);
     755            0 :     triggerLayout();
     756              : 
     757              :     // check spelling
     758            0 :     String str = xmlText();
     759              : 
     760            0 :     std::vector<const ChordDescription*> descriptions = parseHarmony(str, true);
     761            0 :     bool descriptionsValid = true;
     762            0 :     for (const ChordDescription* cd : descriptions) {
     763            0 :         if (!cd) {
     764            0 :             descriptionsValid = false;
     765              :         }
     766              :     }
     767              : 
     768            0 :     bool tpcsValid = true;
     769            0 :     for (const HarmonyInfo* info : m_chords) {
     770            0 :         tpcsValid &= tpcIsValid(info->rootTpc());
     771              :     }
     772              : 
     773            0 :     m_isMisspelled = !str.isEmpty()
     774            0 :                      && !descriptionsValid
     775            0 :                      && !tpcsValid
     776            0 :                      && m_harmonyType == HarmonyType::STANDARD;
     777              : 
     778            0 :     if (m_isMisspelled) {
     779            0 :         LOGD("bad spell");
     780              :     }
     781              : 
     782            0 :     return rv;
     783            0 : }
     784              : 
     785              : //---------------------------------------------------------
     786              : //   endEdit
     787              : //---------------------------------------------------------
     788              : 
     789            0 : void Harmony::endEdit(EditData& ed)
     790              : {
     791              :     // get plain text
     792            0 :     String s = plainText();
     793              : 
     794              :     // if user explicitly added symbols to the text,
     795              :     // convert them back to their respective replacement texts
     796            0 :     if (harmonyType() != HarmonyType::ROMAN) {
     797            0 :         s.replace(u"\u1d12b", u"bb");     // double-flat
     798            0 :         s.replace(u"\u266d",  u"b");      // flat
     799            0 :         s.replace(u"\ue260",  u"b");      // flat
     800              :         // do not replace natural sign
     801              :         // (right now adding the symbol explicitly is the only way to force a natural sign to appear at all)
     802              :         //s.replace("\u266e",  "n");  // natural, if one day we support that too
     803              :         //s.replace("\ue261",  "n");  // natural, if one day we support that too
     804            0 :         s.replace(u"\u266f",  u"#");      // sharp
     805            0 :         s.replace(u"\ue262",  u"#");      // sharp
     806            0 :         s.replace(u"\u1d12a", u"x");      // double-sharp
     807            0 :         s.replace(u"\u0394",  u"^");      // &Delta;
     808            0 :         s.replace(u"\u00d0",  u"o");      // &deg;
     809            0 :         s.replace(u"\u00f8",  u"0");      // &oslash;
     810            0 :         s.replace(u"\u00d8",  u"0");      // &Oslash;
     811              :     } else {
     812            0 :         s.replace(u"\ue260",  u"\u266d");         // flat
     813            0 :         s.replace(u"\ue261",  u"\u266e");         // natural
     814            0 :         s.replace(u"\ue262",  u"\u266f");         // sharp
     815              :     }
     816              : 
     817              :     //play chord on edit and set dirty
     818            0 :     score()->setPlayChord(true);
     819              : 
     820            0 :     setHarmony(s);
     821            0 :     setPlainText(harmonyName());
     822              : 
     823              :     // disable spell check
     824            0 :     m_isMisspelled = false;
     825              : 
     826            0 :     const bool textEdited = textWasEdited(ed);
     827              : 
     828            0 :     TextBase::endEdit(ed);
     829              : 
     830            0 :     if (!links() || !textEdited) {
     831            0 :         return;
     832              :     }
     833              : 
     834            0 :     const bool thisConcertPitch = style().styleB(Sid::concertPitch);
     835            0 :     const Interval thisInterval = thisConcertPitch ? Interval(0, 0) : staff()->transpose(tick());
     836              : 
     837            0 :     for (EngravingObject* e : *links()) {
     838            0 :         if (e == this) {
     839            0 :             continue;
     840              :         }
     841            0 :         Harmony* h = toHarmony(e);
     842              :         // transpose if necessary
     843              :         // at this point chord will already have been rendered in same key as original
     844              :         // (as a result of TextBase::endEdit() calling setText() for linked elements)
     845              :         // we may now need to change the TPC's and the text, and re-render
     846            0 :         const bool linkedConcertPitch = h->style().styleB(Sid::concertPitch);
     847            0 :         if (thisConcertPitch == linkedConcertPitch) {
     848            0 :             continue;
     849              :         }
     850              : 
     851            0 :         Score* linkedScore = h->score();
     852            0 :         const Staff* linkedStaff = h->staff();
     853            0 :         const Interval linkedInterval = linkedConcertPitch ? Interval(0, 0) : linkedStaff->transpose(h->tick());
     854            0 :         const Interval transposeDiff(thisInterval.diatonic - linkedInterval.diatonic, thisInterval.chromatic - linkedInterval.chromatic);
     855            0 :         if (transposeDiff.isZero()) {
     856            0 :             continue;
     857              :         }
     858              : 
     859            0 :         UndoStack* undoStack = linkedScore->undoStack();
     860            0 :         const size_t undoIdx = undoStack->currentIndex();
     861              : 
     862            0 :         score()->transactionManager()->transaction(TranslatableString("undoableAction", "Transpose harmony"), [&](auto& tx) {
     863            0 :             Transpose::undoTransposeHarmony(tx, h, transposeDiff);
     864            0 :         });
     865              : 
     866              :         // Merge with end of text editing
     867            0 :         if (undoIdx != undoStack->currentIndex() && undoStack->currentIndex() >= 2) {
     868            0 :             const size_t penultimateCmdIdx = undoStack->currentIndex() - 2;
     869            0 :             undoStack->mergeTransactions(penultimateCmdIdx);
     870              :         }
     871              :     }
     872            0 : }
     873              : 
     874            0 : bool Harmony::isPlayable() const
     875              : {
     876            0 :     return !isInFretBox();
     877              : }
     878              : 
     879              : //---------------------------------------------------------
     880              : //   setHarmony
     881              : //---------------------------------------------------------
     882              : 
     883           87 : void Harmony::setHarmony(const String& s)
     884              : {
     885           87 :     m_realizedHarmony.setDirty(true);
     886              : 
     887           87 :     std::vector<const ChordDescription*> descriptions = parseHarmony(s);
     888          175 :     for (size_t i = 0; i < m_chords.size(); i++) {
     889           88 :         HarmonyInfo* info = m_chords.at(i);
     890           88 :         const ChordDescription* cd = i < descriptions.size() ? descriptions.at(i) : nullptr;
     891              : 
     892           88 :         if (!cd && info->getParsedChord()->parseable()) {
     893              :             // our first time encountering this chord
     894              :             // generate a descriptor and use it
     895           49 :             cd = info->generateDescription();
     896           49 :             info->setId(cd->id);
     897              :         }
     898           88 :         if (!cd) {
     899              :             // unparseable chord or roman numeral, render as plain text
     900            0 :             info->setRootTpc(Tpc::TPC_INVALID);
     901            0 :             info->setBassTpc(Tpc::TPC_INVALID);
     902            0 :             info->setId(-1);
     903              :         }
     904              :     }
     905           87 : }
     906              : 
     907              : //---------------------------------------------------------
     908              : //   baseLine
     909              : //---------------------------------------------------------
     910              : 
     911         3556 : double Harmony::baseLine() const
     912              : {
     913         3556 :     if (ldata()->renderItemList().empty() || !ldata()->baseline.has_value()) {
     914            0 :         return TextBase::baseLine();
     915              :     }
     916              : 
     917         3556 :     return ldata()->baseline.value();
     918              : }
     919              : 
     920              : //---------------------------------------------------------
     921              : //   text
     922              : //---------------------------------------------------------
     923              : 
     924           10 : String HDegree::text() const
     925              : {
     926           10 :     if (m_type == HDegreeType::UNDEF) {
     927            0 :         return String();
     928              :     }
     929           10 :     String degree;
     930           10 :     switch (m_type) {
     931            0 :     case HDegreeType::UNDEF: break;
     932            8 :     case HDegreeType::ADD:         degree = u"add";
     933            8 :         break;
     934            1 :     case HDegreeType::ALTER:       degree = u"alt";
     935            1 :         break;
     936            1 :     case HDegreeType::SUBTRACT:    degree = u"sub";
     937            1 :         break;
     938              :     }
     939              : 
     940           10 :     switch (m_alter) {
     941            1 :     case -1:          degree += u"b";
     942            1 :         break;
     943            1 :     case 1:           degree += u"#";
     944            1 :         break;
     945            8 :     default:          break;
     946              :     }
     947           20 :     String s = String::number(m_value);
     948           20 :     String ss = degree + s;
     949           10 :     return ss;
     950           10 : }
     951              : 
     952              : //---------------------------------------------------------
     953              : //   findInSeg
     954              : ///   find a Harmony in a given segment on the same track as this harmony.
     955              : ///
     956              : ///   returns 0 if there is none
     957              : //---------------------------------------------------------
     958              : 
     959         1173 : Harmony* Harmony::findInSeg(Segment* seg) const
     960              : {
     961              :     // Find harmony as parent of fret diagram on same track
     962         1173 :     EngravingItem* fde = seg->findAnnotation(ElementType::FRET_DIAGRAM, track(), track());
     963         1173 :     if (fde) {
     964            0 :         FretDiagram* fd = toFretDiagram(fde);
     965            0 :         if (fd->harmony()) {
     966            0 :             return toHarmony(fd->harmony());
     967              :         }
     968              :     }
     969              : 
     970              :     // Find harmony on same track
     971         1173 :     EngravingItem* e = seg->findAnnotation(ElementType::HARMONY, track(), track());
     972         1173 :     if (e) {
     973          481 :         return toHarmony(e);
     974              :     }
     975          692 :     return nullptr;
     976              : }
     977              : 
     978              : //---------------------------------------------------------
     979              : //   findNext
     980              : ///   find the next Harmony in the score
     981              : ///
     982              : ///   returns 0 if there is none
     983              : //---------------------------------------------------------
     984              : 
     985          492 : Harmony* Harmony::findNext() const
     986              : {
     987          492 :     const Segment* segment = getParentSeg();
     988          492 :     Segment* cur = segment ? segment->next1() : nullptr;
     989         1184 :     while (cur) {
     990         1173 :         Harmony* h = findInSeg(cur);
     991         1173 :         if (h) {
     992          481 :             return h;
     993              :         }
     994          692 :         cur = cur->next1();
     995              :     }
     996           11 :     return nullptr;
     997              : }
     998              : 
     999              : //---------------------------------------------------------
    1000              : //   findPrev
    1001              : ///   find the previous Harmony in the score
    1002              : ///
    1003              : ///   returns 0 if there is none
    1004              : //---------------------------------------------------------
    1005              : 
    1006            0 : Harmony* Harmony::findPrev() const
    1007              : {
    1008            0 :     const Segment* segment = getParentSeg();
    1009            0 :     Segment* cur = segment ? segment->prev1() : nullptr;
    1010            0 :     while (cur) {
    1011            0 :         Harmony* h = findInSeg(cur);
    1012            0 :         if (h) {
    1013            0 :             return h;
    1014              :         }
    1015            0 :         cur = cur->prev1();
    1016              :     }
    1017            0 :     return nullptr;
    1018              : }
    1019              : 
    1020              : //---------------------------------------------------------
    1021              : //   ticksTillNext
    1022              : ///   finds ticks until the next chord symbol or end of score
    1023              : ///
    1024              : ///   utick is our current playback position to start from
    1025              : ///
    1026              : ///   stopAtMeasureEnd being set to true will have the loop
    1027              : ///   stop at measure end.
    1028              : //---------------------------------------------------------
    1029          482 : Fraction Harmony::ticksTillNext(int utick, bool stopAtMeasureEnd) const
    1030              : {
    1031          482 :     const Segment* seg = getParentSeg();
    1032          482 :     if (!seg) {
    1033            0 :         return Fraction(-1, 1);
    1034              :     }
    1035              : 
    1036          482 :     Fraction duration = seg->ticks();
    1037              : 
    1038          482 :     const RepeatList& repeats = score()->repeatList();
    1039          482 :     auto rsIt = repeats.findRepeatSegmentFromUTick(utick);
    1040          482 :     if (rsIt == repeats.cend()) {
    1041            0 :         return duration;
    1042              :     }
    1043              : 
    1044          482 :     Segment* cur = seg->next();
    1045          482 :     Measure const* currentMeasure = seg->measure();
    1046          482 :     Measure const* endMeasure = (stopAtMeasureEnd) ? currentMeasure : (*rsIt)->lastMeasure();
    1047          482 :     Harmony const* nextHarmony = nullptr;
    1048              : 
    1049              :     do {
    1050              :         // Loop over segments of this measure
    1051         1597 :         while (cur) {
    1052              :             //find harmony on same track
    1053         1115 :             EngravingItem* e = cur->findAnnotation(ElementType::HARMONY, track(), track());
    1054         1115 :             if (e) {
    1055          471 :                 nextHarmony = toHarmony(e);
    1056              :             } else {
    1057              :                 // no harmony; look for fret diagram
    1058          644 :                 e = cur->findAnnotation(ElementType::FRET_DIAGRAM, track(), track());
    1059          644 :                 if (e) {
    1060            0 :                     nextHarmony = toFretDiagram(e)->harmony();
    1061              :                 }
    1062              :             }
    1063         1115 :             if (nextHarmony != nullptr) {
    1064              :                 //we have found the next chord symbol
    1065          471 :                 break;
    1066              :             }
    1067              :             //keep adding the duration of the current segment
    1068              :             //in case we are not able to find a next
    1069              :             //chord symbol
    1070          644 :             duration += cur->ticks();
    1071          644 :             cur = cur->next();
    1072              :         }
    1073              :         // Move segment reference to next measure
    1074          953 :         if (currentMeasure != endMeasure) {
    1075          932 :             currentMeasure = currentMeasure->nextMeasure();
    1076          932 :             if (currentMeasure && currentMeasure->mmRest()) {
    1077            0 :                 duration += currentMeasure->mmRest()->ticks();
    1078            0 :                 currentMeasure = currentMeasure->mmRest()->nextMeasure();
    1079              :             }
    1080          932 :             cur = (currentMeasure) ? currentMeasure->first() : nullptr;
    1081              :         } else {
    1082              :             // End of repeatSegment or search boundary reached
    1083           21 :             if (stopAtMeasureEnd) {
    1084            3 :                 break;
    1085           18 :             } else if (!nextHarmony) {
    1086              :                 // move to next RepeatSegment
    1087            9 :                 if (++rsIt != repeats.end()) {
    1088            0 :                     currentMeasure = (*rsIt)->firstMeasure();
    1089            0 :                     endMeasure     = (*rsIt)->lastMeasure();
    1090            0 :                     cur = currentMeasure->first();
    1091              :                 }
    1092              :             }
    1093              :         }
    1094          950 :     } while ((nextHarmony == nullptr) && (cur != nullptr));
    1095              : 
    1096          482 :     if (nextHarmony && rsIt != repeats.end()) {
    1097          471 :         int tickOffset = (*rsIt)->utick - (*rsIt)->tick;
    1098          471 :         int nextHarmonyUtick = nextHarmony->tick().ticks() + tickOffset;
    1099          471 :         duration = Fraction::fromTicks(nextHarmonyUtick - utick);
    1100              :     }
    1101              : 
    1102          482 :     return duration;
    1103              : }
    1104              : 
    1105              : //---------------------------------------------------------
    1106              : //   getRealizedHarmony
    1107              : //    get realized harmony or create one for the current symbol
    1108              : //    also updates the realized harmony and accounts for
    1109              : //    transposition. RealizedHarmony objects cannot be cached
    1110              : //    since the notes generated depends on context rather than
    1111              : //    just root, bass, chord symbol, and voicing.
    1112              : //---------------------------------------------------------
    1113              : 
    1114          492 : const RealizedHarmony& Harmony::getRealizedHarmony() const
    1115              : {
    1116          492 :     const Staff* st = staff();
    1117          492 :     IF_ASSERT_FAILED(st) {
    1118            0 :         return m_realizedHarmony;
    1119              :     }
    1120              : 
    1121          492 :     const Fraction tick = this->tick();
    1122          492 :     const CapoParams& capo = st->capo(tick);
    1123              : 
    1124          492 :     int offset = 0;
    1125          492 :     if (capo.active && CapoParams::TransposeMode::TAB_ONLY != capo.transposeMode) {
    1126            0 :         offset = capo.fretPosition;
    1127              :     }
    1128              : 
    1129          492 :     Interval interval = originPart()->instrument(tick)->transpose();
    1130          492 :     if (!style().styleB(Sid::concertPitch)) {
    1131          467 :         offset += interval.chromatic;
    1132              :     }
    1133              : 
    1134          492 :     HarmonyInfo* info = m_chords.empty() ? nullptr : m_chords.front();
    1135          492 :     int root = info ? info->rootTpc() : Tpc::TPC_INVALID;
    1136          492 :     int bass = info ? info->bassTpc() : Tpc::TPC_INVALID;
    1137              : 
    1138          492 :     m_realizedHarmony.update(root, bass, offset);
    1139              : 
    1140          492 :     return m_realizedHarmony;
    1141              : }
    1142              : 
    1143              : //---------------------------------------------------------
    1144              : //   realizedHarmony
    1145              : //    get realized harmony or create one for the current symbol
    1146              : //    without updating the realized harmony
    1147              : //---------------------------------------------------------
    1148              : 
    1149          106 : RealizedHarmony& Harmony::realizedHarmony()
    1150              : {
    1151          106 :     return m_realizedHarmony;
    1152              : }
    1153              : 
    1154          571 : const ParsedChord* Harmony::parsedForm()const
    1155              : {
    1156          571 :     if (m_chords.empty()) {
    1157            0 :         return nullptr;
    1158              :     }
    1159          571 :     return m_chords.front()->getParsedChord();
    1160              : }
    1161              : 
    1162            0 : Color Harmony::curColor(const rendering::PaintOptions& opt) const
    1163              : {
    1164            0 :     if (!opt.isPrinting && m_isMisspelled) {
    1165            0 :         return configuration()->criticalColor();
    1166              :     }
    1167              : 
    1168            0 :     return EngravingItem::curColor(opt);
    1169              : }
    1170              : 
    1171         1229 : void Harmony::setColor(const Color& color)
    1172              : {
    1173         1229 :     EngravingItem::setColor(color);
    1174              : 
    1175         1371 :     for (HarmonyRenderItem* item : ldata()->renderItemList()) {
    1176          142 :         if (ChordSymbolParen* paren = dynamic_cast<ChordSymbolParen*>(item)) {
    1177            8 :             paren->parenItem->setColor(color);
    1178              :         }
    1179              :     }
    1180         1229 : }
    1181              : 
    1182              : //---------------------------------------------------------
    1183              : //   width
    1184              : //---------------------------------------------------------
    1185              : 
    1186        10778 : double TextSegment::width() const
    1187              : {
    1188        10778 :     return FontMetrics::width(m_font, m_text);
    1189              : }
    1190              : 
    1191        14234 : double TextSegment::capHeight() const
    1192              : {
    1193        14234 :     return FontMetrics::capHeight(m_font);
    1194              : }
    1195              : 
    1196        53538 : RectF TextSegment::tightBoundingRect() const
    1197              : {
    1198        53538 :     return FontMetrics::tightBoundingRect(m_font, m_text);
    1199              : }
    1200              : 
    1201            0 : RectF TextSegment::boundingRect() const
    1202              : {
    1203            0 :     return FontMetrics::boundingRect(m_font, m_text);
    1204              : }
    1205              : 
    1206        10682 : void TextSegment::setFont(const muse::draw::Font& f)
    1207              : {
    1208        10682 :     m_font = f;
    1209        10682 :     if (f.type() != Font::Type::MusicSymbolText) {
    1210        10522 :         return;
    1211              :     }
    1212              : 
    1213              :     // Check all symbols in string are available in this font
    1214          160 :     FontMetrics fm(f);
    1215          160 :     bool fail = false;
    1216          320 :     for (size_t i = 0; i < m_text.size(); ++i) {
    1217          160 :         const Char& c = m_text.at(i);
    1218          160 :         if (c.isHighSurrogate()) {
    1219            0 :             if (i + 1 == m_text.size()) {
    1220            0 :                 ASSERT_X("bad string");
    1221              :             }
    1222            0 :             const Char& c2 = m_text.at(i + 1);
    1223            0 :             ++i;
    1224            0 :             char32_t v = Char::surrogateToUcs4(c, c2);
    1225            0 :             if (!fm.inFont(v)) {
    1226            0 :                 fail = true;
    1227            0 :                 break;
    1228              :             }
    1229              :         } else {
    1230          160 :             if (!fm.inFont(c.unicode())) {
    1231            0 :                 fail = true;
    1232            0 :                 break;
    1233              :             }
    1234              :         }
    1235              :     }
    1236              :     // Fallback to default musical text font
    1237          160 :     if (fail) {
    1238            0 :         m_font.setFamily(String::fromUtf8(FALLBACK_SYMBOLTEXT_FONT), Font::Type::MusicSymbolText);
    1239              :     }
    1240          160 : }
    1241              : 
    1242              : //---------------------------------------------------------
    1243              : //   spatiumChanged
    1244              : //---------------------------------------------------------
    1245              : 
    1246            5 : void Harmony::spatiumChanged(double oldValue, double newValue)
    1247              : {
    1248            5 :     TextBase::spatiumChanged(oldValue, newValue);
    1249            5 : }
    1250              : 
    1251              : //---------------------------------------------------------
    1252              : //   localSpatiumChanged
    1253              : //---------------------------------------------------------
    1254              : 
    1255            0 : void Harmony::localSpatiumChanged(double oldValue, double newValue)
    1256              : {
    1257            0 :     TextBase::localSpatiumChanged(oldValue, newValue);
    1258            0 : }
    1259              : 
    1260              : //---------------------------------------------------------
    1261              : //   addDegree
    1262              : //---------------------------------------------------------
    1263              : 
    1264            0 : void Harmony::addDegree(const HDegree& d)
    1265              : {
    1266            0 :     m_degreeList.push_back(d);
    1267            0 : }
    1268              : 
    1269              : //---------------------------------------------------------
    1270              : //   degreeList
    1271              : //---------------------------------------------------------
    1272              : 
    1273          748 : const std::vector<HDegree>& Harmony::degreeList() const
    1274              : {
    1275          748 :     return m_degreeList;
    1276              : }
    1277              : 
    1278              : //---------------------------------------------------------
    1279              : //   setHarmonyType
    1280              : //---------------------------------------------------------
    1281              : 
    1282           92 : void Harmony::setHarmonyType(HarmonyType val)
    1283              : {
    1284           92 :     m_harmonyType = val;
    1285           92 :     setPlacement(propertyDefault(Pid::PLACEMENT).value<PlacementV>());
    1286           92 :     switch (m_harmonyType) {
    1287           26 :     case HarmonyType::STANDARD:
    1288           26 :         initTextStyleType(TextStyleType::HARMONY_A);
    1289           26 :         break;
    1290           23 :     case HarmonyType::ROMAN:
    1291           23 :         initTextStyleType(TextStyleType::HARMONY_ROMAN);
    1292           23 :         break;
    1293           43 :     case HarmonyType::NASHVILLE:
    1294           43 :         initTextStyleType(TextStyleType::HARMONY_NASHVILLE);
    1295           43 :         break;
    1296              :     }
    1297              :     // TODO: convert text
    1298           92 : }
    1299              : 
    1300              : //---------------------------------------------------------
    1301              : //   typeUserName
    1302              : //---------------------------------------------------------
    1303              : 
    1304            0 : TranslatableString Harmony::typeUserName() const
    1305              : {
    1306            0 :     switch (m_harmonyType) {
    1307            0 :     case HarmonyType::ROMAN:
    1308            0 :         return TranslatableString("engraving", "Roman numeral");
    1309            0 :     case HarmonyType::NASHVILLE:
    1310            0 :         return TranslatableString("engraving", "Nashville number");
    1311            0 :     case HarmonyType::STANDARD:
    1312            0 :         break;
    1313              :     }
    1314            0 :     return EngravingItem::typeUserName();
    1315              : }
    1316              : 
    1317              : //---------------------------------------------------------
    1318              : //   accessibleInfo
    1319              : //---------------------------------------------------------
    1320              : 
    1321            0 : String Harmony::accessibleInfo() const
    1322              : {
    1323            0 :     return String(u"%1: %2").arg(translatedTypeUserName(), harmonyName());
    1324              : }
    1325              : 
    1326              : //---------------------------------------------------------
    1327              : //   screenReaderInfo
    1328              : //---------------------------------------------------------
    1329              : 
    1330            0 : String Harmony::screenReaderInfo() const
    1331              : {
    1332            0 :     return String(u"%1 %2").arg(translatedTypeUserName(), generateScreenReaderInfo());
    1333              : }
    1334              : 
    1335              : //---------------------------------------------------------
    1336              : //   generateScreenReaderInfo
    1337              : //---------------------------------------------------------
    1338              : 
    1339            0 : String Harmony::generateScreenReaderInfo() const
    1340              : {
    1341            0 :     String rez;
    1342            0 :     for (size_t i = 0; i < m_chords.size(); i++) {
    1343            0 :         HarmonyInfo* info = m_chords.at(i);
    1344            0 :         if (!tpcIsValid(info->rootTpc())) {
    1345            0 :             continue;
    1346              :         }
    1347            0 :         if (i != 0) {
    1348            0 :             rez += u" | ";
    1349              :         }
    1350              : 
    1351            0 :         switch (m_harmonyType) {
    1352            0 :         case HarmonyType::ROMAN: {
    1353            0 :             if (m_chords.empty()) {
    1354            0 :                 return u"";
    1355              :             }
    1356            0 :             String aux = info->textName();
    1357            0 :             bool hasUpper = aux.contains(u'I') || aux.contains(u'V');
    1358            0 :             bool hasLower = aux.contains(u'i') || aux.contains(u'v');
    1359            0 :             if (hasLower && !hasUpper) {
    1360            0 :                 rez = String(u"%1 %2").arg(rez, muse::mtrc("engraving", "lower case"));
    1361              :             }
    1362            0 :             aux = aux.toLower();
    1363              :             static const std::vector<std::pair<String, String> > rnaReplacements {
    1364              :                 { u"vii", u"7" },
    1365              :                 { u"vi", u"6" },
    1366              :                 { u"iv", u"4" },
    1367              :                 { u"v", u"5" },
    1368              :                 { u"iii", u"3" },
    1369              :                 { u"ii", u"2" },
    1370              :                 { u"i", u"1" },
    1371            0 :             };
    1372              :             static const std::vector<std::pair<String, String> > symbolReplacements {
    1373              :                 { u"bb", u"𝄫" },
    1374              :                 { u"##", u"𝄪" },
    1375              :                 { u"h", u"♮" },
    1376              :                 { u"\\♮", u"h" }, // \h should be h, so need to correct replacing in the previous step
    1377              :                 { u"#", u"♯" },
    1378              :                 { u"\\♯", u"#" }, // \# should be #, so need to correct replacing in the previous step
    1379              :                 { u"b", u"♭" },
    1380              :                 { u"\\♭", u"b" }, // \b should be b, so need to correct replacing in the previous step
    1381              :                 // TODO: use SMuFL glyphs and translate
    1382              :                 //{ "o", ""},
    1383              :                 //{ "0", ""},
    1384              :                 //{ "\+", ""},
    1385              :                 //{ "\^", ""},
    1386            0 :             };
    1387            0 :             for (auto const& r : rnaReplacements) {
    1388            0 :                 aux.replace(r.first, r.second);
    1389              :             }
    1390            0 :             for (auto const& r : symbolReplacements) {
    1391            0 :                 aux.replace(r.first, r.second);
    1392              :             }
    1393              :             // construct string one  character at a time
    1394            0 :             for (size_t j = 0; j < aux.size(); ++j) {
    1395            0 :                 rez = String(u"%1 %2").arg(rez).arg(aux.at(j));
    1396              :             }
    1397            0 :         }
    1398            0 :             return rez;
    1399            0 :         case HarmonyType::NASHVILLE: {
    1400            0 :             const Staff* st = staff();
    1401            0 :             Key key = st ? st->key(tick()) : Key::INVALID;
    1402            0 :             rez = String(u"%1 %2").arg(rez, tpc2Function(info->rootTpc(), key));
    1403            0 :             break;
    1404              :         }
    1405            0 :         case HarmonyType::STANDARD:
    1406              :         default:
    1407            0 :             rez = String(u"%1 %2").arg(rez, tpc2name(info->rootTpc(), NoteSpellingType::STANDARD, NoteCaseType::AUTO, true));
    1408              :         }
    1409              : 
    1410            0 :         if (!info->textName().isEmpty()) {
    1411            0 :             String aux = info->getParsedChord()->handle();
    1412            0 :             aux = aux.replace(u"#", u"♯").replace(u"<", u"");
    1413            0 :             String extension;
    1414              : 
    1415            0 :             for (String s : aux.split(u'>', muse::SkipEmptyParts)) {
    1416            0 :                 if (!s.contains(u"blues")) {
    1417            0 :                     s.replace(u"b", u"♭");
    1418              :                 }
    1419            0 :                 extension += s + u' ';
    1420            0 :             }
    1421            0 :             rez = String(u"%1 %2").arg(rez, extension);
    1422            0 :         } else {
    1423            0 :             rez = String(u"%1 %2").arg(rez, info->textName());
    1424              :         }
    1425              : 
    1426            0 :         if (tpcIsValid(info->bassTpc())) {
    1427            0 :             rez = String(u"%1 / %2").arg(rez, tpc2name(info->bassTpc(), NoteSpellingType::STANDARD, NoteCaseType::AUTO, true));
    1428              :         }
    1429              :     }
    1430              : 
    1431            0 :     return rez;
    1432            0 : }
    1433              : 
    1434              : //---------------------------------------------------------
    1435              : //   acceptDrop
    1436              : //---------------------------------------------------------
    1437              : 
    1438            0 : bool Harmony::acceptDrop(EditData& data) const
    1439              : {
    1440            0 :     EngravingItem* e = data.dropElement;
    1441            0 :     if (e->isFretDiagram()) {
    1442            0 :         return true;
    1443            0 :     } else if (e->isSymbol() || e->isFSymbol()) {
    1444              :         // symbols can be added in edit mode
    1445            0 :         if (data.getData(this)) {
    1446            0 :             return true;
    1447              :         } else {
    1448            0 :             return false;
    1449              :         }
    1450              :     }
    1451            0 :     return false;
    1452              : }
    1453              : 
    1454              : //---------------------------------------------------------
    1455              : //   drop
    1456              : //---------------------------------------------------------
    1457              : 
    1458            0 : EngravingItem* Harmony::drop(Transaction& tx, EditData& data)
    1459              : {
    1460            0 :     EngravingItem* e = data.dropElement;
    1461            0 :     if (e->isFretDiagram()) {
    1462            0 :         FretDiagram* fd = toFretDiagram(e);
    1463            0 :         fd->setOwnershipParent(ownershipParent());
    1464            0 :         fd->setTrack(track());
    1465            0 :         score()->undoAddElement(fd);
    1466            0 :     } else if (e->isSymbol() || e->isFSymbol()) {
    1467            0 :         TextBase::drop(tx, data);
    1468            0 :         renderer()->layoutText1(this);
    1469            0 :         e = 0;          // cannot select
    1470              :     } else {
    1471            0 :         LOGW("Harmony: cannot drop <%s>\n", e->typeName());
    1472            0 :         delete e;
    1473            0 :         e = 0;
    1474              :     }
    1475            0 :     return e;
    1476              : }
    1477              : 
    1478            0 : void Harmony::undoChangeProperty(Pid id, const PropertyValue& v, PropertyFlags ps)
    1479              : {
    1480            0 :     if (id == Pid::FONT_STYLE || id == Pid::FONT_FACE || id == Pid::FONT_SIZE) {
    1481            0 :         EngravingItem::undoChangeProperty(id, v, ps);
    1482            0 :     } else if (id == Pid::EXCLUDE_VERTICAL_ALIGN) {
    1483            0 :         TextBase::undoChangeProperty(id, v, ps);
    1484              : 
    1485            0 :         bool val = v.toBool();
    1486            0 :         FretDiagram* fd = getParentFretDiagram();
    1487            0 :         if (fd && fd->excludeVerticalAlign() != val) {
    1488            0 :             fd->undoChangeProperty(Pid::EXCLUDE_VERTICAL_ALIGN, val, ps);
    1489              :         }
    1490            0 :         Segment* parentSeg = getParentSeg();
    1491            0 :         if (!parentSeg) {
    1492            0 :             return;
    1493              :         }
    1494            0 :         for (EngravingItem* item : parentSeg->annotations()) {
    1495            0 :             if ((!item->isFretDiagram() && !item->isHarmony()) || item == this || track2staff(item->track()) != staffIdx()) {
    1496            0 :                 continue;
    1497              :             }
    1498              : 
    1499            0 :             if (item->excludeVerticalAlign() != val) {
    1500            0 :                 item->undoChangeProperty(Pid::EXCLUDE_VERTICAL_ALIGN, val, ps);
    1501              :             }
    1502              :         }
    1503            0 :         return;
    1504              :     }
    1505              : 
    1506            0 :     TextBase::undoChangeProperty(id, v, ps);
    1507              : }
    1508              : 
    1509              : //---------------------------------------------------------
    1510              : //   getProperty
    1511              : //---------------------------------------------------------
    1512              : 
    1513         9866 : PropertyValue Harmony::getProperty(Pid pid) const
    1514              : {
    1515         9866 :     switch (pid) {
    1516          748 :     case Pid::PLAY:
    1517          748 :         return PropertyValue(m_play);
    1518          748 :     case Pid::HARMONY_TYPE:
    1519          748 :         return PropertyValue(int(m_harmonyType));
    1520            0 :     case Pid::HARMONY_BASS_SCALE:
    1521            0 :         return m_bassScale;
    1522           25 :     case Pid::HARMONY_VOICE_LITERAL:
    1523           25 :         return m_realizedHarmony.literal();
    1524           25 :     case Pid::HARMONY_VOICING:
    1525           25 :         return int(m_realizedHarmony.voicing());
    1526            6 :     case Pid::HARMONY_DURATION:
    1527            6 :         return int(m_realizedHarmony.duration());
    1528          748 :     case Pid::HARMONY_DO_NOT_STACK_MODIFIERS:
    1529          748 :         return m_doNotStackModifiers;
    1530            0 :     case Pid::FONT_SIZE:
    1531            0 :         return m_fontSize;
    1532            0 :     case Pid::FONT_STYLE:
    1533            0 :         return static_cast<int>(m_fontStyle);
    1534            0 :     case Pid::FONT_FACE:
    1535            0 :         return m_fontFamily;
    1536         7566 :     default:
    1537         7566 :         return TextBase::getProperty(pid);
    1538              :     }
    1539              : }
    1540              : 
    1541              : //---------------------------------------------------------
    1542              : //   setProperty
    1543              : //---------------------------------------------------------
    1544              : 
    1545        27079 : bool Harmony::setProperty(Pid pid, const PropertyValue& v)
    1546              : {
    1547        27079 :     switch (pid) {
    1548            0 :     case Pid::PLAY:
    1549            0 :         m_play = v.toBool();
    1550            0 :         break;
    1551           29 :     case Pid::HARMONY_TYPE:
    1552           29 :         setHarmonyType(HarmonyType(v.toInt()));
    1553           29 :         break;
    1554         1134 :     case Pid::HARMONY_BASS_SCALE:
    1555         1134 :         setBassScale(v.toDouble());
    1556         1134 :         break;
    1557         1159 :     case Pid::HARMONY_VOICE_LITERAL:
    1558         1159 :         m_realizedHarmony.setLiteral(v.toBool());
    1559         1159 :         break;
    1560         1579 :     case Pid::HARMONY_VOICING:
    1561         1579 :         m_realizedHarmony.setVoicing(Voicing(v.toInt()));
    1562         1579 :         break;
    1563         1140 :     case Pid::HARMONY_DURATION:
    1564         1140 :         m_realizedHarmony.setDuration(HDuration(v.toInt()));
    1565         1140 :         break;
    1566            0 :     case Pid::EXCLUDE_VERTICAL_ALIGN: {
    1567            0 :         setExcludeVerticalAlign(v.toBool());
    1568            0 :         break;
    1569              :     }
    1570            0 :     case Pid::HARMONY_DO_NOT_STACK_MODIFIERS:
    1571            0 :         m_doNotStackModifiers = v.toBool();
    1572            0 :         break;
    1573            0 :     case Pid::TEXT:
    1574              :     {
    1575            0 :         String curText = xmlText();
    1576            0 :         TextBase::setProperty(pid, v);
    1577            0 :         setHarmony(v.value<String>());
    1578            0 :         String newText = xmlText();
    1579            0 :         if (newText != curText) {
    1580            0 :             FretDiagram* fretDiagram = ownershipParent()->isFretDiagram() ? toFretDiagram(ownershipParent()) : nullptr;
    1581            0 :             if (fretDiagram && !fretDiagram->isCustom(curText) && configuration()->autoUpdateFretboardDiagrams()) {
    1582            0 :                 fretDiagram->updateDiagram(plainText());
    1583              :             }
    1584            0 :             score()->rebuildFretBox();
    1585              :         }
    1586            0 :         break;
    1587            0 :     }
    1588        22038 :     default:
    1589        22038 :         return TextBase::setProperty(pid, v);
    1590              :     }
    1591         5041 :     triggerLayout();
    1592         5041 :     return true;
    1593              : }
    1594              : 
    1595              : //---------------------------------------------------------
    1596              : //   propertyDefault
    1597              : //---------------------------------------------------------
    1598              : 
    1599        10264 : PropertyValue Harmony::propertyDefault(Pid id) const
    1600              : {
    1601        10264 :     PropertyValue v;
    1602        10264 :     switch (id) {
    1603          748 :     case Pid::HARMONY_TYPE:
    1604          748 :         v = int(HarmonyType::STANDARD);
    1605          748 :         break;
    1606          748 :     case Pid::TEXT_STYLE: {
    1607          748 :         switch (m_harmonyType) {
    1608          733 :         case HarmonyType::STANDARD:
    1609          733 :             v = TextStyleType::HARMONY_A;
    1610          733 :             break;
    1611           15 :         case HarmonyType::ROMAN:
    1612           15 :             v = TextStyleType::HARMONY_ROMAN;
    1613           15 :             break;
    1614            0 :         case HarmonyType::NASHVILLE:
    1615            0 :             v = TextStyleType::HARMONY_NASHVILLE;
    1616            0 :             break;
    1617              :         }
    1618              :     }
    1619          748 :     break;
    1620            0 :     case Pid::HARMONY_BASS_SCALE:
    1621            0 :         v = style().styleV(Sid::chordBassNoteScale).toDouble();
    1622            0 :         break;
    1623          748 :     case Pid::HARMONY_DO_NOT_STACK_MODIFIERS:
    1624          748 :         return false;
    1625          748 :     case Pid::PLAY:
    1626          748 :         v = true;
    1627          748 :         break;
    1628              :     // fall-through
    1629         7272 :     default:
    1630         7272 :         v = TextBase::propertyDefault(id);
    1631         7272 :         break;
    1632              :     }
    1633         9516 :     return v;
    1634        10264 : }
    1635              : 
    1636           94 : bool Harmony::positionRelativeToNoteheadRest() const
    1637              : {
    1638           94 :     return !parent()->isFretDiagram();
    1639              : }
    1640              : 
    1641              : //---------------------------------------------------------
    1642              : //   getPropertyStyle
    1643              : //---------------------------------------------------------
    1644              : 
    1645         7910 : Sid Harmony::getPropertyStyle(Pid pid) const
    1646              : {
    1647         7910 :     if (pid == Pid::PLACEMENT) {
    1648          163 :         switch (m_harmonyType) {
    1649           97 :         case HarmonyType::STANDARD:
    1650           97 :             return Sid::harmonyPlacement;
    1651           23 :         case HarmonyType::ROMAN:
    1652           23 :             return Sid::romanNumeralPlacement;
    1653           43 :         case HarmonyType::NASHVILLE:
    1654           43 :             return Sid::nashvilleNumberPlacement;
    1655              :         }
    1656              :     }
    1657         7747 :     return TextBase::getPropertyStyle(pid);
    1658              : }
    1659              : 
    1660       134105 : double Harmony::mag() const
    1661              : {
    1662       134105 :     return EngravingItem::mag();
    1663              : }
    1664              : 
    1665           66 : HarmonyInfo::HarmonyInfo(const HarmonyInfo& h)
    1666              : {
    1667           66 :     m_id = h.m_id;
    1668           66 :     m_bassTpc = h.m_bassTpc;
    1669           66 :     m_rootTpc = h.m_rootTpc;
    1670           66 :     m_textName = h.m_textName;
    1671           66 :     m_score = h.m_score;
    1672           66 :     m_parsedChord = h.m_parsedChord ? new ParsedChord(*h.m_parsedChord) : 0;
    1673           66 : }
    1674              : 
    1675         1103 : HarmonyInfo::~HarmonyInfo()
    1676              : {
    1677         1103 :     if (m_parsedChord) {
    1678          774 :         delete m_parsedChord;
    1679              :     }
    1680         1103 : }
    1681              : }
        

Generated by: LCOV version 2.0-1