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",  "^");  // &Delta;
853             s.replace("\u00d0",  "o");  // &deg;
854             s.replace("\u00f8",  "0");  // &oslash;
855             s.replace("\u00d8",  "0");  // &Oslash;
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