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 : }
|