LCOV - code coverage report
Current view: top level - src/engraving/dom - figuredbass.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 41.7 % 696 290
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 "figuredbass.h"
      24              : 
      25              : #include "io/file.h"
      26              : 
      27              : #include "rendering/iscorerenderer.h"
      28              : #include "rw/xmlreader.h"
      29              : #include "style/textstyle.h"
      30              : 
      31              : #include "chord.h"
      32              : #include "factory.h"
      33              : #include "linkedobjects.h"
      34              : #include "measure.h"
      35              : #include "note.h"
      36              : #include "rest.h"
      37              : #include "score.h"
      38              : #include "segment.h"
      39              : 
      40              : #include "log.h"
      41              : 
      42              : using namespace mu;
      43              : using namespace muse::io;
      44              : using namespace mu::engraving;
      45              : 
      46              : namespace mu::engraving {
      47              : static std::vector<FiguredBassFont> g_FBFonts;
      48              : 
      49          441 : const std::vector<FiguredBassFont>& FiguredBass::FBFonts()
      50              : {
      51          441 :     return g_FBFonts;
      52              : }
      53              : 
      54              : //---------------------------------------------------------
      55              : //   figuredBassStyle
      56              : //---------------------------------------------------------
      57              : 
      58              : static const ElementStyle figuredBassStyle {
      59              :     { Sid::figuredBassMinDistance,             Pid::MIN_DISTANCE },
      60              : };
      61              : 
      62              : //---------------------------------------------------------
      63              : //   figuredBassTextStyle
      64              : //---------------------------------------------------------
      65              : 
      66              : static const ElementStyle figuredBassTextStyle {
      67              :     { Sid::figuredBassFontFace,                Pid::FONT_FACE },
      68              :     { Sid::figuredBassFontSize,                Pid::FONT_SIZE },
      69              :     { Sid::figuredBassFontStyle,               Pid::FONT_STYLE },
      70              : };
      71              : 
      72              : //---------------------------------------------------------
      73              : //   F I G U R E D   B A S S   I T E M
      74              : //---------------------------------------------------------
      75              : 
      76              : // used for indexed access to parenthesis chars
      77              : // (these is no normAccidToChar[], as accidentals may use mult. chars in normalized display):
      78              : const Char FiguredBassItem::NORM_PARENTH_TO_CHAR[int(FiguredBassItem::Parenthesis::NUMOF)] =
      79              : { 0, '(', ')', '[', ']' };
      80              : 
      81          128 : FiguredBassItem::FiguredBassItem(FiguredBass* parent, int l)
      82          128 :     : EngravingItem(ElementType::FIGURED_BASS_ITEM, parent), m_ord(l)
      83              : {
      84          128 :     m_prefix     = m_suffix = Modifier::NONE;
      85          128 :     m_digit      = FBIDigitNone;
      86          128 :     m_parenth[0]  = m_parenth[1] = m_parenth[2] = m_parenth[3] = m_parenth[4] = Parenthesis::NONE;
      87          128 :     m_contLine   = ContLine::NONE;
      88          128 : }
      89              : 
      90            0 : FiguredBassItem::FiguredBassItem(const FiguredBassItem& item)
      91            0 :     : EngravingItem(item)
      92              : {
      93            0 :     m_ord         = item.m_ord;
      94            0 :     m_prefix     = item.m_prefix;
      95            0 :     m_digit      = item.m_digit;
      96            0 :     m_suffix     = item.m_suffix;
      97            0 :     m_parenth[0]  = item.m_parenth[0];
      98            0 :     m_parenth[1]  = item.m_parenth[1];
      99            0 :     m_parenth[2]  = item.m_parenth[2];
     100            0 :     m_parenth[3]  = item.m_parenth[3];
     101            0 :     m_parenth[4]  = item.m_parenth[4];
     102            0 :     m_contLine   = item.m_contLine;
     103            0 : }
     104              : 
     105          248 : FiguredBassItem::~FiguredBassItem()
     106              : {
     107          248 : }
     108              : 
     109              : //---------------------------------------------------------
     110              : //   FiguredBassItem parse()
     111              : //
     112              : // converts a string into a property-based representation, if possible;
     113              : // return true on success | false if the string is non-conformant
     114              : //---------------------------------------------------------
     115              : 
     116            8 : bool FiguredBassItem::parse(String& str)
     117              : {
     118              :     int retVal;
     119              : 
     120            8 :     parseParenthesis(str, 0);
     121            8 :     retVal = parsePrefixSuffix(str, true);            // prefix
     122            8 :     if (retVal == -1) {
     123            0 :         return false;
     124              :     }
     125            8 :     parseParenthesis(str, 1);
     126            8 :     retVal = parseDigit(str);                         // digit
     127            8 :     if (retVal == -1) {
     128            0 :         return false;
     129              :     }
     130            8 :     parseParenthesis(str, 2);
     131            8 :     retVal = parsePrefixSuffix(str, false);           // suffix
     132            8 :     if (retVal == -1) {
     133            0 :         return false;
     134              :     }
     135            8 :     parseParenthesis(str, 3);
     136              :     // check for a possible cont. line symbol(s)
     137            8 :     m_contLine = ContLine::NONE;                         // contLine
     138            8 :     if (!str.empty() && (str.at(0) == u'-' || str.at(0) == u'_')) {             // 1 symbol: simple continuation
     139            1 :         m_contLine = ContLine::SIMPLE;
     140            1 :         str.remove(0, 1);
     141              :     }
     142            9 :     while (!str.empty() && (str.at(0) == u'-' || str.at(0) == u'_')) {          // more than 1 symbol: extended continuation
     143            1 :         m_contLine = ContLine::EXTENDED;
     144            1 :         str.remove(0, 1);
     145              :     }
     146            8 :     parseParenthesis(str, 4);
     147              : 
     148              :     // remove useless parentheses, moving external parentheses toward central digit element
     149            8 :     if (m_prefix == Modifier::NONE && m_parenth[1] == Parenthesis::NONE) {
     150            8 :         m_parenth[1] = m_parenth[0];
     151            8 :         m_parenth[0] = Parenthesis::NONE;
     152              :     }
     153            8 :     if (m_digit == FBIDigitNone && m_parenth[2] == Parenthesis::NONE) {
     154            0 :         m_parenth[2] = m_parenth[1];
     155            0 :         m_parenth[1] = Parenthesis::NONE;
     156              :     }
     157            8 :     if (m_contLine == ContLine::NONE && m_parenth[3] == Parenthesis::NONE) {
     158            7 :         m_parenth[3] = m_parenth[4];
     159            7 :         m_parenth[4] = Parenthesis::NONE;
     160              :     }
     161            8 :     if (m_suffix == Modifier::NONE && m_parenth[2] == Parenthesis::NONE) {
     162            2 :         m_parenth[2] = m_parenth[3];
     163            2 :         m_parenth[3] = Parenthesis::NONE;
     164              :     }
     165              : 
     166              :     // some checks:
     167              :     // if some extra input, str is not conformant
     168            8 :     if (!str.empty()) {
     169            0 :         return false;
     170              :     }
     171              :     // can't have BOTH prefix and suffix
     172              :     // prefix, digit, suffix and cont.line cannot be ALL empty
     173              :     // suffix cannot combine with empty digit
     174            8 :     if ((m_prefix != Modifier::NONE && m_suffix != Modifier::NONE)
     175            8 :         || (m_prefix == Modifier::NONE && m_digit == FBIDigitNone && m_suffix == Modifier::NONE && m_contLine == ContLine::NONE)
     176            8 :         || ((m_suffix == Modifier::CROSS || m_suffix == Modifier::BACKSLASH || m_suffix == Modifier::SLASH)
     177            1 :             && m_digit == FBIDigitNone)) {
     178            0 :         return false;
     179              :     }
     180            8 :     return true;
     181              : }
     182              : 
     183              : //---------------------------------------------------------
     184              : //   FiguredBassItem parsePrefixSuffix()
     185              : //
     186              : //    scans str to extract prefix or suffix properties. Stops at the first char which cannot fit.
     187              : //    Fitting chars are removed from str. DOES NOT generate any display text
     188              : //
     189              : // returns the number of Chars read from str or -1 if prefix / suffix has an illegal format
     190              : // (no prefix / suffix at all IS legal)
     191              : //---------------------------------------------------------
     192              : 
     193           16 : int FiguredBassItem::parsePrefixSuffix(String& str, bool bPrefix)
     194              : {
     195           16 :     Modifier* dest  = bPrefix ? &m_prefix : &m_suffix;
     196           16 :     bool done  = false;
     197           16 :     size_t size  = str.size();
     198           16 :     str = str.trimmed();
     199              : 
     200           16 :     *dest       = Modifier::NONE;
     201              : 
     202           24 :     while (str.size()) {
     203           17 :         switch (str.at(0).unicode()) {
     204            3 :         case 'b':
     205            3 :             if (*dest != Modifier::NONE) {
     206            1 :                 if (*dest == Modifier::FLAT) {          // FLAT may double a previous FLAT
     207            1 :                     *dest = Modifier::DOUBLEFLAT;
     208              :                 } else {
     209            0 :                     return -1;                        // but no other combination is acceptable
     210              :                 }
     211              :             } else {
     212            2 :                 *dest = Modifier::FLAT;
     213              :             }
     214            3 :             break;
     215            1 :         case 'h':
     216            1 :             if (*dest != Modifier::NONE) {              // cannot combine with any other accidental
     217            0 :                 return -1;
     218              :             }
     219            1 :             *dest = Modifier::NATURAL;
     220            1 :             break;
     221            3 :         case '#':
     222            3 :             if (*dest != Modifier::NONE) {
     223            1 :                 if (*dest == Modifier::SHARP) {         // SHARP may double a previous SHARP
     224            1 :                     *dest = Modifier::DOUBLESHARP;
     225              :                 } else {
     226            0 :                     return -1;                        // but no other combination is acceptable
     227              :                 }
     228              :             } else {
     229            2 :                 *dest = Modifier::SHARP;
     230              :             }
     231            3 :             break;
     232            1 :         case '+':
     233              :             // accept '+' as both a prefix and a suffix for harmony notation
     234            1 :             if (*dest != Modifier::NONE) {              // cannot combine with any other accidental
     235            0 :                 return -1;
     236              :             }
     237            1 :             *dest = Modifier::CROSS;
     238            1 :             break;
     239              :         // '\\' and '/' go into the suffix
     240            0 :         case '\\':
     241            0 :             if (m_suffix != Modifier::NONE) {            // cannot combine with any other accidental
     242            0 :                 return -1;
     243              :             }
     244            0 :             m_suffix = Modifier::BACKSLASH;
     245            0 :             break;
     246            0 :         case '/':
     247            0 :             if (m_suffix != Modifier::NONE) {            // cannot combine with any other accidental
     248            0 :                 return -1;
     249              :             }
     250            0 :             m_suffix = Modifier::SLASH;
     251            0 :             break;
     252            9 :         default:                                     // any other char: no longer in prefix/suffix
     253            9 :             done = true;
     254            9 :             break;
     255              :         }
     256           17 :         if (done) {
     257            9 :             break;
     258              :         }
     259            8 :         str.remove(0, 1);                             // 'eat' the char and continue
     260              :     }
     261              : 
     262           16 :     return static_cast<int>(size - str.size());                        // return how many chars we had read into prefix/suffix
     263              : }
     264              : 
     265              : //---------------------------------------------------------
     266              : //   FiguredBassItem parseDigit()
     267              : //
     268              : //    scans str to extract digit properties. Stops at the first char which cannot belong to digit part.
     269              : //    Fitting chars are removed from str. DOES NOT generate any display text
     270              : //
     271              : // returns the number of Chars read from str or -1 if no legal digit can be constructed
     272              : // (no digit at all IS legal)
     273              : //---------------------------------------------------------
     274              : 
     275            8 : int FiguredBassItem::parseDigit(String& str)
     276              : {
     277            8 :     size_t size   = str.size();
     278            8 :     str        = str.trimmed();
     279              : 
     280            8 :     m_digit = FBIDigitNone;
     281              : 
     282           16 :     while (str.size()) {
     283              :         // any digit acceptable
     284           14 :         if (str.at(0) >= u'0' && str.at(0) <= u'9') {
     285            8 :             if (m_digit == FBIDigitNone) {
     286            8 :                 m_digit = 0;
     287              :             }
     288            8 :             m_digit = m_digit * 10 + (str.at(0).unicode() - '0');
     289            8 :             str.remove(0, 1);
     290              :         }
     291              :         // anything else: no longer in digit part
     292              :         else {
     293            6 :             break;
     294              :         }
     295              :     }
     296              : 
     297            8 :     return static_cast<int>(size - str.size());
     298              : }
     299              : 
     300              : //---------------------------------------------------------
     301              : //   FiguredBassItem parseParenthesis()
     302              : //
     303              : //    scans str to extract a (possible) parenthesis, stores its code into parenth[parenthIdx]
     304              : //    and removes it from str. Only looks at first str char.
     305              : //
     306              : // returns the number of Chars read from str (actually 0 or 1).
     307              : //---------------------------------------------------------
     308              : 
     309           40 : int FiguredBassItem::parseParenthesis(String& str, int parenthIdx)
     310              : {
     311           40 :     if (str.empty()) {
     312           17 :         return 0;
     313              :     }
     314              : 
     315           23 :     char16_t c = str.at(0).unicode();
     316           23 :     Parenthesis code = Parenthesis::NONE;
     317           23 :     switch (c) {
     318            0 :     case '(':
     319            0 :         code = Parenthesis::ROUNDOPEN;
     320            0 :         break;
     321            0 :     case ')':
     322            0 :         code = Parenthesis::ROUNDCLOSED;
     323            0 :         break;
     324            0 :     case '[':
     325            0 :         code =Parenthesis::SQUAREDOPEN;
     326            0 :         break;
     327            0 :     case ']':
     328            0 :         code = Parenthesis::SQUAREDCLOSED;
     329            0 :         break;
     330           23 :     default:
     331           23 :         break;
     332              :     }
     333           23 :     m_parenth[parenthIdx] = code;
     334           23 :     if (code != Parenthesis::NONE) {
     335            0 :         str.remove(0, 1);
     336            0 :         return 1;
     337              :     }
     338           23 :     return 0;
     339              : }
     340              : 
     341              : //---------------------------------------------------------
     342              : //   FiguredBassItem normalizedText()
     343              : //
     344              : // returns a string with the normalized text, i.e. the text displayed while editing;
     345              : // this is a standard textual representation of the item properties
     346              : //---------------------------------------------------------
     347              : 
     348          128 : String FiguredBassItem::normalizedText() const
     349              : {
     350          128 :     String str;
     351          128 :     if (m_parenth[0] != Parenthesis::NONE) {
     352            0 :         str.append(NORM_PARENTH_TO_CHAR[int(m_parenth[0])]);
     353              :     }
     354              : 
     355          128 :     if (m_prefix != Modifier::NONE) {
     356            9 :         switch (m_prefix) {
     357            2 :         case Modifier::FLAT:
     358            2 :             str.append(u'b');
     359            2 :             break;
     360            1 :         case Modifier::NATURAL:
     361            1 :             str.append(u'h');
     362            1 :             break;
     363            4 :         case Modifier::SHARP:
     364            4 :             str.append(u'#');
     365            4 :             break;
     366            0 :         case Modifier::CROSS:
     367            0 :             str.append(u'+');
     368            0 :             break;
     369            1 :         case Modifier::DOUBLEFLAT:
     370            1 :             str.append(u"bb");
     371            1 :             break;
     372            1 :         case Modifier::DOUBLESHARP:
     373            1 :             str.append(u"##");
     374            1 :             break;
     375            0 :         default:
     376            0 :             break;
     377              :         }
     378              :     }
     379              : 
     380          128 :     if (m_parenth[1] != Parenthesis::NONE) {
     381            2 :         str.append(NORM_PARENTH_TO_CHAR[int(m_parenth[1])]);
     382              :     }
     383              : 
     384              :     // digit
     385          128 :     if (m_digit != FBIDigitNone) {
     386          114 :         str.append(String::number(m_digit));
     387              :     }
     388              : 
     389          128 :     if (m_parenth[2] != Parenthesis::NONE) {
     390            2 :         str.append(NORM_PARENTH_TO_CHAR[int(m_parenth[2])]);
     391              :     }
     392              : 
     393              :     // suffix
     394          128 :     if (m_suffix != Modifier::NONE) {
     395           19 :         switch (m_suffix) {
     396            2 :         case Modifier::FLAT:
     397            2 :             str.append(u'b');
     398            2 :             break;
     399            2 :         case Modifier::NATURAL:
     400            2 :             str.append(u'h');
     401            2 :             break;
     402            5 :         case Modifier::SHARP:
     403            5 :             str.append(u'#');
     404            5 :             break;
     405            3 :         case Modifier::CROSS:
     406            3 :             str.append(u'+');
     407            3 :             break;
     408            1 :         case Modifier::BACKSLASH:
     409            1 :             str.append(u'\\');
     410            1 :             break;
     411            2 :         case Modifier::SLASH:
     412            2 :             str.append(u'/');
     413            2 :             break;
     414            2 :         case Modifier::DOUBLEFLAT:
     415            2 :             str.append(u"bb");
     416            2 :             break;
     417            2 :         case Modifier::DOUBLESHARP:
     418            2 :             str.append(u"##");
     419            2 :             break;
     420            0 :         default:
     421            0 :             break;
     422              :         }
     423              :     }
     424              : 
     425          128 :     if (m_parenth[3] != Parenthesis::NONE) {
     426            0 :         str.append(NORM_PARENTH_TO_CHAR[int(m_parenth[3])]);
     427              :     }
     428          128 :     if (m_contLine > ContLine::NONE) {
     429           17 :         str.append('_');
     430           17 :         if (m_contLine > ContLine::SIMPLE) {
     431           11 :             str.append('_');
     432              :         }
     433              :     }
     434          128 :     if (m_parenth[4] != Parenthesis::NONE) {
     435            0 :         str.append(NORM_PARENTH_TO_CHAR[int(m_parenth[4])]);
     436              :     }
     437              : 
     438          128 :     return str;
     439            0 : }
     440              : 
     441              : //---------------------------------------------------------
     442              : //   PROPERTY METHODS
     443              : //---------------------------------------------------------
     444              : 
     445            0 : PropertyValue FiguredBassItem::getProperty(Pid propertyId) const
     446              : {
     447            0 :     switch (propertyId) {
     448            0 :     case Pid::FBPREFIX:
     449            0 :         return int(m_prefix);
     450            0 :     case Pid::FBDIGIT:
     451            0 :         return m_digit;
     452            0 :     case Pid::FBSUFFIX:
     453            0 :         return int(m_suffix);
     454            0 :     case Pid::FBCONTINUATIONLINE:
     455            0 :         return int(m_contLine);
     456            0 :     case Pid::FBPARENTHESIS1:
     457            0 :         return int(m_parenth[0]);
     458            0 :     case Pid::FBPARENTHESIS2:
     459            0 :         return int(m_parenth[1]);
     460            0 :     case Pid::FBPARENTHESIS3:
     461            0 :         return int(m_parenth[2]);
     462            0 :     case Pid::FBPARENTHESIS4:
     463            0 :         return int(m_parenth[3]);
     464            0 :     case Pid::FBPARENTHESIS5:
     465            0 :         return int(m_parenth[4]);
     466            0 :     default:
     467            0 :         return EngravingItem::getProperty(propertyId);
     468              :     }
     469              : }
     470              : 
     471            0 : bool FiguredBassItem::setProperty(Pid propertyId, const PropertyValue& v)
     472              : {
     473            0 :     score()->addRefresh(canvasBoundingRect());
     474            0 :     int val = v.toInt();
     475            0 :     switch (propertyId) {
     476            0 :     case Pid::FBPREFIX:
     477            0 :         if (val < int(Modifier::NONE) || val >= int(Modifier::NUMOF)) {
     478            0 :             return false;
     479              :         }
     480            0 :         m_prefix = (Modifier)val;
     481            0 :         break;
     482            0 :     case Pid::FBDIGIT:
     483            0 :         if (val < 1 || val > 9) {
     484            0 :             return false;
     485              :         }
     486            0 :         m_digit = val;
     487            0 :         break;
     488            0 :     case Pid::FBSUFFIX:
     489            0 :         if (val < int(Modifier::NONE) || val >= int(Modifier::NUMOF)) {
     490            0 :             return false;
     491              :         }
     492            0 :         m_suffix = (Modifier)val;
     493            0 :         break;
     494            0 :     case Pid::FBCONTINUATIONLINE:
     495            0 :         m_contLine = (ContLine)val;
     496            0 :         break;
     497            0 :     case Pid::FBPARENTHESIS1:
     498            0 :         if (val < int(Parenthesis::NONE) || val >= int(Parenthesis::NUMOF)) {
     499            0 :             return false;
     500              :         }
     501            0 :         m_parenth[0] = (Parenthesis)val;
     502            0 :         break;
     503            0 :     case Pid::FBPARENTHESIS2:
     504            0 :         if (val < int(Parenthesis::NONE) || val >= int(Parenthesis::NUMOF)) {
     505            0 :             return false;
     506              :         }
     507            0 :         m_parenth[1] = (Parenthesis)val;
     508            0 :         break;
     509            0 :     case Pid::FBPARENTHESIS3:
     510            0 :         if (val < int(Parenthesis::NONE) || val >= int(Parenthesis::NUMOF)) {
     511            0 :             return false;
     512              :         }
     513            0 :         m_parenth[2] = (Parenthesis)val;
     514            0 :         break;
     515            0 :     case Pid::FBPARENTHESIS4:
     516            0 :         if (val < int(Parenthesis::NONE) || val >= int(Parenthesis::NUMOF)) {
     517            0 :             return false;
     518              :         }
     519            0 :         m_parenth[3] = (Parenthesis)val;
     520            0 :         break;
     521            0 :     case Pid::FBPARENTHESIS5:
     522            0 :         if (val < int(Parenthesis::NONE) || val >= int(Parenthesis::NUMOF)) {
     523            0 :             return false;
     524              :         }
     525            0 :         m_parenth[4] = (Parenthesis)val;
     526            0 :         break;
     527            0 :     default:
     528            0 :         return EngravingItem::setProperty(propertyId, v);
     529              :     }
     530            0 :     triggerLayout();
     531            0 :     return true;
     532              : }
     533              : 
     534            0 : PropertyValue FiguredBassItem::propertyDefault(Pid id) const
     535              : {
     536            0 :     switch (id) {
     537            0 :     case Pid::FBPREFIX:
     538              :     case Pid::FBSUFFIX:
     539            0 :         return int(Modifier::NONE);
     540            0 :     case Pid::FBDIGIT:
     541            0 :         return FBIDigitNone;
     542            0 :     case Pid::FBCONTINUATIONLINE:
     543            0 :         return static_cast<int>(ContLine::NONE);
     544            0 :     default:
     545            0 :         return EngravingItem::propertyDefault(id);
     546              :     }
     547              : }
     548              : 
     549            0 : void FiguredBassItem::regenerateDisplayText()
     550              : {
     551              :     // re-generate displayText
     552            0 :     renderer()->layoutItem(this);
     553            0 : }
     554              : 
     555              : //---------------------------------------------------------
     556              : //   UNDOABLE PROPERTY SETTERS
     557              : //---------------------------------------------------------
     558              : 
     559            0 : void FiguredBassItem::undoSetPrefix(Modifier pref)
     560              : {
     561            0 :     if (pref <= Modifier::CROSS) {
     562            0 :         undoChangeProperty(Pid::FBPREFIX, (int)pref);
     563              :         // if setting some prefix and there is a suffix already, clear suffix
     564            0 :         if (pref != Modifier::NONE && m_suffix != Modifier::NONE) {
     565            0 :             undoChangeProperty(Pid::FBSUFFIX, int(Modifier::NONE));
     566              :         }
     567            0 :         regenerateDisplayText();
     568              :     }
     569            0 : }
     570              : 
     571            0 : void FiguredBassItem::undoSetDigit(int digit)
     572              : {
     573            0 :     if (digit >= 0 && digit <= 9) {
     574            0 :         undoChangeProperty(Pid::FBDIGIT, digit);
     575            0 :         regenerateDisplayText();
     576              :     }
     577            0 : }
     578              : 
     579            0 : void FiguredBassItem::undoSetSuffix(Modifier suff)
     580              : {
     581            0 :     undoChangeProperty(Pid::FBSUFFIX, int(suff));
     582              :     // if setting some suffix and there is a prefix already, clear prefix
     583            0 :     if (suff != Modifier::NONE && m_prefix != Modifier::NONE) {
     584            0 :         undoChangeProperty(Pid::FBPREFIX, int(Modifier::NONE));
     585              :     }
     586            0 :     regenerateDisplayText();
     587            0 : }
     588              : 
     589            0 : void FiguredBassItem::undoSetContLine(ContLine val)
     590              : {
     591            0 :     undoChangeProperty(Pid::FBCONTINUATIONLINE, int(val));
     592            0 :     regenerateDisplayText();
     593            0 : }
     594              : 
     595            0 : void FiguredBassItem::undoSetParenth1(Parenthesis par)
     596              : {
     597            0 :     undoChangeProperty(Pid::FBPARENTHESIS1, int(par));
     598            0 :     regenerateDisplayText();
     599            0 : }
     600              : 
     601            0 : void FiguredBassItem::undoSetParenth2(Parenthesis par)
     602              : {
     603            0 :     undoChangeProperty(Pid::FBPARENTHESIS2, int(par));
     604            0 :     regenerateDisplayText();
     605            0 : }
     606              : 
     607            0 : void FiguredBassItem::undoSetParenth3(Parenthesis par)
     608              : {
     609            0 :     undoChangeProperty(Pid::FBPARENTHESIS3, int(par));
     610            0 :     regenerateDisplayText();
     611            0 : }
     612              : 
     613            0 : void FiguredBassItem::undoSetParenth4(Parenthesis par)
     614              : {
     615            0 :     undoChangeProperty(Pid::FBPARENTHESIS4, int(par));
     616            0 :     regenerateDisplayText();
     617            0 : }
     618              : 
     619            0 : void FiguredBassItem::undoSetParenth5(Parenthesis par)
     620              : {
     621            0 :     undoChangeProperty(Pid::FBPARENTHESIS5, int(par));
     622            0 :     regenerateDisplayText();
     623            0 : }
     624              : 
     625              : //---------------------------------------------------------
     626              : //   startsWithParenthesis
     627              : //---------------------------------------------------------
     628              : 
     629           75 : bool FiguredBassItem::startsWithParenthesis() const
     630              : {
     631           75 :     if (m_prefix != Modifier::NONE) {
     632            8 :         return m_parenth[0] != Parenthesis::NONE;
     633              :     }
     634           67 :     if (m_digit != FBIDigitNone) {
     635           57 :         return m_parenth[1] != Parenthesis::NONE;
     636              :     }
     637           10 :     if (m_suffix != Modifier::NONE) {
     638            0 :         return m_parenth[2] != Parenthesis::NONE;
     639              :     }
     640           10 :     return false;
     641              : }
     642              : 
     643              : //---------------------------------------------------------
     644              : //   F I G U R E D   B A S S
     645              : //---------------------------------------------------------
     646              : 
     647          100 : FiguredBass::FiguredBass(DummyParentOr<Segment> parent)
     648          100 :     : TextBase(ElementType::FIGURED_BASS, parent, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
     649              : {
     650          100 :     initElementStyle(&figuredBassStyle);
     651              :     // figured bass inherits from TextBase for layout purposes
     652              :     // but there is no specific text style to use as a basis for styled properties
     653              :     // (this is true for historical reasons due to the special layout of figured bass)
     654              :     // override the styled property definitions
     655         1700 :     for (const auto& p : *textStyle(textStyleType())) {
     656         1600 :         setPropertyFlags(p.pid, PropertyFlags::NOSTYLE);
     657              :     }
     658          400 :     for (const StyledProperty& p : figuredBassTextStyle) {
     659          300 :         setPropertyFlags(p.pid, PropertyFlags::STYLED);
     660          300 :         setProperty(p.pid, styleValue(p.pid, p.sid));
     661              :     }
     662          100 :     setOnNote(true);
     663          100 :     setTicks(Fraction(0, 1));
     664          100 :     muse::DeleteAll(m_items);
     665          100 :     m_items.clear();
     666          100 : }
     667              : 
     668            0 : FiguredBass::FiguredBass(const FiguredBass& fb)
     669            0 :     : TextBase(fb)
     670              : {
     671            0 :     setOnNote(fb.onNote());
     672            0 :     setTicks(fb.ticks());
     673            0 :     for (auto i : fb.m_items) {       // deep copy is needed
     674            0 :         FiguredBassItem* fbi = new FiguredBassItem(*i);
     675            0 :         fbi->setOwnershipParent(this);
     676            0 :         m_items.push_back(fbi);
     677              :     }
     678              : //      items = fb.items;
     679            0 : }
     680              : 
     681          192 : FiguredBass::~FiguredBass()
     682              : {
     683          220 :     for (FiguredBassItem* item : m_items) {
     684          124 :         delete item;
     685              :     }
     686          192 : }
     687              : 
     688              : //---------------------------------------------------------
     689              : //   getPropertyStyle
     690              : //---------------------------------------------------------
     691              : 
     692          200 : Sid FiguredBass::getPropertyStyle(Pid id) const
     693              : {
     694              :     // do not use TextBase::getPropertyStyle
     695              :     // as most text style properties do not apply
     696          710 :     for (const StyledProperty& p : figuredBassTextStyle) {
     697          555 :         if (p.pid == id) {
     698           45 :             return p.sid;
     699              :         }
     700              :     }
     701          155 :     return EngravingItem::getPropertyStyle(id);
     702              : }
     703              : 
     704            0 : void FiguredBass::startEdit(EditData& ed)
     705              : {
     706            0 :     clearItems();
     707            0 :     renderer()->layoutText1(this);   // re-layout without F.B.-specific formatting.
     708            0 :     TextBase::startEdit(ed);
     709            0 : }
     710              : 
     711            0 : bool FiguredBass::isEditAllowed(EditData& ed) const
     712              : {
     713            0 :     if (isTextNavigationKey(ed.key, ed.modifiers)) {
     714            0 :         return false;
     715              :     }
     716              : 
     717            0 :     if ((ed.key == Key_Left || ed.key == Key_Right) && (ed.modifiers & ControlModifier)) {
     718            0 :         return false;
     719              :     }
     720              : 
     721            0 :     return TextBase::isEditAllowed(ed);
     722              : }
     723              : 
     724            0 : void FiguredBass::endEdit(EditData& ed)
     725              : {
     726            0 :     TextBase::endEdit(ed);
     727            0 :     regenerateText();
     728            0 : }
     729              : 
     730            0 : void FiguredBass::regenerateText()
     731              : {
     732              :     // as the standard text editor keeps inserting spurious HTML formatting and styles
     733              :     // retrieve and work only on the plain text
     734            0 :     const String txt = plainText();
     735            0 :     if (txt.isEmpty()) { // if no text, nothing to do
     736            0 :         return;
     737              :     }
     738              : 
     739              :     // split text into lines and create an item for each line
     740            0 :     StringList list = txt.split(u'\n', muse::SkipEmptyParts);
     741            0 :     clearItems();
     742            0 :     String normalizedText;
     743            0 :     int idx = 0;
     744            0 :     for (String str : list) {
     745            0 :         FiguredBassItem* pItem = new FiguredBassItem(this, idx++);
     746            0 :         if (!pItem->parse(str)) {               // if any item fails parsing
     747            0 :             clearItems();
     748              :             // TODO: this `startCmd` call is possibly invalid
     749            0 :             score()->startCmd(TranslatableString("undoableAction", "Regenerate figured bass text"));
     750            0 :             triggerLayout();
     751            0 :             score()->endCmd();
     752            0 :             delete pItem;
     753            0 :             return;
     754              :         }
     755            0 :         addItemToLinked(pItem);
     756              : 
     757              :         // add item normalized text
     758            0 :         if (!normalizedText.isEmpty()) {
     759            0 :             normalizedText.append('\n');
     760              :         }
     761            0 :         normalizedText.append(pItem->normalizedText());
     762            0 :     }
     763              :     // if all items parsed and text is styled, replaced entered text with normalized text
     764            0 :     if (m_items.size()) {
     765            0 :         undoChangeProperty(Pid::TEXT, normalizedText);
     766              :     }
     767              : 
     768              :     // TODO: this `startCmd` call is possibly invalid
     769            0 :     score()->startCmd(TranslatableString("undoableAction", "Regenerate figured bass text"));
     770            0 :     triggerLayout();
     771            0 :     score()->endCmd();
     772            0 : }
     773              : 
     774              : //---------------------------------------------------------
     775              : //   setSelected /setVisible
     776              : //
     777              : //    forward flags to items
     778              : //---------------------------------------------------------
     779              : 
     780            4 : void FiguredBass::setSelected(bool flag)
     781              : {
     782            4 :     EngravingItem::setSelected(flag);
     783            9 :     for (FiguredBassItem* item : m_items) {
     784            5 :         item->setSelected(flag);
     785              :     }
     786            4 : }
     787              : 
     788           45 : void FiguredBass::setVisible(bool flag)
     789              : {
     790           45 :     EngravingItem::setVisible(flag);
     791           45 :     for (FiguredBassItem* item : m_items) {
     792            0 :         item->setVisible(flag);
     793              :     }
     794           45 : }
     795              : 
     796              : //---------------------------------------------------------
     797              : //   nextFiguredBass
     798              : //
     799              : //    returns the next *contiguous* FiguredBass element if it exists,
     800              : //    i.e. the FiguredBass element which starts where 'this' ends
     801              : //    returns 0 if none
     802              : //---------------------------------------------------------
     803              : 
     804            0 : FiguredBass* FiguredBass::nextFiguredBass() const
     805              : {
     806            0 :     if (m_ticks <= Fraction(0, 1)) {                                      // if _ticks unset, no clear idea of when 'this' ends
     807            0 :         return 0;
     808              :     }
     809              :     Segment* nextSegm;                                   // the Segment beyond this' segment
     810            0 :     Fraction nextTick = segment()->tick() + m_ticks;      // the tick beyond this' duration
     811              : 
     812              :     // locate the ChordRest segment right after this' end
     813            0 :     nextSegm = score()->tick2segment(nextTick, true, SegmentType::ChordRest);
     814            0 :     if (nextSegm == 0) {
     815            0 :         return 0;
     816              :     }
     817              : 
     818              :     // scan segment annotations for an existing FB element in the this' staff
     819            0 :     for (EngravingItem* e : nextSegm->annotations()) {
     820            0 :         if (e->isFiguredBass() && e->track() == track()) {
     821            0 :             return toFiguredBass(e);
     822              :         }
     823              :     }
     824              : 
     825            0 :     return 0;
     826              : }
     827              : 
     828              : //---------------------------------------------------------
     829              : //   additionalContLineX
     830              : //
     831              : //    if there is a continuation line, without other text elements, at pagePosY, returns its X coord (in page coords)
     832              : //    returns 0 if no cont.line there or if there are text elements before the cont.line
     833              : //
     834              : //    In practice, returns the X coord of a cont. line which can be the continuation of a previous cont. line
     835              : //
     836              : //    Note: pagePosY is the Y coord of the FiguredBassItem containing the line, not of the line itself,
     837              : //    as line position might depend on styles.
     838              : //---------------------------------------------------------
     839              : 
     840            0 : double FiguredBass::additionalContLineX(double pagePosY) const
     841              : {
     842            0 :     PointF pgPos = pagePos();
     843            0 :     for (FiguredBassItem* fbi : m_items) {
     844              :         // if item has cont.line but nothing before it
     845              :         // and item Y coord near enough to pagePosY
     846            0 :         if (fbi->contLine() != FiguredBassItem::ContLine::NONE
     847            0 :             && fbi->digit() == FBIDigitNone
     848            0 :             && fbi->prefix() == FiguredBassItem::Modifier::NONE
     849            0 :             && fbi->suffix() == FiguredBassItem::Modifier::NONE
     850            0 :             && fbi->parenth4() == FiguredBassItem::Parenthesis::NONE
     851            0 :             && std::abs(pgPos.y() + fbi->ldata()->pos().y() - pagePosY) < 0.05) {
     852            0 :             return pgPos.x() + fbi->ldata()->pos().x();
     853              :         }
     854              :     }
     855              : 
     856            0 :     return 0.0;                                 // no suitable line
     857              : }
     858              : 
     859              : //---------------------------------------------------------
     860              : //   PROPERTY METHODS
     861              : //---------------------------------------------------------
     862              : 
     863          108 : PropertyValue FiguredBass::getProperty(Pid propertyId) const
     864              : {
     865          108 :     return TextBase::getProperty(propertyId);
     866              : }
     867              : 
     868         2053 : bool FiguredBass::setProperty(Pid propertyId, const PropertyValue& v)
     869              : {
     870         2053 :     score()->addRefresh(canvasBoundingRect());
     871         2053 :     if (propertyId == Pid::TEXT_LINKED_TO_MASTER) {
     872            0 :         if (TextBase::setProperty(propertyId, v)) {
     873            0 :             regenerateText();
     874            0 :             return true;
     875              :         }
     876            0 :         return false;
     877              :     }
     878         2053 :     return TextBase::setProperty(propertyId, v);
     879              : }
     880              : 
     881          192 : PropertyValue FiguredBass::propertyDefault(Pid id) const
     882              : {
     883          192 :     return TextBase::propertyDefault(id);
     884              : }
     885              : 
     886            0 : void FiguredBass::clearItems()
     887              : {
     888            0 :     const std::list<EngravingObject*> links = linkList();
     889            0 :     for (EngravingObject* linkedObject : links) {
     890            0 :         if (!linkedObject || !linkedObject->isFiguredBass()) {
     891            0 :             continue;
     892              :         }
     893            0 :         if (linkedObject == this) {
     894            0 :             muse::DeleteAll(m_items);
     895            0 :             m_items.clear();
     896              :         } else {
     897            0 :             bool isThisTextLinked = getProperty(Pid::TEXT_LINKED_TO_MASTER).toBool();
     898            0 :             bool isOtherTextLinked = linkedObject->getProperty(Pid::TEXT_LINKED_TO_MASTER).toBool();
     899            0 :             if ((score()->isMaster() && !isOtherTextLinked)
     900            0 :                 || (!score()->isMaster() && !isThisTextLinked)) {
     901            0 :                 continue;
     902              :             }
     903            0 :             FiguredBass* linkedFb = toFiguredBass(linkedObject);
     904            0 :             muse::DeleteAll(linkedFb->m_items);
     905            0 :             linkedFb->m_items.clear();
     906              :         }
     907              :     }
     908            0 : }
     909              : 
     910            0 : void FiguredBass::addItemToLinked(FiguredBassItem* item)
     911              : {
     912            0 :     const std::list<EngravingObject*> links = linkList();
     913            0 :     for (EngravingObject* linkedObject : links) {
     914            0 :         if (!linkedObject || !linkedObject->isFiguredBass()) {
     915            0 :             continue;
     916              :         }
     917            0 :         Score* linkedScore = linkedObject->score();
     918            0 :         if (linkedObject == this) {
     919            0 :             item->setTrack(track());
     920            0 :             item->setOwnershipParent(this);
     921            0 :             m_items.push_back(item);
     922            0 :             score()->doUndoAddElement(item);
     923              :         } else {
     924            0 :             bool isThisTextLinked = getProperty(Pid::TEXT_LINKED_TO_MASTER).toBool();
     925            0 :             bool isOtherTextLinked = linkedObject->getProperty(Pid::TEXT_LINKED_TO_MASTER).toBool();
     926            0 :             if ((score()->isMaster() && !isOtherTextLinked)
     927            0 :                 || (!score()->isMaster() && !isThisTextLinked)) {
     928            0 :                 continue;
     929              :             }
     930            0 :             FiguredBass* linkedFb = toFiguredBass(linkedObject);
     931            0 :             FiguredBassItem* itemClone = item->clone();
     932              : 
     933            0 :             itemClone->linkTo(item);
     934            0 :             itemClone->setTrack(linkedFb->track());
     935            0 :             itemClone->setOwnershipParent(linkedFb);
     936            0 :             itemClone->setScore(linkedFb->score());
     937            0 :             linkedFb->appendItem(itemClone);
     938            0 :             linkedScore->doUndoAddElement(itemClone);
     939              :         }
     940              :     }
     941            0 : }
     942              : 
     943              : //---------------------------------------------------------
     944              : //   STATIC FUNCTION
     945              : //    adding a new FiguredBass to a Segment;
     946              : //    the main purpose of this function is to ensure that ONLY ONE F.b. element exists for each Segment/staff;
     947              : //    it either re-uses an existing FiguredBass or creates a new one if none is found;
     948              : //    returns the FiguredBass and sets pNew to true if it has been newly created.
     949              : //
     950              : //    Sets an initial duration of the element up to the next ChordRest of the same staff.
     951              : //
     952              : //    As the F.b. very concept requires the base chord to have ONLY ONE note,
     953              : //    FiguredBass elements are created and looked for only in the first track of the staff.
     954              : //---------------------------------------------------------
     955              : 
     956            0 : FiguredBass* FiguredBass::addFiguredBassToSegment(Segment* seg, track_idx_t track, const Fraction& extTicks, bool* pNew)
     957              : {
     958            0 :     Fraction endTick;                        // where this FB is initially assumed to end
     959            0 :     staff_idx_t staff = track / VOICES;          // convert track to staff
     960            0 :     track = staff * VOICES;                     // first track for this staff
     961              : 
     962              :     // scan segment annotations for an existing FB element in the same staff
     963            0 :     FiguredBass* fb = 0;
     964            0 :     for (EngravingItem* e : seg->annotations()) {
     965            0 :         if (e->isFiguredBass() && (e->track() / VOICES) == staff) {
     966              :             // an FB already exists in segment: re-use it
     967            0 :             fb = toFiguredBass(e);
     968            0 :             *pNew = false;
     969            0 :             endTick = seg->tick() + fb->ticks();
     970            0 :             break;
     971              :         }
     972              :     }
     973            0 :     if (fb == 0) {                            // no FB at segment: create new
     974            0 :         fb = Factory::createFiguredBass(seg);
     975            0 :         fb->setTrack(track);
     976              : 
     977              :         // locate next SegChordRest in the same staff to estimate presumed duration of element
     978            0 :         endTick = Fraction::max();
     979              :         Segment* nextSegm;
     980            0 :         for (voice_idx_t iVoice = 0; iVoice < VOICES; iVoice++) {
     981            0 :             nextSegm = seg->nextCR(track + iVoice);
     982            0 :             if (nextSegm && nextSegm->tick() < endTick) {
     983            0 :                 endTick = nextSegm->tick();
     984              :             }
     985              :         }
     986            0 :         if (endTick == Fraction::max()) {               // no next segment: set up to score end
     987            0 :             Measure* meas = seg->score()->lastMeasure();
     988            0 :             endTick = meas->endTick();
     989              :         }
     990            0 :         fb->setTicks(endTick - seg->tick());
     991              : 
     992              :         // set onNote status
     993            0 :         fb->setOnNote(false);                   // assume not onNote
     994            0 :         for (track_idx_t i = track; i < track + VOICES; i++) {           // if segment has chord in staff, set onNote
     995            0 :             if (seg->element(i) && seg->element(i)->isChord()) {
     996            0 :                 fb->setOnNote(true);
     997            0 :                 break;
     998              :             }
     999              :         }
    1000            0 :         *pNew = true;
    1001              :     }
    1002              : 
    1003              :     // if we are extending a previous FB
    1004            0 :     if (extTicks > Fraction(0, 1)) {
    1005              :         // locate previous FB for same staff
    1006              :         Segment* prevSegm;
    1007            0 :         FiguredBass* prevFB = 0;
    1008            0 :         for (prevSegm = seg->prev1(SegmentType::Duration); prevSegm;
    1009            0 :              prevSegm = prevSegm->prev1(SegmentType::Duration)) {
    1010            0 :             for (EngravingItem* e : prevSegm->annotations()) {
    1011            0 :                 if (e->isFiguredBass() && (e->track()) == track) {
    1012            0 :                     prevFB = toFiguredBass(e);             // previous FB found
    1013            0 :                     break;
    1014              :                 }
    1015              :             }
    1016            0 :             if (prevFB) {
    1017              :                 // if previous FB did not stop more than extTicks before this FB...
    1018            0 :                 Fraction delta = seg->tick() - prevFB->segment()->tick();
    1019            0 :                 if (prevFB->ticks() + extTicks >= delta) {
    1020            0 :                     prevFB->setTicks(delta);                // update prev FB ticks to last up to this FB
    1021              :                 }
    1022            0 :                 break;
    1023              :             }
    1024              :         }
    1025              :     }
    1026            0 :     return fb;
    1027              : }
    1028              : 
    1029              : //---------------------------------------------------------
    1030              : //   STATIC FUNCTIONS FOR FONT CONFIGURATION MANAGEMENT
    1031              : //---------------------------------------------------------
    1032              : 
    1033           16 : bool FiguredBassFont::read(XmlReader& e)
    1034              : {
    1035          432 :     while (e.readNextStartElement()) {
    1036          416 :         const AsciiStringView tag(e.name());
    1037              : 
    1038          416 :         if (tag == "family") {
    1039           16 :             family = e.readText();
    1040          400 :         } else if (tag == "displayName") {
    1041           16 :             displayName = e.readText();
    1042          384 :         } else if (tag == "defaultPitch") {
    1043           16 :             defPitch = e.readDouble();
    1044          368 :         } else if (tag == "defaultLineHeight") {
    1045           16 :             defLineHeight = e.readDouble();
    1046          352 :         } else if (tag == "parenthesisRoundOpen") {
    1047           16 :             displayParenthesis[1] = e.readText().at(0);
    1048          336 :         } else if (tag == "parenthesisRoundClosed") {
    1049           16 :             displayParenthesis[2] = e.readText().at(0);
    1050          320 :         } else if (tag == "parenthesisSquareOpen") {
    1051           16 :             displayParenthesis[3] = e.readText().at(0);
    1052          304 :         } else if (tag == "parenthesisSquareClosed") {
    1053           16 :             displayParenthesis[4] = e.readText().at(0);
    1054          288 :         } else if (tag == "doubleflat") {
    1055           16 :             displayAccidental[int(FiguredBassItem::Modifier::DOUBLEFLAT)]= e.readText().at(0);
    1056          272 :         } else if (tag == "flat") {
    1057           16 :             displayAccidental[int(FiguredBassItem::Modifier::FLAT)]      = e.readText().at(0);
    1058          256 :         } else if (tag == "natural") {
    1059           16 :             displayAccidental[int(FiguredBassItem::Modifier::NATURAL)]   = e.readText().at(0);
    1060          240 :         } else if (tag == "sharp") {
    1061           16 :             displayAccidental[int(FiguredBassItem::Modifier::SHARP)]     = e.readText().at(0);
    1062          224 :         } else if (tag == "doublesharp") {
    1063           16 :             displayAccidental[int(FiguredBassItem::Modifier::DOUBLESHARP)]= e.readText().at(0);
    1064          208 :         } else if (tag == "cross") {
    1065           16 :             displayAccidental[int(FiguredBassItem::Modifier::CROSS)]     = e.readText().at(0);
    1066          192 :         } else if (tag == "backslash") {
    1067           16 :             displayAccidental[int(FiguredBassItem::Modifier::BACKSLASH)] = e.readText().at(0);
    1068          176 :         } else if (tag == "slash") {
    1069           16 :             displayAccidental[int(FiguredBassItem::Modifier::SLASH)]     = e.readText().at(0);
    1070          160 :         } else if (tag == "digit") {
    1071          160 :             int digit = e.intAttribute("value");
    1072          160 :             if (digit < 0 || digit > 9) {
    1073            0 :                 return false;
    1074              :             }
    1075         1440 :             while (e.readNextStartElement()) {
    1076         1280 :                 const AsciiStringView t(e.name());
    1077         1280 :                 if (t == "simple") {
    1078              :                     displayDigit[int(FiguredBassItem::Style::MODERN)]  [digit][int(FiguredBassItem::Combination::SIMPLE)]
    1079          160 :                         = e.readText().at(0);
    1080         1120 :                 } else if (t == "crossed") {
    1081              :                     displayDigit[int(FiguredBassItem::Style::MODERN)]  [digit][int(FiguredBassItem::Combination::CROSSED)]
    1082          160 :                         = e.readText().at(0);
    1083          960 :                 } else if (t == "backslashed") {
    1084              :                     displayDigit[int(FiguredBassItem::Style::MODERN)]  [digit][int(FiguredBassItem::Combination::BACKSLASHED)]
    1085          160 :                         = e.readText().at(0);
    1086          800 :                 } else if (t == "slashed") {
    1087              :                     displayDigit[int(FiguredBassItem::Style::MODERN)]  [digit][int(FiguredBassItem::Combination::SLASHED)]
    1088          160 :                         = e.readText().at(0);
    1089          640 :                 } else if (t == "simpleHistoric") {
    1090              :                     displayDigit[int(FiguredBassItem::Style::HISTORIC)][digit][int(FiguredBassItem::Combination::SIMPLE)]
    1091          160 :                         = e.readText().at(0);
    1092          480 :                 } else if (t == "crossedHistoric") {
    1093              :                     displayDigit[int(FiguredBassItem::Style::HISTORIC)][digit][int(FiguredBassItem::Combination::CROSSED)]
    1094          160 :                         = e.readText().at(0);
    1095          320 :                 } else if (t == "backslashedHistoric") {
    1096              :                     displayDigit[int(FiguredBassItem::Style::HISTORIC)][digit][int(FiguredBassItem::Combination::BACKSLASHED)]
    1097          160 :                         = e.readText().at(0);
    1098          160 :                 } else if (t == "slashedHistoric") {
    1099              :                     displayDigit[int(FiguredBassItem::Style::HISTORIC)][digit][int(FiguredBassItem::Combination::SLASHED)]
    1100          160 :                         = e.readText().at(0);
    1101              :                 } else {
    1102            0 :                     e.unknown();
    1103            0 :                     return false;
    1104              :                 }
    1105              :             }
    1106              :         } else {
    1107            0 :             e.unknown();
    1108            0 :             return false;
    1109              :         }
    1110              :     }
    1111           16 :     displayParenthesis[0] = displayAccidental[int(FiguredBassItem::Modifier::NONE)] = ' ';
    1112           16 :     return true;
    1113              : }
    1114              : 
    1115              : //---------------------------------------------------------
    1116              : //   Read Configuration File
    1117              : //
    1118              : //    reads a configuration and appends read data to g_FBFonts
    1119              : //    resets everything and reads the built-in config file if fileName is null or empty
    1120              : //---------------------------------------------------------
    1121              : 
    1122           16 : bool FiguredBass::readConfigFile(const String& fileName)
    1123              : {
    1124           16 :     String path;
    1125              : 
    1126           16 :     if (fileName.isEmpty()) {         // defaults to built-in xml
    1127           16 :         path = u":/fonts/fonts_figuredbass.xml";
    1128           16 :         g_FBFonts.clear();
    1129              :     } else {
    1130            0 :         path = fileName;
    1131              :     }
    1132              : 
    1133           16 :     File fi(path);
    1134           16 :     if (!fi.open(IODevice::ReadOnly)) {
    1135            0 :         LOGE() << "Cannot open figured bass description: " << fi.filePath();
    1136            0 :         return false;
    1137              :     }
    1138           16 :     XmlReader e(&fi);
    1139           16 :     while (e.readNextStartElement()) {
    1140           16 :         if (e.name() == "museScore") {
    1141              :             // String version = e.attribute(String("version"));
    1142              :             // StringList sl = version.split('.');
    1143              :             // int _mscVersion = sl[0].toInt() * 100 + sl[1].toInt();
    1144              : 
    1145           32 :             while (e.readNextStartElement()) {
    1146           16 :                 if (e.name() == "font") {
    1147           16 :                     FiguredBassFont f;
    1148           16 :                     if (f.read(e)) {
    1149           16 :                         g_FBFonts.push_back(f);
    1150              :                     } else {
    1151            0 :                         return false;
    1152              :                     }
    1153           16 :                 } else {
    1154            0 :                     e.unknown();
    1155              :                 }
    1156              :             }
    1157           16 :             return true;
    1158              :         }
    1159              :     }
    1160            0 :     return false;
    1161           16 : }
    1162              : 
    1163              : //---------------------------------------------------------
    1164              : //   Get Font Names
    1165              : //
    1166              : //    returns a list of display names for the fonts  configured to work with Figured Bass;
    1167              : //    the index of a name in the list can be used to retrieve the font data with fontData()
    1168              : //---------------------------------------------------------
    1169              : 
    1170            0 : std::vector<String> FiguredBass::fontNames()
    1171              : {
    1172            0 :     std::vector<String> names;
    1173            0 :     names.reserve(g_FBFonts.size());
    1174            0 :     for (const FiguredBassFont& f : g_FBFonts) {
    1175            0 :         names.push_back(f.displayName);
    1176              :     }
    1177            0 :     return names;
    1178            0 : }
    1179              : 
    1180              : //---------------------------------------------------------
    1181              : //   Get Font Data
    1182              : //
    1183              : //    retrieves data about a Figured Bass font.
    1184              : //    returns: true if idx is valid | false if it is not
    1185              : // any of the pointer parameter can be null, if that datum is not needed
    1186              : //---------------------------------------------------------
    1187              : 
    1188            0 : bool FiguredBass::fontData(int nIdx, String* pFamily, String* pDisplayName,
    1189              :                            double* pSize, double* pLineHeight)
    1190              : {
    1191            0 :     if (nIdx >= 0 && nIdx < static_cast<int>(g_FBFonts.size())) {
    1192            0 :         FiguredBassFont f = g_FBFonts.at(nIdx);
    1193            0 :         if (pFamily) {
    1194            0 :             *pFamily          = f.family;
    1195              :         }
    1196            0 :         if (pDisplayName) {
    1197            0 :             *pDisplayName     = f.displayName;
    1198              :         }
    1199            0 :         if (pSize) {
    1200            0 :             *pSize            = f.defPitch;
    1201              :         }
    1202            0 :         if (pLineHeight) {
    1203            0 :             *pLineHeight      = f.defLineHeight;
    1204              :         }
    1205            0 :         return true;
    1206            0 :     }
    1207            0 :     return false;
    1208              : }
    1209              : 
    1210              : //---------------------------------------------------------
    1211              : //   hasParentheses
    1212              : //
    1213              : //   return true if any FiguredBassItem starts with a parenthesis
    1214              : //---------------------------------------------------------
    1215              : 
    1216           50 : bool FiguredBass::parenthesesMode() const
    1217              : {
    1218          124 :     for (FiguredBassItem* item : m_items) {
    1219           75 :         if (item->startsWithParenthesis()) {
    1220            1 :             return true;
    1221              :         }
    1222              :     }
    1223           49 :     return false;
    1224              : }
    1225              : 
    1226              : //---------------------------------------------------------
    1227              : //
    1228              : // METHODS BELONGING TO OTHER CLASSES
    1229              : //
    1230              : //    Work In Progress: kept here until the FiguredBass framework is reasonably set up;
    1231              : //    To be finally moved to their respective class implementation files.
    1232              : //
    1233              : //---------------------------------------------------------
    1234              : 
    1235              : //---------------------------------------------------------
    1236              : //   Score::addFiguredBass
    1237              : //    called from Keyboard Accelerator & menus
    1238              : //---------------------------------------------------------
    1239              : 
    1240            0 : FiguredBass* Score::addFiguredBass()
    1241              : {
    1242            0 :     EngravingItem* el = selection().element();
    1243            0 :     if (!el || (!(el->isNote()) && !(el->isRest()) && !(el->isFiguredBass()))) {
    1244            0 :         MScore::setError(MsError::NO_NOTE_FIGUREDBASS_SELECTED);
    1245            0 :         return 0;
    1246              :     }
    1247              : 
    1248              :     FiguredBass* fb;
    1249            0 :     bool bNew = true;
    1250            0 :     if (el->isNote()) {
    1251            0 :         ChordRest* cr = toNote(el)->chord();
    1252            0 :         fb = FiguredBass::addFiguredBassToSegment(cr->segment(), cr->staffIdx() * VOICES, Fraction(0, 1), &bNew);
    1253            0 :     } else if (el->isRest()) {
    1254            0 :         ChordRest* cr = toRest(el);
    1255            0 :         fb = FiguredBass::addFiguredBassToSegment(cr->segment(), cr->staffIdx() * VOICES, Fraction(0, 1), &bNew);
    1256            0 :     } else if (el->isFiguredBass()) {
    1257            0 :         fb = toFiguredBass(el);
    1258            0 :         bNew = false;
    1259              :     } else {
    1260            0 :         return 0;
    1261              :     }
    1262              : 
    1263            0 :     if (fb == 0) {
    1264            0 :         return 0;
    1265              :     }
    1266              : 
    1267            0 :     if (bNew) {
    1268            0 :         undoAddElement(fb);
    1269              :     }
    1270            0 :     select(fb, SelectType::SINGLE, 0);
    1271            0 :     return fb;
    1272              : }
    1273              : }
        

Generated by: LCOV version 2.0-1