1 //=============================================================================
2 // MuseScore
3 // Music Composition & Notation
4 //
5 // Copyright (C) 2008-2011 Werner Schweer
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License version 2
9 // as published by the Free Software Foundation and appearing in
10 // the file LICENCE.GPL
11 //=============================================================================
12
13 #include "harmony.h"
14
15 #include "chordlist.h"
16 #include "fret.h"
17 #include "measure.h"
18 #include "mscore.h"
19 #include "part.h"
20 #include "pitchspelling.h"
21 #include "repeatlist.h"
22 #include "score.h"
23 #include "segment.h"
24 #include "staff.h"
25 #include "sym.h"
26 #include "system.h"
27 #include "utils.h"
28 #include "xml.h"
29
30 namespace Ms {
31
32 //---------------------------------------------------------
33 // harmonyName
34 //---------------------------------------------------------
35
harmonyName() const36 QString Harmony::harmonyName() const
37 {
38 // Hack:
39 const_cast<Harmony*>(this)->determineRootBaseSpelling();
40
41 HChord hc = descr() ? descr()->chord : HChord();
42 QString s, r, e, b;
43
44 if (_leftParen)
45 s = "(";
46
47 if (_rootTpc != Tpc::TPC_INVALID)
48 r = tpc2name(_rootTpc, _rootSpelling, _rootCase);
49 else if (_harmonyType != HarmonyType::STANDARD)
50 r = _function;
51
52 if (_textName != "") {
53 e = _textName;
54 if (_harmonyType != HarmonyType::ROMAN)
55 e.remove('=');
56 }
57 else if (!_degreeList.empty()) {
58 hc.add(_degreeList);
59 // try to find the chord in chordList
60 const ChordDescription* newExtension = 0;
61 const ChordList* cl = score()->style().chordList();
62 for (const ChordDescription& cd : *cl) {
63 if (cd.chord == hc && !cd.names.empty()) {
64 newExtension = &cd;
65 break;
66 }
67 }
68 // now determine the chord name
69 if (newExtension)
70 e = newExtension->names.front();
71 else {
72 // not in table, fallback to using HChord.name()
73 r = hc.name(_rootTpc);
74 e = "";
75 }
76 }
77
78 if (_baseTpc != Tpc::TPC_INVALID)
79 b = "/" + tpc2name(_baseTpc, _baseSpelling, _baseCase);
80
81 s += r + e + b;
82
83 if (_rightParen)
84 s += ")";
85
86 return s;
87 }
88
89 //---------------------------------------------------------
90 // rootName
91 //---------------------------------------------------------
92
rootName()93 QString Harmony::rootName()
94 {
95 determineRootBaseSpelling();
96 return tpc2name(_rootTpc, _rootSpelling, _rootCase);
97 }
98
99 //---------------------------------------------------------
100 // baseName
101 //---------------------------------------------------------
102
baseName()103 QString Harmony::baseName()
104 {
105 determineRootBaseSpelling();
106 if (_baseTpc == Tpc::TPC_INVALID)
107 return rootName();
108 return tpc2name(_baseTpc, _baseSpelling, _baseCase);
109 }
110
111
isRealizable() const112 bool Harmony::isRealizable() const
113 {
114 return (_rootTpc != Tpc::TPC_INVALID)
115 || (_harmonyType == HarmonyType::NASHVILLE); // unable to fully check at for nashville at the moment
116 }
117
118 //---------------------------------------------------------
119 // resolveDegreeList
120 // try to detect chord number and to eliminate degree
121 // list
122 //---------------------------------------------------------
123
resolveDegreeList()124 void Harmony::resolveDegreeList()
125 {
126 if (_degreeList.empty())
127 return;
128
129 HChord hc = descr() ? descr()->chord : HChord();
130
131 hc.add(_degreeList);
132
133 // qDebug("resolveDegreeList: <%s> <%s-%s>: ", _descr->name, _descr->xmlKind, _descr->xmlDegrees);
134 // hc.print();
135 // _descr->chord.print();
136
137 // try to find the chord in chordList
138 const ChordList* cl = score()->style().chordList();
139 for (const ChordDescription& cd : *cl) {
140 if ((cd.chord == hc) && !cd.names.empty()) {
141 qDebug("ResolveDegreeList: found in table as %s", qPrintable(cd.names.front()));
142 _id = cd.id;
143 _degreeList.clear();
144 return;
145 }
146 }
147 qDebug("ResolveDegreeList: not found in table");
148 }
149
150 //---------------------------------------------------------
151 // chordSymbolStyle
152 //---------------------------------------------------------
153
154 const ElementStyle chordSymbolStyle {
155 { Sid::harmonyPlacement, Pid::PLACEMENT },
156 { Sid::minHarmonyDistance, Pid::MIN_DISTANCE },
157 { Sid::harmonyPlay, Pid::PLAY },
158 { Sid::harmonyVoiceLiteral, Pid::HARMONY_VOICE_LITERAL },
159 { Sid::harmonyVoicing, Pid::HARMONY_VOICING },
160 { Sid::harmonyDuration, Pid::HARMONY_DURATION }
161 };
162
163 //---------------------------------------------------------
164 // Harmony
165 //---------------------------------------------------------
166
Harmony(Score * s)167 Harmony::Harmony(Score* s)
168 : TextBase(s, Tid::HARMONY_A, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
169 {
170 _rootTpc = Tpc::TPC_INVALID;
171 _baseTpc = Tpc::TPC_INVALID;
172 _rootSpelling = NoteSpellingType::STANDARD;
173 _baseSpelling = NoteSpellingType::STANDARD;
174 _rootCase = NoteCaseType::CAPITAL;
175 _baseCase = NoteCaseType::CAPITAL;
176 _rootRenderCase = NoteCaseType::CAPITAL;
177 _baseRenderCase = NoteCaseType::CAPITAL;
178 _id = -1;
179 _parsedForm = 0;
180 _harmonyType = HarmonyType::STANDARD;
181 _leftParen = false;
182 _rightParen = false;
183 _realizedHarmony = RealizedHarmony(this);
184 initElementStyle(&chordSymbolStyle);
185 }
186
Harmony(const Harmony & h)187 Harmony::Harmony(const Harmony& h)
188 : TextBase(h)
189 {
190 _rootTpc = h._rootTpc;
191 _baseTpc = h._baseTpc;
192 _rootSpelling = h._rootSpelling;
193 _baseSpelling = h._baseSpelling;
194 _rootCase = h._rootCase;
195 _baseCase = h._baseCase;
196 _rootRenderCase = h._rootRenderCase;
197 _baseRenderCase = h._baseRenderCase;
198 _id = h._id;
199 _leftParen = h._leftParen;
200 _rightParen = h._rightParen;
201 _degreeList = h._degreeList;
202 _parsedForm = h._parsedForm ? new ParsedChord(*h._parsedForm) : 0;
203 _harmonyType = h._harmonyType;
204 _textName = h._textName;
205 _userName = h._userName;
206 _function = h._function;
207 _play = h._play;
208 _realizedHarmony = h._realizedHarmony;
209 _realizedHarmony.setHarmony(this);
210 for (const TextSegment* s : h.textList) {
211 TextSegment* ns = new TextSegment();
212 ns->set(s->text, s->font, s->x, s->y, s->offset);
213 textList.append(ns);
214 }
215 }
216
217 //---------------------------------------------------------
218 // ~Harmony
219 //---------------------------------------------------------
220
~Harmony()221 Harmony::~Harmony()
222 {
223 for (const TextSegment* ts : qAsConst(textList))
224 delete ts;
225 if (_parsedForm)
226 delete _parsedForm;
227 }
228
229 //---------------------------------------------------------
230 // write
231 //---------------------------------------------------------
232
write(XmlWriter & xml) const233 void Harmony::write(XmlWriter& xml) const
234 {
235 if (!xml.canWrite(this))
236 return;
237 xml.stag(this);
238 writeProperty(xml, Pid::HARMONY_TYPE);
239 if (_leftParen)
240 xml.tagE("leftParen");
241 if (_rootTpc != Tpc::TPC_INVALID || _baseTpc != Tpc::TPC_INVALID) {
242 int rRootTpc = _rootTpc;
243 int rBaseTpc = _baseTpc;
244 if (staff()) {
245 // parent can be a fret diagram
246 Segment* segment = getParentSeg();
247 Fraction tick = segment ? segment->tick() : Fraction(-1,1);
248 const Interval& interval = part()->instrument(tick)->transpose();
249 if (xml.clipboardmode() && !score()->styleB(Sid::concertPitch) && interval.chromatic) {
250 rRootTpc = transposeTpc(_rootTpc, interval, true);
251 rBaseTpc = transposeTpc(_baseTpc, interval, true);
252 }
253 }
254 if (rRootTpc != Tpc::TPC_INVALID) {
255 xml.tag("root", rRootTpc);
256 if (_rootCase != NoteCaseType::CAPITAL)
257 xml.tag("rootCase", static_cast<int>(_rootCase));
258 }
259 if (_id > 0)
260 xml.tag("extension", _id);
261 // parser uses leading "=" as a hidden specifier for minor
262 // this may or may not currently be incorporated into _textName
263 QString writeName = _textName;
264 if (_parsedForm && _parsedForm->name().startsWith("=") && !writeName.startsWith("="))
265 writeName = "=" + writeName;
266 if (!writeName.isEmpty())
267 xml.tag("name", writeName);
268
269 if (rBaseTpc != Tpc::TPC_INVALID) {
270 xml.tag("base", rBaseTpc);
271 if (_baseCase != NoteCaseType::CAPITAL)
272 xml.tag("baseCase", static_cast<int>(_baseCase));
273 }
274 for (const HDegree& hd : _degreeList) {
275 HDegreeType tp = hd.type();
276 if (tp == HDegreeType::ADD || tp == HDegreeType::ALTER || tp == HDegreeType::SUBTRACT) {
277 xml.stag("degree");
278 xml.tag("degree-value", hd.value());
279 xml.tag("degree-alter", hd.alter());
280 switch (tp) {
281 case HDegreeType::ADD:
282 xml.tag("degree-type", "add");
283 break;
284 case HDegreeType::ALTER:
285 xml.tag("degree-type", "alter");
286 break;
287 case HDegreeType::SUBTRACT:
288 xml.tag("degree-type", "subtract");
289 break;
290 default:
291 break;
292 }
293 xml.etag();
294 }
295 }
296 }
297 else
298 xml.tag("name", _textName);
299 if (!_function.isEmpty())
300 xml.tag("function", _function);
301 TextBase::writeProperties(xml, false, true);
302 //Pid::PLAY, Pid::HARMONY_VOICE_LITERAL, Pid::HARMONY_VOICING, Pid::HARMONY_DURATION
303 //written by the above function call because they are part of element style
304 if (_rightParen)
305 xml.tagE("rightParen");
306 xml.etag();
307 }
308
309 //---------------------------------------------------------
310 // read
311 //---------------------------------------------------------
312
read(XmlReader & e)313 void Harmony::read(XmlReader& e)
314 {
315 while (e.readNextStartElement()) {
316 const QStringRef& tag(e.name());
317 if (tag == "base")
318 setBaseTpc(e.readInt());
319 else if (tag == "baseCase")
320 _baseCase = static_cast<NoteCaseType>(e.readInt());
321 else if (tag == "extension")
322 setId(e.readInt());
323 else if (tag == "name")
324 _textName = e.readElementText();
325 else if (tag == "root")
326 setRootTpc(e.readInt());
327 else if (tag == "rootCase")
328 _rootCase = static_cast<NoteCaseType>(e.readInt());
329 else if (tag == "function")
330 _function = e.readElementText();
331 else if (tag == "degree") {
332 int degreeValue = 0;
333 int degreeAlter = 0;
334 QString degreeType = "";
335 while (e.readNextStartElement()) {
336 const QStringRef& t(e.name());
337 if (t == "degree-value")
338 degreeValue = e.readInt();
339 else if (t == "degree-alter")
340 degreeAlter = e.readInt();
341 else if (t == "degree-type")
342 degreeType = e.readElementText();
343 else
344 e.unknown();
345 }
346 if (degreeValue <= 0 || degreeValue > 13
347 || degreeAlter < -2 || degreeAlter > 2
348 || (degreeType != "add" && degreeType != "alter" && degreeType != "subtract")) {
349 qDebug("incorrect degree: degreeValue=%d degreeAlter=%d degreeType=%s",
350 degreeValue, degreeAlter, qPrintable(degreeType));
351 }
352 else {
353 if (degreeType == "add")
354 addDegree(HDegree(degreeValue, degreeAlter, HDegreeType::ADD));
355 else if (degreeType == "alter")
356 addDegree(HDegree(degreeValue, degreeAlter, HDegreeType::ALTER));
357 else if (degreeType == "subtract")
358 addDegree(HDegree(degreeValue, degreeAlter, HDegreeType::SUBTRACT));
359 }
360 }
361 else if (tag == "leftParen") {
362 _leftParen = true;
363 e.readNext();
364 }
365 else if (tag == "rightParen") {
366 _rightParen = true;
367 e.readNext();
368 }
369 else if (readProperty(tag, e, Pid::POS_ABOVE))
370 ;
371 else if (readProperty(tag, e, Pid::HARMONY_TYPE))
372 ;
373 else if (readProperty(tag, e, Pid::PLAY))
374 ;
375 else if (readProperty(tag, e, Pid::HARMONY_VOICE_LITERAL))
376 ;
377 else if (readProperty(tag, e, Pid::HARMONY_VOICING))
378 ;
379 else if (readProperty(tag, e, Pid::HARMONY_DURATION))
380 ;
381 else if (!TextBase::readProperties(e))
382 e.unknown();
383 }
384
385 // TODO: now that we can render arbitrary chords,
386 // we could try to construct a full representation from a degree list.
387 // These will typically only exist for chords imported from MusicXML prior to MuseScore 2.0
388 // or constructed in the Chord Symbol Properties dialog.
389
390 if (_rootTpc != Tpc::TPC_INVALID) {
391 if (_id > 0) {
392 // positive id will happen only for scores that were created with explicit chord lists
393 // lookup id in chord list and generate new description if necessary
394 getDescription();
395 }
396 else
397 {
398 // default case: look up by name
399 // description will be found for any chord already read in this score
400 // and we will generate a new one if necessary
401 getDescription(_textName);
402 }
403 }
404 else if (_textName == "") {
405 // unrecognized chords prior to 2.0 were stored as text with markup
406 // we need to strip away the markup
407 // this removes any user-applied formatting,
408 // but we no longer support user-applied formatting for chord symbols anyhow
409 // with any luck, the resulting text will be parseable now, so give it a shot
410 createLayout();
411 QString s = plainText();
412 if (!s.isEmpty()) {
413 setHarmony(s);
414 return;
415 }
416 // empty text could also indicate a root-less slash chord ("/E")
417 // we'll fall through and render it normally
418 }
419
420 // render chord from description (or _textName)
421 render();
422 setPlainText(harmonyName());
423 }
424
425 //---------------------------------------------------------
426 // determineRootBaseSpelling
427 //---------------------------------------------------------
428
determineRootBaseSpelling(NoteSpellingType & rootSpelling,NoteCaseType & rootCase,NoteSpellingType & baseSpelling,NoteCaseType & baseCase)429 void Harmony::determineRootBaseSpelling(NoteSpellingType& rootSpelling, NoteCaseType& rootCase,
430 NoteSpellingType& baseSpelling, NoteCaseType& baseCase)
431 {
432 // spelling
433 if (score()->styleB(Sid::useStandardNoteNames))
434 rootSpelling = NoteSpellingType::STANDARD;
435 else if (score()->styleB(Sid::useGermanNoteNames))
436 rootSpelling = NoteSpellingType::GERMAN;
437 else if (score()->styleB(Sid::useFullGermanNoteNames))
438 rootSpelling = NoteSpellingType::GERMAN_PURE;
439 else if (score()->styleB(Sid::useSolfeggioNoteNames))
440 rootSpelling = NoteSpellingType::SOLFEGGIO;
441 else if (score()->styleB(Sid::useFrenchNoteNames))
442 rootSpelling = NoteSpellingType::FRENCH;
443 baseSpelling = rootSpelling;
444
445 // case
446
447 // always use case as typed if automatic capitalization is off
448 if (!score()->styleB(Sid::automaticCapitalization)) {
449 rootCase = _rootCase;
450 baseCase = _baseCase;
451 return;
452 }
453
454 // set default
455 if (score()->styleB(Sid::allCapsNoteNames)) {
456 rootCase = NoteCaseType::UPPER;
457 baseCase = NoteCaseType::UPPER;
458 }
459 else {
460 rootCase = NoteCaseType::CAPITAL;
461 baseCase = NoteCaseType::CAPITAL;
462 }
463
464 // override for bass note
465 if (score()->styleB(Sid::lowerCaseBassNotes))
466 baseCase = NoteCaseType::LOWER;
467
468 // override for minor chords
469 if (score()->styleB(Sid::lowerCaseMinorChords)) {
470 const ChordDescription* cd = descr();
471 QString quality;
472 if (cd) {
473 // use chord description if possible
474 // this is the usual case
475 quality = cd->quality();
476 }
477 else if (_parsedForm) {
478 // this happens on load of new chord list
479 // for chord symbols that were added/edited since the score was loaded
480 // or read aloud with screenreader
481 // parsed form is usable even if out of date with respect to chord list
482 quality = _parsedForm->quality();
483 }
484 else {
485 // this happens on load of new chord list
486 // for chord symbols that have not been edited since the score was loaded
487 // we need to parse this chord for now to determine quality
488 // but don't keep the parsed form around as we're not ready for it yet
489 quality = parsedForm()->quality();
490 delete _parsedForm;
491 _parsedForm = 0;
492 }
493 if (quality == "minor" || quality == "diminished" || quality == "half-diminished")
494 rootCase = NoteCaseType::LOWER;
495 }
496 }
497
498 //---------------------------------------------------------
499 // determineRootBaseSpelling
500 //---------------------------------------------------------
501
determineRootBaseSpelling()502 void Harmony::determineRootBaseSpelling()
503 {
504 determineRootBaseSpelling(_rootSpelling, _rootRenderCase,
505 _baseSpelling, _baseRenderCase);
506 }
507
508 //---------------------------------------------------------
509 // convertNote
510 // convert something like "C#" into tpc 21
511 //---------------------------------------------------------
512
convertNote(const QString & s,NoteSpellingType noteSpelling,NoteCaseType & noteCase,int & idx)513 static int convertNote(const QString& s, NoteSpellingType noteSpelling, NoteCaseType& noteCase, int& idx)
514 {
515 bool useGerman = false;
516 bool useSolfeggio = false;
517 static const int spellings[] = {
518 // bb b - # ##
519 0, 7, 14, 21, 28, // C
520 2, 9, 16, 23, 30, // D
521 4, 11, 18, 25, 32, // E
522 -1, 6, 13, 20, 27, // F
523 1, 8, 15, 22, 29, // G
524 3, 10, 17, 24, 31, // A
525 5, 12, 19, 26, 33, // B
526 };
527 if (s == "")
528 return Tpc::TPC_INVALID;
529 noteCase = s[0].isLower() ? NoteCaseType::LOWER : NoteCaseType::CAPITAL;
530 int acci;
531 switch (noteSpelling) {
532 case NoteSpellingType::SOLFEGGIO:
533 case NoteSpellingType::FRENCH:
534 useSolfeggio = true;
535 if (s.startsWith("sol", Qt::CaseInsensitive))
536 acci = 3;
537 else
538 acci = 2;
539 break;
540 case NoteSpellingType::GERMAN:
541 case NoteSpellingType::GERMAN_PURE:
542 useGerman = true;
543 // fall through
544 default:
545 acci = 1;
546 }
547 idx = acci;
548 int alter = 0;
549 int n = s.size();
550 QString acc = s.right(n-acci);
551 if (acc != "") {
552 if (acc.startsWith("bb")) {
553 alter = -2;
554 idx += 2;
555 }
556 else if (acc.startsWith("b")) {
557 alter = -1;
558 idx += 1;
559 }
560 else if (useGerman && acc.startsWith("eses")) {
561 alter = -2;
562 idx += 4;
563 }
564 else if (useGerman && (acc.startsWith("ses") || acc.startsWith("sas"))) {
565 alter = -2;
566 idx += 3;
567 }
568 else if (useGerman && acc.startsWith("es")) {
569 alter = -1;
570 idx += 2;
571 }
572 else if (useGerman && acc.startsWith("s") && !acc.startsWith("su")) {
573 alter = -1;
574 idx += 1;
575 }
576 else if (acc.startsWith("##")) {
577 alter = 2;
578 idx += 2;
579 }
580 else if (acc.startsWith("x")) {
581 alter = 2;
582 idx += 1;
583 }
584 else if (acc.startsWith("#")) {
585 alter = 1;
586 idx += 1;
587 }
588 else if (useGerman && acc.startsWith("isis")) {
589 alter = 2;
590 idx += 4;
591 }
592 else if (useGerman && acc.startsWith("is")) {
593 alter = 1;
594 idx += 2;
595 }
596 }
597 int r;
598 if (useGerman) {
599 switch(s[0].toLower().toLatin1()) {
600 case 'c': r = 0; break;
601 case 'd': r = 1; break;
602 case 'e': r = 2; break;
603 case 'f': r = 3; break;
604 case 'g': r = 4; break;
605 case 'a': r = 5; break;
606 case 'h': r = 6; break;
607 case 'b':
608 if (alter && alter != -1)
609 return Tpc::TPC_INVALID;
610 r = 6;
611 alter = -1;
612 break;
613 default:
614 return Tpc::TPC_INVALID;
615 }
616 }
617 else if (useSolfeggio) {
618 if (s.length() < 2)
619 return Tpc::TPC_INVALID;
620 if (s[1].isUpper())
621 noteCase = NoteCaseType::UPPER;
622 QString ss = s.toLower().left(2);
623 if (ss == "do")
624 r = 0;
625 else if (ss == "re" || ss == "ré")
626 r = 1;
627 else if (ss == "mi")
628 r = 2;
629 else if (ss == "fa")
630 r = 3;
631 else if (ss == "so") // sol, but only check first 2 characters
632 r = 4;
633 else if (ss == "la")
634 r = 5;
635 else if (ss == "si")
636 r = 6;
637 else
638 return Tpc::TPC_INVALID;
639 }
640 else {
641 switch(s[0].toLower().toLatin1()) {
642 case 'c': r = 0; break;
643 case 'd': r = 1; break;
644 case 'e': r = 2; break;
645 case 'f': r = 3; break;
646 case 'g': r = 4; break;
647 case 'a': r = 5; break;
648 case 'b': r = 6; break;
649 default: return Tpc::TPC_INVALID;
650 }
651 }
652 r = spellings[r * 5 + alter + 2];
653 return r;
654 }
655
656 //---------------------------------------------------------
657 // parseHarmony
658 // determine root and bass tpc & case
659 // compare body of chordname against chord list
660 // return true if chord is recognized
661 //---------------------------------------------------------
662
parseHarmony(const QString & ss,int * root,int * base,bool syntaxOnly)663 const ChordDescription* Harmony::parseHarmony(const QString& ss, int* root, int* base, bool syntaxOnly)
664 {
665 _id = -1;
666 if (_parsedForm) {
667 delete _parsedForm;
668 _parsedForm = 0;
669 }
670 _textName.clear();
671 bool useLiteral = false;
672 if (ss.endsWith(' '))
673 useLiteral = true;
674
675 if (_harmonyType == HarmonyType::ROMAN) {
676 _userName = ss;
677 _textName = ss;
678 *root = Tpc::TPC_INVALID;
679 *base = Tpc::TPC_INVALID;
680 return 0;
681 }
682
683 // pre-process for parentheses
684 QString s = ss.simplified();
685 if ((_leftParen = s.startsWith('(')))
686 s.remove(0,1);
687 if ((_rightParen = (s.endsWith(')') && s.count('(') < s.count(')'))))
688 s.remove(s.size()-1,1);
689 if (_leftParen || _rightParen)
690 s = s.simplified(); // in case of spaces inside parentheses
691 if (s.isEmpty())
692 return 0;
693
694 // pre-process for lower case minor chords
695 bool preferMinor;
696 if (score()->styleB(Sid::lowerCaseMinorChords) && s[0].isLower())
697 preferMinor = true;
698 else
699 preferMinor = false;
700
701 if (_harmonyType == HarmonyType::NASHVILLE) {
702 int n = 0;
703 if (s[0].isDigit())
704 n = 1;
705 else if (s[1].isDigit())
706 n = 2;
707 _function = s.mid(0, n);
708 s = s.mid(n);
709 *root = Tpc::TPC_INVALID;
710 *base = Tpc::TPC_INVALID;
711 }
712 else {
713 determineRootBaseSpelling();
714 int idx;
715 int r = convertNote(s, _rootSpelling, _rootCase, idx);
716 if (r == Tpc::TPC_INVALID) {
717 if (s[0] == '/')
718 idx = 0;
719 else {
720 qDebug("failed <%s>", qPrintable(ss));
721 _userName = s;
722 _textName = s;
723 return 0;
724 }
725 }
726 *root = r;
727 *base = Tpc::TPC_INVALID;
728 int slash = s.lastIndexOf('/');
729 if (slash != -1) {
730 QString bs = s.mid(slash + 1).simplified();
731 s = s.mid(idx, slash - idx).simplified();
732 int idx2;
733 *base = convertNote(bs, _baseSpelling, _baseCase, idx2);
734 if (idx2 != bs.size())
735 *base = Tpc::TPC_INVALID;
736 if (*base == Tpc::TPC_INVALID) {
737 // if what follows after slash is not (just) a TPC
738 // then reassemble chord and try to parse with the slash
739 s = s + "/" + bs;
740 }
741 }
742 else
743 s = s.mid(idx); // don't simplify; keep leading space before extension if present
744 }
745
746 _userName = s;
747 const ChordList* cl = score()->style().chordList();
748 const ChordDescription* cd = 0;
749 if (useLiteral)
750 cd = descr(s);
751 else {
752 _parsedForm = new ParsedChord();
753 _parsedForm->parse(s, cl, syntaxOnly, preferMinor);
754 // parser prepends "=" to name of implied minor chords
755 // use this here as well
756 if (preferMinor)
757 s = _parsedForm->name();
758 // look up to see if we already have a descriptor (chord has been used before)
759 cd = descr(s, _parsedForm);
760 }
761 if (cd) {
762 // descriptor found; use its information
763 _id = cd->id;
764 if (!cd->names.empty())
765 _textName = cd->names.front();
766 }
767 else {
768 // no descriptor yet; just set textname
769 // we will generate descriptor later if necessary (when we are done editing this chord)
770 _textName = s;
771 }
772 return cd;
773 }
774
775 //---------------------------------------------------------
776 // startEdit
777 //---------------------------------------------------------
778
startEdit(EditData & ed)779 void Harmony::startEdit(EditData& ed)
780 {
781 if (!textList.empty()) {
782 // convert chord symbol to plain text
783 setPlainText(harmonyName());
784 // clear rendering
785 for (const TextSegment* t : qAsConst(textList))
786 delete t;
787 textList.clear();
788 }
789
790 // layout as text, without position reset
791 TextBase::layout1();
792 triggerLayout();
793
794 TextBase::startEdit(ed);
795 }
796
797 //---------------------------------------------------------
798 // edit
799 //---------------------------------------------------------
800
edit(EditData & ed)801 bool Harmony::edit(EditData& ed)
802 {
803 if (ed.key == Qt::Key_Return)
804 return true; // Harmony only single line
805
806 bool rv = TextBase::edit(ed);
807
808 // layout as text, without position reset
809 TextBase::layout1();
810 triggerLayout();
811
812 // check spelling
813 int root = TPC_INVALID;
814 int base = TPC_INVALID;
815 QString str = xmlText();
816 showSpell = !str.isEmpty() && !parseHarmony(str, &root, &base, true) && root == TPC_INVALID && _harmonyType == HarmonyType::STANDARD;
817 if (showSpell)
818 qDebug("bad spell");
819
820 return rv;
821 }
822
823 //---------------------------------------------------------
824 // endEdit
825 //---------------------------------------------------------
826
endEdit(EditData & ed)827 void Harmony::endEdit(EditData& ed)
828 {
829 // complete editing: generate xml text, set Pid::TEXT, perform initial layout
830 // if text has changed, this also triggers setHarmony() which renders chord symbol
831 // but any rendering or layout performed here is tentative,
832 // we may still need to substitute special characters,
833 // and that cannot be until after editing is completed
834 TextBase::endEdit(ed);
835
836 // get plain text
837 QString s = plainText();
838
839 // if user explicitly added symbols to the text,
840 // convert them back to their respective replacement texts
841 if (harmonyType() != HarmonyType::ROMAN) {
842 s.replace("\u1d12b", "bb"); // double-flat
843 s.replace("\u266d", "b"); // flat
844 s.replace("\ue260", "b"); // flat
845 // do not replace natural sign
846 // (right now adding the symbol explicitly is the only way to force a natural sign to appear at all)
847 //s.replace("\u266e", "n"); // natural, if one day we support that too
848 //s.replace("\ue261", "n"); // natural, if one day we support that too
849 s.replace("\u266f", "#"); // sharp
850 s.replace("\ue262", "#"); // sharp
851 s.replace("\u1d12a", "x"); // double-sharp
852 s.replace("\u0394", "^"); // Δ
853 s.replace("\u00d0", "o"); // °
854 s.replace("\u00f8", "0"); // ø
855 s.replace("\u00d8", "0"); // Ø
856 }
857 else {
858 s.replace("\ue260", "\u266d"); // flat
859 s.replace("\ue261", "\u266e"); // natural
860 s.replace("\ue262", "\u266f"); // sharp
861 }
862
863 //play chord on edit and set dirty
864 score()->setPlayChord(true);
865 _realizedHarmony.setDirty(true);
866
867 // render and layout chord symbol
868 // (needs to be done here if text hasn't changed, or redone if replacemens were performed above)
869 score()->startCmd();
870 setHarmony(s);
871 layout1();
872 triggerLayout();
873 score()->endCmd();
874
875 // disable spell check
876 showSpell = false;
877
878 if (links()) {
879 for (ScoreElement* e : *links()) {
880 if (e == this)
881 continue;
882 Harmony* h = toHarmony(e);
883 // transpose if necessary
884 // at this point chord will already have been rendered in same key as original
885 // (as a result of TextBase::endEdit() calling setText() for linked elements)
886 // we may now need to change the TPC's and the text, and re-render
887 if (score()->styleB(Sid::concertPitch) != h->score()->styleB(Sid::concertPitch)) {
888 Part* partDest = h->part();
889 Segment* segment = getParentSeg();
890 Fraction tick = segment ? segment->tick() : Fraction(-1,1);
891 Interval interval = partDest->instrument(tick)->transpose();
892 if (!interval.isZero()) {
893 if (!h->score()->styleB(Sid::concertPitch))
894 interval.flip();
895 int rootTpc = transposeTpc(h->rootTpc(), interval, true);
896 int baseTpc = transposeTpc(h->baseTpc(), interval, true);
897 //score()->undoTransposeHarmony(h, rootTpc, baseTpc);
898 h->setRootTpc(rootTpc);
899 h->setBaseTpc(baseTpc);
900 h->setPlainText(h->harmonyName());
901 h->setHarmony(h->plainText());
902 h->triggerLayout();
903 }
904 }
905 }
906 }
907 }
908
909 //---------------------------------------------------------
910 // setHarmony
911 //---------------------------------------------------------
912
setHarmony(const QString & s)913 void Harmony::setHarmony(const QString& s)
914 {
915 int r, b;
916 const ChordDescription* cd = parseHarmony(s, &r, &b);
917 if (!cd && _parsedForm && _parsedForm->parseable()) {
918 // our first time encountering this chord
919 // generate a descriptor and use it
920 cd = generateDescription();
921 _id = cd->id;
922 }
923 if (cd) {
924 setRootTpc(r);
925 setBaseTpc(b);
926 render();
927 }
928 else {
929 // unparseable chord, render as plain text
930 for (const TextSegment* ts : qAsConst(textList))
931 delete ts;
932 textList.clear();
933 setRootTpc(Tpc::TPC_INVALID);
934 setBaseTpc(Tpc::TPC_INVALID);
935 _id = -1;
936 render();
937 }
938 }
939
940 //---------------------------------------------------------
941 // baseLine
942 //---------------------------------------------------------
943
baseLine() const944 qreal Harmony::baseLine() const
945 {
946 return (textList.empty()) ? TextBase::baseLine() : 0.0;
947 }
948
949 //---------------------------------------------------------
950 // text
951 //---------------------------------------------------------
952
text() const953 QString HDegree::text() const
954 {
955 if (_type == HDegreeType::UNDEF)
956 return QString();
957 const char* d = 0;
958 switch(_type) {
959 case HDegreeType::UNDEF: break;
960 case HDegreeType::ADD: d= "add"; break;
961 case HDegreeType::ALTER: d= "alt"; break;
962 case HDegreeType::SUBTRACT: d= "sub"; break;
963 }
964 QString degree(d);
965 switch(_alter) {
966 case -1: degree += "b"; break;
967 case 1: degree += "#"; break;
968 default: break;
969 }
970 QString s = QString("%1").arg(_value);
971 QString ss = degree + s;
972 return ss;
973 }
974
975 //---------------------------------------------------------
976 // findInSeg
977 /// find a Harmony in a given segment on the same track as this harmony.
978 ///
979 /// returns 0 if there is none
980 //---------------------------------------------------------
981
findInSeg(Segment * seg) const982 Harmony* Harmony::findInSeg(Segment* seg) const
983 {
984 // Find harmony as parent of fret diagram on same track
985 Element* fde = seg->findAnnotation(ElementType::FRET_DIAGRAM, track(), track());
986 if (fde) {
987 FretDiagram* fd = toFretDiagram(fde);
988 if (fd->harmony()) {
989 return toHarmony(fd->harmony());
990 }
991 }
992
993 // Find harmony on same track
994 Element* e = seg->findAnnotation(ElementType::HARMONY, track(), track());
995 if (e) {
996 return toHarmony(e);
997 }
998 return nullptr;
999 }
1000
1001 //---------------------------------------------------------
1002 // getParentSeg
1003 /// gets the parent segment of this harmony
1004 //---------------------------------------------------------
1005
getParentSeg() const1006 Segment* Harmony::getParentSeg() const
1007 {
1008 Segment* seg;
1009 if (parent()->isFretDiagram()) {
1010 // When this harmony is the child of a fret diagram, we need to go up twice
1011 // to get to the parent seg.
1012 seg = toSegment(parent()->parent());
1013 }
1014 else {
1015 seg = toSegment(parent());
1016 }
1017 return seg;
1018 }
1019
1020 //---------------------------------------------------------
1021 // findNext
1022 /// find the next Harmony in the score
1023 ///
1024 /// returns 0 if there is none
1025 //---------------------------------------------------------
1026
findNext() const1027 Harmony* Harmony::findNext() const
1028 {
1029 Segment* cur = getParentSeg()->next1();
1030 while (cur) {
1031 Harmony* h = findInSeg(cur);
1032 if (h) {
1033 return h;
1034 }
1035 cur = cur->next1();
1036 }
1037 return 0;
1038 }
1039
1040 //---------------------------------------------------------
1041 // findPrev
1042 /// find the previous Harmony in the score
1043 ///
1044 /// returns 0 if there is none
1045 //---------------------------------------------------------
1046
findPrev() const1047 Harmony* Harmony::findPrev() const
1048 {
1049 Segment* cur = getParentSeg()->prev1();
1050 while (cur) {
1051 Harmony* h = findInSeg(cur);
1052 if (h) {
1053 return h;
1054 }
1055 cur = cur->prev1();
1056 }
1057 return 0;
1058 }
1059
1060 //---------------------------------------------------------
1061 // ticksTillNext
1062 /// finds ticks until the next chord symbol or end of score
1063 ///
1064 /// utick is our current playback position to start from
1065 ///
1066 /// stopAtMeasureEnd being set to true will have the loop
1067 /// stop at measure end.
1068 //---------------------------------------------------------
ticksTillNext(int utick,bool stopAtMeasureEnd) const1069 Fraction Harmony::ticksTillNext(int utick, bool stopAtMeasureEnd) const
1070 {
1071 Segment* seg = getParentSeg();
1072 Fraction duration = seg->ticks();
1073 Segment* cur = seg->next();
1074 auto rsIt = score()->repeatList().findRepeatSegmentFromUTick(utick);
1075
1076 Measure const * currentMeasure = seg->measure();
1077 Measure const * endMeasure = (stopAtMeasureEnd)? currentMeasure : (*rsIt)->lastMeasure();
1078 Harmony const * nextHarmony = nullptr;
1079
1080 do {
1081 // Loop over segments of this measure
1082 while (cur) {
1083 //find harmony on same track
1084 Element* e = cur->findAnnotation(ElementType::HARMONY, track(), track());
1085 if (e) {
1086 nextHarmony = toHarmony(e);
1087 }
1088 else {// no harmony; look for fret diagram
1089 e = cur->findAnnotation(ElementType::FRET_DIAGRAM, track(), track());
1090 if (e) {
1091 nextHarmony = toFretDiagram(e)->harmony();
1092 }
1093 }
1094 if (nextHarmony != nullptr) {
1095 //we have found the next chord symbol
1096 break;
1097 }
1098 //keep adding the duration of the current segment
1099 //in case we are not able to find a next
1100 //chord symbol
1101 duration += cur->ticks();
1102 cur = cur->next();
1103 }
1104 // Move segment reference to next measure
1105 if (currentMeasure != endMeasure) {
1106 currentMeasure = currentMeasure->nextMeasure();
1107 cur = (currentMeasure)? currentMeasure->first() : nullptr;
1108 }
1109 else { // End of repeatSegment or search boundary reached
1110 if (stopAtMeasureEnd) {
1111 break;
1112 }
1113 else { // move to next RepeatSegment
1114 if (++rsIt != score()->repeatList().end()) {
1115 currentMeasure = (*rsIt)->firstMeasure();
1116 endMeasure = (*rsIt)->lastMeasure();
1117 cur = currentMeasure->first();
1118 }
1119 }
1120 }
1121 } while ((nextHarmony == nullptr) && (cur != nullptr));
1122
1123 return duration;
1124 }
1125
1126 //---------------------------------------------------------
1127 // fromXml
1128 // lookup harmony in harmony data base
1129 // using musicXml "kind" string and degree list
1130 //---------------------------------------------------------
1131
fromXml(const QString & kind,const QList<HDegree> & dl)1132 const ChordDescription* Harmony::fromXml(const QString& kind, const QList<HDegree>& dl)
1133 {
1134 QStringList degrees;
1135
1136 for (const HDegree& d : dl)
1137 degrees.append(d.text());
1138
1139 QString lowerCaseKind = kind.toLower();
1140 const ChordList* cl = score()->style().chordList();
1141 for (const ChordDescription& cd : *cl) {
1142 QString k = cd.xmlKind;
1143 QString lowerCaseK = k.toLower(); // required for xmlKind Tristan
1144 QStringList d = cd.xmlDegrees;
1145 if ((lowerCaseKind == lowerCaseK) && (d == degrees)) {
1146 // qDebug("harmony found in db: %s %s -> %d", qPrintable(kind), qPrintable(degrees), cd->id);
1147 return &cd;
1148 }
1149 }
1150 return 0;
1151 }
1152
1153 //---------------------------------------------------------
1154 // fromXml
1155 // lookup harmony in harmony data base
1156 // using musicXml "kind" string only
1157 //---------------------------------------------------------
1158
fromXml(const QString & kind)1159 const ChordDescription* Harmony::fromXml(const QString& kind)
1160 {
1161 QString lowerCaseKind = kind.toLower();
1162 const ChordList* cl = score()->style().chordList();
1163 for (const ChordDescription& cd : *cl) {
1164 if (lowerCaseKind == cd.xmlKind)
1165 return &cd;
1166 }
1167 return 0;
1168 }
1169
1170 //---------------------------------------------------------
1171 // fromXml
1172 // construct harmony directly from XML
1173 // build name first
1174 // then generate chord description from that
1175 //---------------------------------------------------------
1176
fromXml(const QString & kind,const QString & kindText,const QString & symbols,const QString & parens,const QList<HDegree> & dl)1177 const ChordDescription* Harmony::fromXml(const QString& kind, const QString& kindText, const QString& symbols, const QString& parens, const QList<HDegree>& dl)
1178 {
1179 ParsedChord* pc = new ParsedChord;
1180 _textName = pc->fromXml(kind, kindText, symbols, parens, dl, score()->style().chordList());
1181 _parsedForm = pc;
1182 const ChordDescription* cd = getDescription(_textName,pc);
1183 return cd;
1184 }
1185
1186 //---------------------------------------------------------
1187 // descr
1188 // look up id in chord list
1189 // return chord description if found, or null
1190 //---------------------------------------------------------
1191
descr() const1192 const ChordDescription* Harmony::descr() const
1193 {
1194 return score()->style().chordDescription(_id);
1195 }
1196
1197 //---------------------------------------------------------
1198 // descr
1199 // look up name in chord list
1200 // optionally look up by parsed chord as fallback
1201 // return chord description if found, or null
1202 //---------------------------------------------------------
1203
descr(const QString & name,const ParsedChord * pc) const1204 const ChordDescription* Harmony::descr(const QString& name, const ParsedChord* pc) const
1205 {
1206 const ChordList* cl = score()->style().chordList();
1207 const ChordDescription* match = 0;
1208 if (cl) {
1209 for (const ChordDescription& cd : *cl) {
1210 for (const QString& s : cd.names) {
1211 if (s == name)
1212 return &cd;
1213 else if (pc) {
1214 for (const ParsedChord& sParsed : cd.parsedChords) {
1215 if (sParsed == *pc)
1216 match = &cd;
1217 }
1218 }
1219 }
1220 }
1221 }
1222 // exact match failed, so fall back on parsed match if one was found
1223 return match;
1224 }
1225
1226 //---------------------------------------------------------
1227 // getDescription
1228 // look up id in chord list
1229 // return chord description if found
1230 // if not found, and chord is parseable,
1231 // generate a new chord description
1232 // and add to chord list
1233 //---------------------------------------------------------
1234
getDescription()1235 const ChordDescription* Harmony::getDescription()
1236 {
1237 const ChordDescription* cd = descr();
1238 if (cd && !cd->names.empty())
1239 _textName = cd->names.front();
1240 else if (_textName != "") {
1241 cd = generateDescription();
1242 _id = cd->id;
1243 }
1244 return cd;
1245 }
1246
1247 //---------------------------------------------------------
1248 // getDescription
1249 // same but lookup by name and optionally parsed chord
1250 //---------------------------------------------------------
1251
getDescription(const QString & name,const ParsedChord * pc)1252 const ChordDescription* Harmony::getDescription(const QString& name, const ParsedChord* pc)
1253 {
1254 const ChordDescription* cd = descr(name, pc);
1255 if (cd)
1256 _id = cd->id;
1257 else {
1258 cd = generateDescription();
1259 _id = cd->id;
1260 }
1261 return cd;
1262 }
1263
1264 //---------------------------------------------------------
1265 // getRealizedHarmony
1266 // get realized harmony or create one for the current symbol
1267 // also updates the realized harmony and accounts for
1268 // transposition. RealizedHarmony objects cannot be cached
1269 // since the notes generated depends on context rather than
1270 // just root, bass, chord symbol, and voicing.
1271 //---------------------------------------------------------
1272
getRealizedHarmony()1273 const RealizedHarmony& Harmony::getRealizedHarmony()
1274 {
1275 int offset = 0; //semitone offset for pitch adjustment
1276 Staff* st = staff();
1277 Interval interval = st->part()->instrument(tick())->transpose();
1278 if (!score()->styleB(Sid::concertPitch))
1279 offset = interval.chromatic;
1280
1281 //Adjust for Nashville Notation, might be temporary
1282 // TODO: set dirty on add/remove of keysig
1283 if (_harmonyType == HarmonyType::NASHVILLE && !_realizedHarmony.valid()) {
1284 Key key = staff()->key(tick());
1285 //parse root
1286 int rootTpc = function2Tpc(_function, key);
1287
1288 //parse bass
1289 int slash = _textName.lastIndexOf('/');
1290 int bassTpc;
1291 if (slash == -1)
1292 bassTpc = Tpc::TPC_INVALID;
1293 else
1294 bassTpc = function2Tpc(_textName.mid(slash + 1), key);
1295 _realizedHarmony.update(rootTpc, bassTpc, offset);
1296 }
1297 else
1298 _realizedHarmony.update(_rootTpc, _baseTpc, offset);
1299 return _realizedHarmony;
1300 }
1301
1302 //---------------------------------------------------------
1303 // realizedHarmony
1304 // get realized harmony or create one for the current symbol
1305 // without updating the realized harmony
1306 //---------------------------------------------------------
1307
realizedHarmony()1308 RealizedHarmony& Harmony::realizedHarmony()
1309 {
1310 return _realizedHarmony;
1311 }
1312
1313 //---------------------------------------------------------
1314 // generateDescription
1315 // generate new chord description from _textName
1316 // add to chord list using private id
1317 //---------------------------------------------------------
1318
generateDescription()1319 const ChordDescription* Harmony::generateDescription()
1320 {
1321 ChordList* cl = score()->style().chordList();
1322 ChordDescription cd(_textName);
1323 cd.complete(_parsedForm, cl);
1324 // remove parsed chord from description
1325 // so we will only match it literally in the future
1326 cd.parsedChords.clear();
1327 return &*cl->insert(cd.id, cd);
1328 }
1329
1330 //---------------------------------------------------------
1331 // layout
1332 //---------------------------------------------------------
1333
layout()1334 void Harmony::layout()
1335 {
1336 if (!parent()) {
1337 setPos(0.0, 0.0);
1338 setOffset(0.0, 0.0);
1339 layout1();
1340 return;
1341 }
1342 //if (isStyled(Pid::OFFSET))
1343 // setOffset(propertyDefault(Pid::OFFSET).toPointF());
1344
1345 layout1();
1346 setPos(calculateBoundingRect());
1347 }
1348
1349 //---------------------------------------------------------
1350 // layout1
1351 //---------------------------------------------------------
1352
layout1()1353 void Harmony::layout1()
1354 {
1355 if (isLayoutInvalid())
1356 createLayout();
1357 if (textBlockList().empty())
1358 textBlockList().append(TextBlock());
1359 calculateBoundingRect(); // for normal symbols this is called in layout: computeMinWidth()
1360 if (hasFrame())
1361 layoutFrame();
1362 score()->addRefresh(canvasBoundingRect());
1363 }
1364
1365 //---------------------------------------------------------
1366 // calculateBoundingRect
1367 //---------------------------------------------------------
1368
calculateBoundingRect()1369 QPoint Harmony::calculateBoundingRect()
1370 {
1371 const qreal ypos = (placeBelow() && staff()) ? staff()->height() : 0.0;
1372 const FretDiagram* fd = (parent() && parent()->isFretDiagram()) ? toFretDiagram(parent()) : nullptr;
1373 const qreal cw = symWidth(SymId::noteheadBlack);
1374 qreal newx = 0.0;
1375 qreal newy = 0.0;
1376
1377 if (textList.empty()) {
1378 TextBase::layout1();
1379
1380 // When in EDIT mode, the bbox is different as in NORMAL mode.
1381 // Adjust the position so the both bbox have the same alignment.
1382
1383 qreal xx = 0.0;
1384 qreal yy = 0.0;
1385 if (fd) {
1386 if (align() & Align::RIGHT)
1387 xx = fd->width() / 2.0;
1388 yy = rypos();
1389 }
1390 else {
1391 if (align() & Align::RIGHT)
1392 xx = cw;
1393 else if (align() & Align::HCENTER)
1394 xx = cw / 2.0;
1395 yy = ypos - ((align() & Align::BOTTOM) ? _harmonyHeight - bbox().height() : 0.0);
1396 }
1397
1398 newx = xx;
1399 newy = yy;
1400 }
1401 else {
1402 QRectF bb;
1403 for (TextSegment* ts : qAsConst(textList))
1404 bb |= ts->tightBoundingRect().translated(ts->x, ts->y);
1405
1406 qreal yy = -bb.y(); // Align::TOP
1407 if (align() & Align::VCENTER)
1408 yy = -bb.y() / 2.0;
1409 else if (align() & Align::BASELINE)
1410 yy = 0.0;
1411 else if (align() & Align::BOTTOM)
1412 yy = -bb.height() - bb.y();
1413
1414 qreal xx = -bb.x(); // Align::LEFT
1415 if (fd) {
1416 if (align() & Align::RIGHT)
1417 xx = fd->bbox().width() - bb.width();
1418 else if (align() & Align::HCENTER)
1419 xx = fd->centerX() - bb.width() / 2.0;
1420
1421 newx = 0.0;
1422 newy = ypos - yy - score()->styleP(Sid::harmonyFretDist);
1423 }
1424 else {
1425 if (align() & Align::RIGHT)
1426 xx = -bb.x() -bb.width() + cw;
1427 else if (align() & Align::HCENTER)
1428 xx = -bb.x() -bb.width() / 2.0 + cw / 2.0;
1429
1430 newx = 0.0;
1431 newy = ypos;
1432 }
1433
1434 for (TextSegment* ts : qAsConst(textList))
1435 ts->offset = QPointF(xx, yy);
1436
1437 setbbox(bb.translated(xx, yy));
1438 _harmonyHeight = bbox().height();
1439
1440 for (int i = 0; i < rows(); ++i) {
1441 TextBlock& t = textBlockList()[i];
1442
1443 // when MS switch to editing Harmony MS draws text defined by textBlockList().
1444 // When MS switches back to normal state it draws text from textList
1445 // To correct placement of text in editing we need to layout textBlockList() elements
1446 t.layout(this);
1447 for (auto& s : t.fragments()) {
1448 s.pos = { 0.0 , 0.0 };
1449 }
1450
1451 }
1452 }
1453 return QPoint(newx, newy);
1454 }
1455
1456 //---------------------------------------------------------
1457 // xShapeOffset
1458 // Returns the offset for the shapes.
1459 //---------------------------------------------------------
1460
xShapeOffset() const1461 qreal Harmony::xShapeOffset() const
1462 {
1463 const FretDiagram* fd = (parent() && parent()->isFretDiagram()) ? toFretDiagram(parent()) : nullptr;
1464 return (fd && textList.empty()) ? fd->centerX() : 0.0;
1465 }
1466
1467 //---------------------------------------------------------
1468 // draw
1469 //---------------------------------------------------------
1470
draw(QPainter * painter) const1471 void Harmony::draw(QPainter* painter) const
1472 {
1473 // painter->setPen(curColor());
1474 if (textList.empty()) {
1475 TextBase::draw(painter);
1476 return;
1477 }
1478 if (hasFrame()) {
1479 if (frameWidth().val() != 0.0) {
1480 QColor color = frameColor();
1481 QPen pen(color, frameWidth().val() * spatium(), Qt::SolidLine,
1482 Qt::SquareCap, Qt::MiterJoin);
1483 painter->setPen(pen);
1484 }
1485 else
1486 painter->setPen(Qt::NoPen);
1487 QColor bg(bgColor());
1488 painter->setBrush(bg.alpha() ? QBrush(bg) : Qt::NoBrush);
1489 if (circle())
1490 painter->drawArc(frame, 0, 5760);
1491 else {
1492 int r2 = frameRound();
1493 if (r2 > 99)
1494 r2 = 99;
1495 painter->drawRoundedRect(frame, frameRound(), r2);
1496 }
1497 }
1498 painter->setBrush(Qt::NoBrush);
1499 QColor color = textColor();
1500 painter->setPen(color);
1501 for (const TextSegment* ts : textList) {
1502 QFont f(ts->font);
1503 f.setPointSizeF(f.pointSizeF() * MScore::pixelRatio);
1504 #ifndef Q_OS_MACOS
1505 TextBase::drawTextWorkaround(painter, f, ts->pos(), ts->text);
1506 #else
1507 painter->setFont(f);
1508 painter->drawText(ts->pos(), ts->text);
1509 #endif
1510 }
1511 }
1512
1513 //---------------------------------------------------------
1514 // drawEditMode
1515 //---------------------------------------------------------
1516
drawEditMode(QPainter * p,EditData & ed)1517 void Harmony::drawEditMode(QPainter* p, EditData& ed)
1518 {
1519 TextBase::drawEditMode(p, ed);
1520
1521 QColor originalColor = color();
1522 if (showSpell) {
1523 setColor(QColor(Qt::red));
1524 setSelected(false);
1525 }
1526 QPointF pos(canvasPos());
1527 p->translate(pos);
1528 TextBase::draw(p);
1529 p->translate(-pos);
1530 if (showSpell) {
1531 setColor(originalColor);
1532 setSelected(true);
1533 }
1534 }
1535
1536 //---------------------------------------------------------
1537 // TextSegment
1538 //---------------------------------------------------------
1539
TextSegment(const QString & s,const QFont & f,qreal x,qreal y)1540 TextSegment::TextSegment(const QString& s, const QFont& f, qreal x, qreal y)
1541 {
1542 set(s, f, x, y, QPointF());
1543 select = false;
1544 }
1545
1546 //---------------------------------------------------------
1547 // width
1548 //---------------------------------------------------------
1549
width() const1550 qreal TextSegment::width() const
1551 {
1552 QFontMetricsF fm(font, MScore::paintDevice());
1553 #if 1
1554 return fm.width(text);
1555 #else
1556 qreal w = 0.0;
1557 foreach(QChar c, text) {
1558 // if we calculate width by character, at least skip high surrogates
1559 if (c.isHighSurrogate())
1560 continue;
1561 w += fm.width(c);
1562 }
1563 return w;
1564 #endif
1565 }
1566
1567 //---------------------------------------------------------
1568 // boundingRect
1569 //---------------------------------------------------------
1570
boundingRect() const1571 QRectF TextSegment::boundingRect() const
1572 {
1573 QFontMetricsF fm(font, MScore::paintDevice());
1574 return fm.boundingRect(text);
1575 }
1576
1577 //---------------------------------------------------------
1578 // tightBoundingRect
1579 //---------------------------------------------------------
1580
tightBoundingRect() const1581 QRectF TextSegment::tightBoundingRect() const
1582 {
1583 QFontMetricsF fm(font, MScore::paintDevice());
1584 return fm.tightBoundingRect(text);
1585 }
1586
1587 //---------------------------------------------------------
1588 // set
1589 //---------------------------------------------------------
1590
set(const QString & s,const QFont & f,qreal _x,qreal _y,QPointF _offset)1591 void TextSegment::set(const QString& s, const QFont& f, qreal _x, qreal _y, QPointF _offset)
1592 {
1593 font = f;
1594 x = _x;
1595 y = _y;
1596 offset = _offset;
1597 setText(s);
1598 }
1599
1600 //---------------------------------------------------------
1601 // render
1602 //---------------------------------------------------------
1603
render(const QString & s,qreal & x,qreal & y)1604 void Harmony::render(const QString& s, qreal& x, qreal& y)
1605 {
1606 int fontIdx = 0;
1607 if (!s.isEmpty()) {
1608 QFont f = _harmonyType != HarmonyType::ROMAN ? fontList[fontIdx] : font();
1609 TextSegment* ts = new TextSegment(s, f, x, y);
1610 textList.append(ts);
1611 x += ts->width();
1612 }
1613 }
1614
1615 //---------------------------------------------------------
1616 // render
1617 //---------------------------------------------------------
1618
render(const QList<RenderAction> & renderList,qreal & x,qreal & y,int tpc,NoteSpellingType noteSpelling,NoteCaseType noteCase)1619 void Harmony::render(const QList<RenderAction>& renderList, qreal& x, qreal& y, int tpc, NoteSpellingType noteSpelling, NoteCaseType noteCase)
1620 {
1621 ChordList* chordList = score()->style().chordList();
1622 QStack<QPointF> stack;
1623 int fontIdx = 0;
1624 qreal _spatium = spatium();
1625 qreal mag = magS();
1626
1627 // qDebug("===");
1628 for (const RenderAction& a : renderList) {
1629 // a.print();
1630 if (a.type == RenderAction::RenderActionType::SET) {
1631 TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
1632 ChordSymbol cs = chordList->symbol(a.text);
1633 if (cs.isValid()) {
1634 ts->font = fontList[cs.fontIdx];
1635 ts->setText(cs.value);
1636 }
1637 else
1638 ts->setText(a.text);
1639 if (_harmonyType == HarmonyType::NASHVILLE) {
1640 qreal nmag = chordList->nominalMag();
1641 ts->font.setPointSizeF(ts->font.pointSizeF() * nmag);
1642 }
1643 textList.append(ts);
1644 x += ts->width();
1645 }
1646 else if (a.type == RenderAction::RenderActionType::MOVE) {
1647 x += a.movex * mag * _spatium * .2;
1648 y += a.movey * mag * _spatium * .2;
1649 }
1650 else if (a.type == RenderAction::RenderActionType::PUSH)
1651 stack.push(QPointF(x,y));
1652 else if (a.type == RenderAction::RenderActionType::POP) {
1653 if (!stack.empty()) {
1654 QPointF pt = stack.pop();
1655 x = pt.x();
1656 y = pt.y();
1657 }
1658 else
1659 qDebug("RenderAction::RenderActionType::POP: stack empty");
1660 }
1661 else if (a.type == RenderAction::RenderActionType::NOTE) {
1662 QString c;
1663 AccidentalVal acc;
1664 if (tpcIsValid(tpc))
1665 tpc2name(tpc, noteSpelling, noteCase, c, acc);
1666 else if (_function.size() > 0)
1667 c = _function.at(_function.size() - 1);
1668 TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
1669 QString lookup = "note" + c;
1670 ChordSymbol cs = chordList->symbol(lookup);
1671 if (!cs.isValid())
1672 cs = chordList->symbol(c);
1673 if (cs.isValid()) {
1674 ts->font = fontList[cs.fontIdx];
1675 ts->setText(cs.value);
1676 }
1677 else {
1678 ts->setText(c);
1679 }
1680 textList.append(ts);
1681 x += ts->width();
1682 }
1683 else if (a.type == RenderAction::RenderActionType::ACCIDENTAL) {
1684 QString c;
1685 QString acc;
1686 QString context = "accidental";
1687 if (tpcIsValid(tpc))
1688 tpc2name(tpc, noteSpelling, noteCase, c, acc);
1689 else if (_function.size() > 1)
1690 acc = _function.at(0);
1691 // German spelling - use special symbol for accidental in TPC_B_B
1692 // to allow it to be rendered as either Bb or B
1693 if (tpc == Tpc::TPC_B_B && noteSpelling == NoteSpellingType::GERMAN)
1694 context = "german_B";
1695 if (acc != "") {
1696 TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
1697 QString lookup = context + acc;
1698 ChordSymbol cs = chordList->symbol(lookup);
1699 if (!cs.isValid())
1700 cs = chordList->symbol(acc);
1701 if (cs.isValid()) {
1702 ts->font = fontList[cs.fontIdx];
1703 ts->setText(cs.value);
1704 }
1705 else
1706 ts->setText(acc);
1707 textList.append(ts);
1708 x += ts->width();
1709 }
1710 }
1711 else
1712 qDebug("unknown render action %d", static_cast<int>(a.type));
1713 }
1714 }
1715
1716 //---------------------------------------------------------
1717 // render
1718 // construct Chord Symbol
1719 //---------------------------------------------------------
1720
render()1721 void Harmony::render()
1722 {
1723 int capo = score()->styleI(Sid::capoPosition);
1724
1725 ChordList* chordList = score()->style().chordList();
1726
1727 fontList.clear();
1728 for (const ChordFont& cf : qAsConst(chordList->fonts)) {
1729 QFont ff(font());
1730 ff.setPointSizeF(ff.pointSizeF() * cf.mag);
1731 if (!(cf.family.isEmpty() || cf.family == "default"))
1732 ff.setFamily(cf.family);
1733 fontList.append(ff);
1734 }
1735 if (fontList.empty())
1736 fontList.append(font());
1737
1738 for (const TextSegment* s : qAsConst(textList))
1739 delete s;
1740 textList.clear();
1741 qreal x = 0.0, y = 0.0;
1742
1743 determineRootBaseSpelling();
1744
1745 if (_leftParen)
1746 render("( ", x, y);
1747
1748 if (_rootTpc != Tpc::TPC_INVALID) {
1749 // render root
1750 render(chordList->renderListRoot, x, y, _rootTpc, _rootSpelling, _rootRenderCase);
1751 // render extension
1752 const ChordDescription* cd = getDescription();
1753 if (cd)
1754 render(cd->renderList, x, y, 0);
1755 }
1756 else if (_harmonyType == HarmonyType::NASHVILLE) {
1757 // render function
1758 render(chordList->renderListFunction, x, y, _rootTpc, _rootSpelling, _rootRenderCase);
1759 qreal adjust = chordList->nominalAdjust();
1760 y += adjust * magS() * spatium() * .2;
1761 // render extension
1762 const ChordDescription* cd = getDescription();
1763 if (cd)
1764 render(cd->renderList, x, y, 0);
1765 }
1766 else {
1767 render(_textName, x, y);
1768 }
1769
1770 // render bass
1771 if (_baseTpc != Tpc::TPC_INVALID)
1772 render(chordList->renderListBase, x, y, _baseTpc, _baseSpelling, _baseRenderCase);
1773
1774 if (_rootTpc != Tpc::TPC_INVALID && capo > 0 && capo < 12) {
1775 int tpcOffset[] = { 0, 5, -2, 3, -4, 1, 6, -1, 4, -3, 2, -5 };
1776 int capoRootTpc = _rootTpc + tpcOffset[capo];
1777 int capoBassTpc = _baseTpc;
1778
1779 if (capoBassTpc != Tpc::TPC_INVALID)
1780 capoBassTpc += tpcOffset[capo];
1781
1782 /*
1783 * For guitarists, avoid x and bb in Root or Bass,
1784 * and also avoid E#, B#, Cb and Fb in Root.
1785 */
1786 if (capoRootTpc < 8 || (capoBassTpc != Tpc::TPC_INVALID && capoBassTpc < 6)) {
1787 capoRootTpc += 12;
1788 if (capoBassTpc != Tpc::TPC_INVALID)
1789 capoBassTpc += 12;
1790 }
1791 else if (capoRootTpc > 24 || (capoBassTpc != Tpc::TPC_INVALID && capoBassTpc > 26)) {
1792 capoRootTpc -= 12;
1793 if (capoBassTpc != Tpc::TPC_INVALID)
1794 capoBassTpc -= 12;
1795 }
1796
1797 render("(", x, y);
1798 render(chordList->renderListRoot, x, y, capoRootTpc, _rootSpelling, _rootRenderCase);
1799
1800 // render extension
1801 const ChordDescription* cd = getDescription();
1802 if (cd)
1803 render(cd->renderList, x, y, 0);
1804
1805 if (capoBassTpc != Tpc::TPC_INVALID)
1806 render(chordList->renderListBase, x, y, capoBassTpc, _baseSpelling, _baseRenderCase);
1807 render(")", x, y);
1808 }
1809
1810 if (_rightParen)
1811 render(" )", x, y);
1812 }
1813
1814 //---------------------------------------------------------
1815 // spatiumChanged
1816 //---------------------------------------------------------
1817
spatiumChanged(qreal oldValue,qreal newValue)1818 void Harmony::spatiumChanged(qreal oldValue, qreal newValue)
1819 {
1820 TextBase::spatiumChanged(oldValue, newValue);
1821 render();
1822 }
1823
1824 //---------------------------------------------------------
1825 // localSpatiumChanged
1826 //---------------------------------------------------------
1827
localSpatiumChanged(qreal oldValue,qreal newValue)1828 void Harmony::localSpatiumChanged(qreal oldValue, qreal newValue)
1829 {
1830 TextBase::localSpatiumChanged(oldValue, newValue);
1831 render();
1832 }
1833
1834 //---------------------------------------------------------
1835 // extensionName
1836 //---------------------------------------------------------
1837
extensionName() const1838 const QString& Harmony::extensionName() const
1839 {
1840 return _textName;
1841 }
1842
1843 //---------------------------------------------------------
1844 // xmlKind
1845 //---------------------------------------------------------
1846
xmlKind() const1847 QString Harmony::xmlKind() const
1848 {
1849 const ChordDescription* cd = descr();
1850 return cd ? cd->xmlKind : QString();
1851 }
1852
1853 //---------------------------------------------------------
1854 // musicXmlText
1855 //---------------------------------------------------------
1856
musicXmlText() const1857 QString Harmony::musicXmlText() const
1858 {
1859 const ChordDescription* cd = descr();
1860 return cd ? cd->xmlText : QString();
1861 }
1862
1863 //---------------------------------------------------------
1864 // xmlSymbols
1865 //---------------------------------------------------------
1866
xmlSymbols() const1867 QString Harmony::xmlSymbols() const
1868 {
1869 const ChordDescription* cd = descr();
1870 return cd ? cd->xmlSymbols : QString();
1871 }
1872
1873 //---------------------------------------------------------
1874 // xmlParens
1875 //---------------------------------------------------------
1876
xmlParens() const1877 QString Harmony::xmlParens() const
1878 {
1879 const ChordDescription* cd = descr();
1880 return cd ? cd->xmlParens : QString();
1881 }
1882
1883 //---------------------------------------------------------
1884 // xmlDegrees
1885 //---------------------------------------------------------
1886
xmlDegrees() const1887 QStringList Harmony::xmlDegrees() const
1888 {
1889 const ChordDescription* cd = descr();
1890 return cd ? cd->xmlDegrees : QStringList();
1891 }
1892
1893 //---------------------------------------------------------
1894 // degree
1895 //---------------------------------------------------------
1896
degree(int i) const1897 HDegree Harmony::degree(int i) const
1898 {
1899 return _degreeList.value(i);
1900 }
1901
1902 //---------------------------------------------------------
1903 // addDegree
1904 //---------------------------------------------------------
1905
addDegree(const HDegree & d)1906 void Harmony::addDegree(const HDegree& d)
1907 {
1908 _degreeList << d;
1909 }
1910
1911 //---------------------------------------------------------
1912 // numberOfDegrees
1913 //---------------------------------------------------------
1914
numberOfDegrees() const1915 int Harmony::numberOfDegrees() const
1916 {
1917 return _degreeList.size();
1918 }
1919
1920 //---------------------------------------------------------
1921 // clearDegrees
1922 //---------------------------------------------------------
1923
clearDegrees()1924 void Harmony::clearDegrees()
1925 {
1926 _degreeList.clear();
1927 }
1928
1929 //---------------------------------------------------------
1930 // degreeList
1931 //---------------------------------------------------------
1932
degreeList() const1933 const QList<HDegree>& Harmony::degreeList() const
1934 {
1935 return _degreeList;
1936 }
1937
1938 //---------------------------------------------------------
1939 // parsedForm
1940 //---------------------------------------------------------
1941
parsedForm()1942 const ParsedChord* Harmony::parsedForm()
1943 {
1944 if (!_parsedForm) {
1945 ChordList* cl = score()->style().chordList();
1946 _parsedForm = new ParsedChord();
1947 _parsedForm->parse(_textName, cl, false);
1948 }
1949 return _parsedForm;
1950 }
1951
1952 //---------------------------------------------------------
1953 // setHarmonyType
1954 //---------------------------------------------------------
1955
setHarmonyType(HarmonyType val)1956 void Harmony::setHarmonyType(HarmonyType val)
1957 {
1958 _harmonyType = val;
1959 setPlacement(Placement(propertyDefault(Pid::PLACEMENT).toInt()));
1960 switch (_harmonyType) {
1961 case HarmonyType::STANDARD:
1962 initTid(Tid::HARMONY_A);
1963 break;
1964 case HarmonyType::ROMAN:
1965 initTid(Tid::HARMONY_ROMAN);
1966 break;
1967 case HarmonyType::NASHVILLE:
1968 initTid(Tid::HARMONY_NASHVILLE);
1969 break;
1970 }
1971 // TODO: convert text
1972 }
1973
1974 //---------------------------------------------------------
1975 // userName
1976 //---------------------------------------------------------
1977
userName() const1978 QString Harmony::userName() const
1979 {
1980 switch (_harmonyType) {
1981 case HarmonyType::ROMAN:
1982 return QObject::tr("Roman numeral");
1983 case HarmonyType::NASHVILLE:
1984 return QObject::tr("Nashville number");
1985 case HarmonyType::STANDARD:
1986 break;
1987 }
1988 return Element::userName();
1989 }
1990
1991 //---------------------------------------------------------
1992 // accessibleInfo
1993 //---------------------------------------------------------
1994
accessibleInfo() const1995 QString Harmony::accessibleInfo() const
1996 {
1997 return QString("%1: %2").arg(userName(), harmonyName());
1998 }
1999
2000 //---------------------------------------------------------
2001 // screenReaderInfo
2002 //---------------------------------------------------------
2003
screenReaderInfo() const2004 QString Harmony::screenReaderInfo() const
2005 {
2006 return QString("%1 %2").arg(userName(), generateScreenReaderInfo());
2007 }
2008
2009 //---------------------------------------------------------
2010 // generateScreenReaderInfo
2011 //---------------------------------------------------------
2012
generateScreenReaderInfo() const2013 QString Harmony::generateScreenReaderInfo() const
2014 {
2015 QString rez;
2016 switch (_harmonyType) {
2017 case HarmonyType::ROMAN: {
2018 QString aux = _textName;
2019 bool hasUpper = aux.contains('I') || aux.contains('V');
2020 bool hasLower = aux.contains('i') || aux.contains('v');
2021 if (hasLower && !hasUpper)
2022 rez = QString("%1 %2").arg(rez, QObject::tr("lower case"));
2023 aux = aux.toLower();
2024 static std::vector<std::pair<QString, QString>> rnaReplacements {
2025 { "vii", "7" },
2026 { "vi", "6" },
2027 { "iv", "4" },
2028 { "v", "5" },
2029 { "iii", "3" },
2030 { "ii", "2" },
2031 { "i", "1" },
2032 };
2033 static std::vector<std::pair<QString, QString>> symbolReplacements {
2034 { "b", "♭" },
2035 { "h", "♮" },
2036 { "#", "♯" },
2037 { "bb", "" },
2038 { "##", "" },
2039 // TODO: use SMuFL glyphs and translate
2040 //{ "o", ""},
2041 //{ "0", ""},
2042 //{ "\+", ""},
2043 //{ "\^", ""},
2044 };
2045 for (auto const &r : rnaReplacements)
2046 aux.replace(r.first, r.second);
2047 for (auto const &r : symbolReplacements) {
2048 // only replace when not preceded by backslash
2049 QString s = "(?<!\\\\)" + r.first;
2050 QRegularExpression re(s);
2051 aux.replace(re, r.second);
2052 }
2053 // construct string one character at a time
2054 for (auto c : qAsConst(aux))
2055 rez = QString("%1 %2").arg(rez).arg(c);
2056 }
2057 return rez;
2058 case HarmonyType::NASHVILLE:
2059 if (!_function.isEmpty())
2060 rez = QString("%1 %2").arg(rez, _function);
2061 break;
2062 case HarmonyType::STANDARD:
2063 default:
2064 if (_rootTpc != Tpc::TPC_INVALID)
2065 rez = QString("%1 %2").arg(rez, tpc2name(_rootTpc, NoteSpellingType::STANDARD, NoteCaseType::AUTO, true));
2066 }
2067
2068 if (const_cast<Harmony*>(this)->parsedForm() && !hTextName().isEmpty()) {
2069 QString aux = const_cast<Harmony*>(this)->parsedForm()->handle();
2070 aux = aux.replace("#", QObject::tr("♯")).replace("<", "");
2071 QString extension = "";
2072
2073 for (QString s : aux.split(">", QString::SkipEmptyParts)) {
2074 if (!s.contains("blues"))
2075 s.replace("b", QObject::tr("♭"));
2076 extension += s + " ";
2077 }
2078 rez = QString("%1 %2").arg(rez, extension);
2079 }
2080 else {
2081 rez = QString("%1 %2").arg(rez, hTextName());
2082 }
2083
2084 if (_baseTpc != Tpc::TPC_INVALID)
2085 rez = QString("%1 / %2").arg(rez, tpc2name(_baseTpc, NoteSpellingType::STANDARD, NoteCaseType::AUTO, true));
2086
2087 return rez;
2088 }
2089
2090 //---------------------------------------------------------
2091 // acceptDrop
2092 //---------------------------------------------------------
2093
acceptDrop(EditData & data) const2094 bool Harmony::acceptDrop(EditData& data) const
2095 {
2096 Element* e = data.dropElement;
2097 if (e->isFretDiagram()) {
2098 return true;
2099 }
2100 else if (e->isSymbol() || e->isFSymbol()) {
2101 // symbols can be added in edit mode
2102 if (data.getData(this))
2103 return true;
2104 else
2105 return false;
2106 }
2107 return false;
2108 }
2109
2110 //---------------------------------------------------------
2111 // drop
2112 //---------------------------------------------------------
2113
drop(EditData & data)2114 Element* Harmony::drop(EditData& data)
2115 {
2116 Element* e = data.dropElement;
2117 if (e->isFretDiagram()) {
2118 FretDiagram* fd = toFretDiagram(e);
2119 fd->setParent(parent());
2120 fd->setTrack(track());
2121 score()->undoAddElement(fd);
2122 }
2123 else if (e->isSymbol() || e->isFSymbol()) {
2124 TextBase::drop(data);
2125 layout1();
2126 e = 0; // cannot select
2127 }
2128 else {
2129 qWarning("Harmony: cannot drop <%s>\n", e->name());
2130 delete e;
2131 e = 0;
2132 }
2133 return e;
2134 }
2135
2136 //---------------------------------------------------------
2137 // getProperty
2138 //---------------------------------------------------------
2139
getProperty(Pid pid) const2140 QVariant Harmony::getProperty(Pid pid) const
2141 {
2142 switch (pid) {
2143 case Pid::PLAY:
2144 return QVariant(_play);
2145 break;
2146 case Pid::HARMONY_TYPE:
2147 return QVariant(int(_harmonyType));
2148 break;
2149 case Pid::HARMONY_VOICE_LITERAL:
2150 return _realizedHarmony.literal();
2151 break;
2152 case Pid::HARMONY_VOICING:
2153 return int(_realizedHarmony.voicing());
2154 break;
2155 case Pid::HARMONY_DURATION:
2156 return int(_realizedHarmony.duration());
2157 break;
2158 default:
2159 return TextBase::getProperty(pid);
2160 }
2161 }
2162
2163 //---------------------------------------------------------
2164 // setProperty
2165 //---------------------------------------------------------
2166
setProperty(Pid pid,const QVariant & v)2167 bool Harmony::setProperty(Pid pid, const QVariant& v)
2168 {
2169 switch (pid) {
2170 case Pid::PLAY:
2171 setPlay(v.toBool());
2172 break;
2173 case Pid::HARMONY_TYPE:
2174 setHarmonyType(HarmonyType(v.toInt()));
2175 break;
2176 case Pid::HARMONY_VOICE_LITERAL:
2177 _realizedHarmony.setLiteral(v.toBool());
2178 break;
2179 case Pid::HARMONY_VOICING:
2180 _realizedHarmony.setVoicing(Voicing(v.toInt()));
2181 break;
2182 case Pid::HARMONY_DURATION:
2183 _realizedHarmony.setDuration(HDuration(v.toInt()));
2184 break;
2185 default:
2186 if (TextBase::setProperty(pid, v)) {
2187 if (pid == Pid::TEXT)
2188 setHarmony(v.toString());
2189 render();
2190 break;
2191 }
2192 return false;
2193 }
2194 return true;
2195 }
2196
2197 //---------------------------------------------------------
2198 // propertyDefault
2199 //---------------------------------------------------------
2200
propertyDefault(Pid id) const2201 QVariant Harmony::propertyDefault(Pid id) const
2202 {
2203 QVariant v;
2204 switch (id) {
2205 case Pid::HARMONY_TYPE:
2206 v = int(HarmonyType::STANDARD);
2207 break;
2208 case Pid::SUB_STYLE: {
2209 switch (_harmonyType) {
2210 case HarmonyType::STANDARD:
2211 v = int(Tid::HARMONY_A);
2212 break;
2213 case HarmonyType::ROMAN:
2214 v = int(Tid::HARMONY_ROMAN);
2215 break;
2216 case HarmonyType::NASHVILLE:
2217 v = int(Tid::HARMONY_NASHVILLE);
2218 break;
2219 }
2220 }
2221 break;
2222 case Pid::OFFSET:
2223 if (parent() && parent()->isFretDiagram()) {
2224 v = QVariant(QPointF(0.0, 0.0));
2225 break;
2226 }
2227 // fall-through
2228 default:
2229 v = TextBase::propertyDefault(id);
2230 break;
2231 }
2232 return v;
2233 }
2234
2235 //---------------------------------------------------------
2236 // getPropertyStyle
2237 //---------------------------------------------------------
2238
getPropertyStyle(Pid pid) const2239 Sid Harmony::getPropertyStyle(Pid pid) const
2240 {
2241 if (pid == Pid::OFFSET) {
2242 if (parent() && parent()->isFretDiagram())
2243 return Sid::NOSTYLE;
2244 else if (tid() == Tid::HARMONY_A)
2245 return placeAbove() ? Sid::chordSymbolAPosAbove : Sid::chordSymbolAPosBelow;
2246 else if (tid() == Tid::HARMONY_B)
2247 return placeAbove() ? Sid::chordSymbolBPosAbove : Sid::chordSymbolBPosBelow;
2248 else if (tid() == Tid::HARMONY_ROMAN)
2249 return placeAbove() ? Sid::romanNumeralPosAbove : Sid::romanNumeralPosBelow;
2250 else if (tid() == Tid::HARMONY_NASHVILLE)
2251 return placeAbove() ? Sid::nashvilleNumberPosAbove : Sid::nashvilleNumberPosBelow;
2252 }
2253 if (pid == Pid::PLACEMENT) {
2254 switch (_harmonyType) {
2255 case HarmonyType::STANDARD:
2256 return Sid::harmonyPlacement;
2257 case HarmonyType::ROMAN:
2258 return Sid::romanNumeralPlacement;
2259 case HarmonyType::NASHVILLE:
2260 return Sid::nashvilleNumberPlacement;
2261 }
2262 }
2263 return TextBase::getPropertyStyle(pid);
2264 }
2265
2266 }
2267