//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2008-2011 Werner Schweer
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2
//  as published by the Free Software Foundation and appearing in
//  the file LICENCE.GPL
//=============================================================================

#include "harmony.h"
#include "pitchspelling.h"
#include "score.h"
#include "system.h"
#include "measure.h"
#include "segment.h"
#include "chordlist.h"
#include "mscore.h"
#include "fret.h"
#include "staff.h"
#include "part.h"
#include "utils.h"
#include "sym.h"
#include "xml.h"

namespace Ms {

//---------------------------------------------------------
//   harmonyName
//---------------------------------------------------------

QString Harmony::harmonyName() const
      {
      // Hack:
      const_cast<Harmony*>(this)->determineRootBaseSpelling();

      HChord hc = descr() ? descr()->chord : HChord();
      QString s, r, e, b;

      if (_leftParen)
            s = "(";

      if (_rootTpc != Tpc::TPC_INVALID)
            r = tpc2name(_rootTpc, _rootSpelling, _rootCase);
      else if (_harmonyType != HarmonyType::STANDARD)
            r = _function;

      if (_textName != "") {
            e = _textName;
            if (_harmonyType != HarmonyType::ROMAN)
                  e.remove('=');
            }
      else if (!_degreeList.empty()) {
            hc.add(_degreeList);
            // try to find the chord in chordList
            const ChordDescription* newExtension = 0;
            const ChordList* cl = score()->style().chordList();
            for (const ChordDescription& cd : *cl) {
                  if (cd.chord == hc && !cd.names.empty()) {
                        newExtension = &cd;
                        break;
                        }
                  }
            // now determine the chord name
            if (newExtension)
                  e = newExtension->names.front();
            else {
                  // not in table, fallback to using HChord.name()
                  r = hc.name(_rootTpc);
                  e = "";
                  }
            }

      if (_baseTpc != Tpc::TPC_INVALID)
            b = "/" + tpc2name(_baseTpc, _baseSpelling, _baseCase);

      s += r + e + b;

      if (_rightParen)
            s += ")";

      return s;
      }

//---------------------------------------------------------
//   rootName
//---------------------------------------------------------

QString Harmony::rootName()
      {
      determineRootBaseSpelling();
      return tpc2name(_rootTpc, _rootSpelling, _rootCase);
      }

//---------------------------------------------------------
//   baseName
//---------------------------------------------------------

QString Harmony::baseName()
      {
      determineRootBaseSpelling();
      return tpc2name(_baseTpc, _baseSpelling, _baseCase);
      }

//---------------------------------------------------------
//   resolveDegreeList
//    try to detect chord number and to eliminate degree
//    list
//---------------------------------------------------------

void Harmony::resolveDegreeList()
      {
      if (_degreeList.empty())
            return;

      HChord hc = descr() ? descr()->chord : HChord();

      hc.add(_degreeList);

// qDebug("resolveDegreeList: <%s> <%s-%s>: ", _descr->name, _descr->xmlKind, _descr->xmlDegrees);
// hc.print();
// _descr->chord.print();

      // try to find the chord in chordList
      const ChordList* cl = score()->style().chordList();
      foreach(const ChordDescription& cd, *cl) {
            if ((cd.chord == hc) && !cd.names.empty()) {
qDebug("ResolveDegreeList: found in table as %s", qPrintable(cd.names.front()));
                  _id = cd.id;
                  _degreeList.clear();
                  return;
                  }
            }
qDebug("ResolveDegreeList: not found in table");
      }

//---------------------------------------------------------
//   chordSymbolStyle
//---------------------------------------------------------

const ElementStyle chordSymbolStyle {
      { Sid::harmonyPlacement, Pid::PLACEMENT  },
      { Sid::minHarmonyDistance, Pid::MIN_DISTANCE },
      };

//---------------------------------------------------------
//   Harmony
//---------------------------------------------------------

Harmony::Harmony(Score* s)
   : TextBase(s, Tid::HARMONY_A, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
      {
      _rootTpc    = Tpc::TPC_INVALID;
      _baseTpc    = Tpc::TPC_INVALID;
      _rootCase   = NoteCaseType::CAPITAL;
      _baseCase   = NoteCaseType::CAPITAL;
      _id         = -1;
      _parsedForm = 0;
      _harmonyType = HarmonyType::STANDARD;
      _leftParen  = false;
      _rightParen = false;
      initElementStyle(&chordSymbolStyle);
      }

Harmony::Harmony(const Harmony& h)
   : TextBase(h)
      {
      _rootTpc    = h._rootTpc;
      _baseTpc    = h._baseTpc;
      _rootCase   = h._rootCase;
      _baseCase   = h._baseCase;
      _id         = h._id;
      _leftParen  = h._leftParen;
      _rightParen = h._rightParen;
      _degreeList = h._degreeList;
      _parsedForm = h._parsedForm ? new ParsedChord(*h._parsedForm) : 0;
      _harmonyType = h._harmonyType;
      _textName   = h._textName;
      _userName   = h._userName;
      _function   = h._function;
      for (const TextSegment* s : h.textList) {
            TextSegment* ns = new TextSegment();
            ns->set(s->text, s->font, s->x, s->y);
            textList.append(ns);
            }
      }

//---------------------------------------------------------
//   ~Harmony
//---------------------------------------------------------

Harmony::~Harmony()
      {
      foreach(const TextSegment* ts, textList)
            delete ts;
      if (_parsedForm)
            delete _parsedForm;
      }

//---------------------------------------------------------
//   write
//---------------------------------------------------------

void Harmony::write(XmlWriter& xml) const
      {
      if (!xml.canWrite(this))
            return;
      xml.stag(this);
      writeProperty(xml, Pid::HARMONY_TYPE);
      if (_leftParen)
            xml.tagE("leftParen");
      if (_rootTpc != Tpc::TPC_INVALID || _baseTpc != Tpc::TPC_INVALID) {
            int rRootTpc = _rootTpc;
            int rBaseTpc = _baseTpc;
            if (staff()) {
                  // parent can be a fret diagram
                  Segment* segment = parent()->isSegment() ? toSegment(parent()) : toSegment(parent()->parent());
                  Fraction tick = segment ? segment->tick() : Fraction(-1,1);
                  const Interval& interval = part()->instrument(tick)->transpose();
                  if (xml.clipboardmode() && !score()->styleB(Sid::concertPitch) && interval.chromatic) {
                        rRootTpc = transposeTpc(_rootTpc, interval, true);
                        rBaseTpc = transposeTpc(_baseTpc, interval, true);
                        }
                  }
            if (rRootTpc != Tpc::TPC_INVALID) {
                  xml.tag("root", rRootTpc);
                  if (_rootCase != NoteCaseType::CAPITAL)
                        xml.tag("rootCase", static_cast<int>(_rootCase));
                  }
            if (_id > 0)
                  xml.tag("extension", _id);
            // parser uses leading "=" as a hidden specifier for minor
            // this may or may not currently be incorporated into _textName
            QString writeName = _textName;
            if (_parsedForm && _parsedForm->name().startsWith("=") && !writeName.startsWith("="))
                  writeName = "=" + writeName;
            if (!writeName.isEmpty())
                  xml.tag("name", writeName);

            if (rBaseTpc != Tpc::TPC_INVALID) {
                  xml.tag("base", rBaseTpc);
                  if (_baseCase != NoteCaseType::CAPITAL)
                        xml.tag("baseCase", static_cast<int>(_baseCase));
                  }
            for (const HDegree& hd : _degreeList) {
                  HDegreeType tp = hd.type();
                  if (tp == HDegreeType::ADD || tp == HDegreeType::ALTER || tp == HDegreeType::SUBTRACT) {
                        xml.stag("degree");
                        xml.tag("degree-value", hd.value());
                        xml.tag("degree-alter", hd.alter());
                        switch (tp) {
                              case HDegreeType::ADD:
                                    xml.tag("degree-type", "add");
                                    break;
                              case HDegreeType::ALTER:
                                    xml.tag("degree-type", "alter");
                                    break;
                              case HDegreeType::SUBTRACT:
                                    xml.tag("degree-type", "subtract");
                                    break;
                              default:
                                    break;
                              }
                        xml.etag();
                        }
                  }
            }
      else
            xml.tag("name", _textName);
      if (!_function.isEmpty())
            xml.tag("function", _function);
      TextBase::writeProperties(xml, false, true);
      if (_rightParen)
            xml.tagE("rightParen");
      xml.etag();
      }

//---------------------------------------------------------
//   read
//---------------------------------------------------------

void Harmony::read(XmlReader& e)
      {
      while (e.readNextStartElement()) {
            const QStringRef& tag(e.name());
            if (tag == "base")
                  setBaseTpc(e.readInt());
            else if (tag == "baseCase")
                  _baseCase = static_cast<NoteCaseType>(e.readInt());
            else if (tag == "extension")
                  setId(e.readInt());
            else if (tag == "name")
                  _textName = e.readElementText();
            else if (tag == "root")
                  setRootTpc(e.readInt());
            else if (tag == "rootCase")
                  _rootCase = static_cast<NoteCaseType>(e.readInt());
            else if (tag == "function")
                  _function = e.readElementText();
            else if (tag == "degree") {
                  int degreeValue = 0;
                  int degreeAlter = 0;
                  QString degreeType = "";
                  while (e.readNextStartElement()) {
                        const QStringRef& t(e.name());
                        if (t == "degree-value")
                              degreeValue = e.readInt();
                        else if (t == "degree-alter")
                              degreeAlter = e.readInt();
                        else if (t == "degree-type")
                              degreeType = e.readElementText();
                        else
                              e.unknown();
                        }
                  if (degreeValue <= 0 || degreeValue > 13
                      || degreeAlter < -2 || degreeAlter > 2
                      || (degreeType != "add" && degreeType != "alter" && degreeType != "subtract")) {
                        qDebug("incorrect degree: degreeValue=%d degreeAlter=%d degreeType=%s",
                               degreeValue, degreeAlter, qPrintable(degreeType));
                        }
                  else {
                        if (degreeType == "add")
                              addDegree(HDegree(degreeValue, degreeAlter, HDegreeType::ADD));
                        else if (degreeType == "alter")
                              addDegree(HDegree(degreeValue, degreeAlter, HDegreeType::ALTER));
                        else if (degreeType == "subtract")
                              addDegree(HDegree(degreeValue, degreeAlter, HDegreeType::SUBTRACT));
                        }
                  }
            else if (tag == "leftParen") {
                  _leftParen = true;
                  e.readNext();
                  }
            else if (tag == "rightParen") {
                  _rightParen = true;
                  e.readNext();
                  }
            else if (readProperty(tag, e, Pid::POS_ABOVE))
                  ;
            else if (readProperty(tag, e, Pid::HARMONY_TYPE))
                  ;
            else if (!TextBase::readProperties(e))
                  e.unknown();
            }

      // TODO: now that we can render arbitrary chords,
      // we could try to construct a full representation from a degree list.
      // These will typically only exist for chords imported from MusicXML prior to MuseScore 2.0
      // or constructed in the Chord Symbol Properties dialog.

      if (_rootTpc != Tpc::TPC_INVALID) {
            if (_id > 0) {
                  // positive id will happen only for scores that were created with explicit chord lists
                  // lookup id in chord list and generate new description if necessary
                  getDescription();
                  }
            else
                  {
                  // default case: look up by name
                  // description will be found for any chord already read in this score
                  // and we will generate a new one if necessary
                  getDescription(_textName);
                  }
            }
      else if (_textName == "") {
            // unrecognized chords prior to 2.0 were stored as text with markup
            // we need to strip away the markup
            // this removes any user-applied formatting,
            // but we no longer support user-applied formatting for chord symbols anyhow
            // with any luck, the resulting text will be parseable now, so give it a shot
            createLayout();
            QString s = plainText();
            if (!s.isEmpty()) {
                  setHarmony(s);
                  return;
                  }
            // empty text could also indicate a root-less slash chord ("/E")
            // we'll fall through and render it normally
            }

      // render chord from description (or _textName)
      render();
      setPlainText(harmonyName());
      }

//---------------------------------------------------------
//   determineRootBaseSpelling
//---------------------------------------------------------

void Harmony::determineRootBaseSpelling(NoteSpellingType& rootSpelling, NoteCaseType& rootCase,
   NoteSpellingType& baseSpelling, NoteCaseType& baseCase)
      {
      // spelling
      if (score()->styleB(Sid::useStandardNoteNames))
            rootSpelling = NoteSpellingType::STANDARD;
      else if (score()->styleB(Sid::useGermanNoteNames))
            rootSpelling = NoteSpellingType::GERMAN;
      else if (score()->styleB(Sid::useFullGermanNoteNames))
            rootSpelling = NoteSpellingType::GERMAN_PURE;
      else if (score()->styleB(Sid::useSolfeggioNoteNames))
            rootSpelling = NoteSpellingType::SOLFEGGIO;
      else if (score()->styleB(Sid::useFrenchNoteNames))
            rootSpelling = NoteSpellingType::FRENCH;
      baseSpelling = rootSpelling;

      // case

      // always use case as typed if automatic capitalization is off
      if (!score()->styleB(Sid::automaticCapitalization)) {
            rootCase = _rootCase;
            baseCase = _baseCase;
            return;
            }

      // set default
      if (score()->styleB(Sid::allCapsNoteNames)) {
            rootCase = NoteCaseType::UPPER;
            baseCase = NoteCaseType::UPPER;
            }
      else {
            rootCase = NoteCaseType::CAPITAL;
            baseCase = NoteCaseType::CAPITAL;
            }

      // override for bass note
      if (score()->styleB(Sid::lowerCaseBassNotes))
            baseCase = NoteCaseType::LOWER;

      // override for minor chords
      if (score()->styleB(Sid::lowerCaseMinorChords)) {
            const ChordDescription* cd = descr();
            QString quality;
            if (cd) {
                  // use chord description if possible
                  // this is the usual case
                  quality = cd->quality();
                  }
            else if (_parsedForm) {
                  // this happens on load of new chord list
                  // for chord symbols that were added/edited since the score was loaded
                  // or read aloud with screenreader
                  // parsed form is usable even if out of date with respect to chord list
                  quality = _parsedForm->quality();
                  }
            else {
                  // this happens on load of new chord list
                  // for chord symbols that have not been edited since the score was loaded
                  // we need to parse this chord for now to determine quality
                  // but don't keep the parsed form around as we're not ready for it yet
                  quality = parsedForm()->quality();
                  delete _parsedForm;
                  _parsedForm = 0;
                  }
            if (quality == "minor" || quality == "diminished" || quality == "half-diminished")
                  rootCase = NoteCaseType::LOWER;
            }
      }

//---------------------------------------------------------
//   determineRootBaseSpelling
//---------------------------------------------------------

void Harmony::determineRootBaseSpelling()
      {
      determineRootBaseSpelling(_rootSpelling, _rootRenderCase,
        _baseSpelling, _baseRenderCase);
      }

//---------------------------------------------------------
//   convertNote
//    convert something like "C#" into tpc 21
//---------------------------------------------------------

static int convertNote(const QString& s, NoteSpellingType noteSpelling, NoteCaseType& noteCase, int& idx)
      {
      bool useGerman = false;
      bool useSolfeggio = false;
      static const int spellings[] = {
         // bb  b   -   #  ##
            0,  7, 14, 21, 28,  // C
            2,  9, 16, 23, 30,  // D
            4, 11, 18, 25, 32,  // E
           -1,  6, 13, 20, 27,  // F
            1,  8, 15, 22, 29,  // G
            3, 10, 17, 24, 31,  // A
            5, 12, 19, 26, 33,  // B
            };
      if (s == "")
            return Tpc::TPC_INVALID;
      noteCase = s[0].isLower() ? NoteCaseType::LOWER : NoteCaseType::CAPITAL;
      int acci;
      switch (noteSpelling) {
            case NoteSpellingType::SOLFEGGIO:
            case NoteSpellingType::FRENCH:
                  useSolfeggio = true;
                  if (s.toLower().startsWith("sol"))
                        acci = 3;
                  else
                        acci = 2;
                  break;
            case NoteSpellingType::GERMAN:
            case NoteSpellingType::GERMAN_PURE:
                  useGerman = true;
                  // fall through
            default:
                  acci = 1;
            }
      idx = acci;
      int alter = 0;
      int n = s.size();
      QString acc = s.right(n-acci);
      if (acc != "") {
            if (acc.startsWith("bb")) {
                  alter = -2;
                  idx += 2;
                  }
            else if (acc.startsWith("b")) {
                  alter = -1;
                  idx += 1;
                  }
            else if (useGerman && acc.startsWith("eses")) {
                  alter = -2;
                  idx += 4;
                  }
            else if (useGerman && (acc.startsWith("ses") || acc.startsWith("sas"))) {
                  alter = -2;
                  idx += 3;
                  }
            else if (useGerman && acc.startsWith("es")) {
                  alter = -1;
                  idx += 2;
                  }
            else if (useGerman && acc.startsWith("s") && !acc.startsWith("su")) {
                  alter = -1;
                  idx += 1;
                  }
            else if (acc.startsWith("##")) {
                  alter = 2;
                  idx += 2;
                  }
            else if (acc.startsWith("x")) {
                  alter = 2;
                  idx += 1;
                  }
            else if (acc.startsWith("#")) {
                  alter = 1;
                  idx += 1;
                  }
            else if (useGerman && acc.startsWith("isis")) {
                  alter = 2;
                  idx += 4;
                  }
            else if (useGerman && acc.startsWith("is")) {
                  alter = 1;
                  idx += 2;
                  }
            }
      int r;
      if (useGerman) {
            switch(s[0].toLower().toLatin1()) {
                  case 'c':   r = 0; break;
                  case 'd':   r = 1; break;
                  case 'e':   r = 2; break;
                  case 'f':   r = 3; break;
                  case 'g':   r = 4; break;
                  case 'a':   r = 5; break;
                  case 'h':   r = 6; break;
                  case 'b':
                        if (alter && alter != -1)
                              return Tpc::TPC_INVALID;
                        r = 6;
                        alter = -1;
                        break;
                  default:
                        return Tpc::TPC_INVALID;
                  }
            }
      else if (useSolfeggio) {
            if (s.length() < 2)
                  return Tpc::TPC_INVALID;
            if (s[1].isUpper())
                  noteCase = NoteCaseType::UPPER;
            QString ss = s.toLower().left(2);
            if (ss == "do")
                  r = 0;
            else if (ss == "re" || ss == "ré")
                  r = 1;
            else if (ss == "mi")
                  r = 2;
            else if (ss == "fa")
                  r = 3;
            else if (ss == "so")    // sol, but only check first 2 characters
                  r = 4;
            else if (ss == "la")
                  r = 5;
            else if (ss == "si")
                  r = 6;
            else
                  return Tpc::TPC_INVALID;
            }
      else {
            switch(s[0].toLower().toLatin1()) {
                  case 'c':   r = 0; break;
                  case 'd':   r = 1; break;
                  case 'e':   r = 2; break;
                  case 'f':   r = 3; break;
                  case 'g':   r = 4; break;
                  case 'a':   r = 5; break;
                  case 'b':   r = 6; break;
                  default:    return Tpc::TPC_INVALID;
                  }
            }
      r = spellings[r * 5 + alter + 2];
      return r;
      }

//---------------------------------------------------------
//   parseHarmony
//    determine root and bass tpc & case
//    compare body of chordname against chord list
//    return true if chord is recognized
//---------------------------------------------------------

const ChordDescription* Harmony::parseHarmony(const QString& ss, int* root, int* base, bool syntaxOnly)
      {
      _id = -1;
      if (_parsedForm) {
            delete _parsedForm;
            _parsedForm = 0;
            }
      _textName.clear();
      bool useLiteral = false;
      if (ss.endsWith(' '))
            useLiteral = true;

      if (_harmonyType == HarmonyType::ROMAN) {
            _userName = ss;
            _textName = ss;
            *root = Tpc::TPC_INVALID;
            *base = Tpc::TPC_INVALID;
            return 0;
            }

      // pre-process for parentheses
      QString s = ss.simplified();
      if ((_leftParen = s.startsWith('(')))
            s.remove(0,1);
      if ((_rightParen = (s.endsWith(')') && s.count('(') < s.count(')'))))
            s.remove(s.size()-1,1);
      if (_leftParen || _rightParen)
            s = s.simplified();     // in case of spaces inside parentheses
      if (s.isEmpty())
            return 0;

      // pre-process for lower case minor chords
      bool preferMinor;
      if (score()->styleB(Sid::lowerCaseMinorChords) && s[0].isLower())
            preferMinor = true;
      else
            preferMinor = false;

      if (_harmonyType == HarmonyType::NASHVILLE) {
            int n = 0;
            if (s[0].isDigit())
                  n = 1;
            else if (s[1].isDigit())
                  n = 2;
            _function = s.mid(0, n);
            s = s.mid(n);
            *root = Tpc::TPC_INVALID;
            *base = Tpc::TPC_INVALID;
            }
      else {
            determineRootBaseSpelling();
            int idx;
            int r = convertNote(s, _rootSpelling, _rootCase, idx);
            if (r == Tpc::TPC_INVALID) {
                  if (s[0] == '/')
                        idx = 0;
                  else {
                        qDebug("failed <%s>", qPrintable(ss));
                        _userName = s;
                        _textName = s;
                        return 0;
                        }
                  }
            *root = r;
            *base = Tpc::TPC_INVALID;
            int slash = s.lastIndexOf('/');
            if (slash != -1) {
                  QString bs = s.mid(slash + 1).simplified();
                  s = s.mid(idx, slash - idx).simplified();
                  int idx2;
                  *base = convertNote(bs, _baseSpelling, _baseCase, idx2);
                  if (idx2 != bs.size())
                        *base = Tpc::TPC_INVALID;
                  if (*base == Tpc::TPC_INVALID) {
                        // if what follows after slash is not (just) a TPC
                        // then reassemble chord and try to parse with the slash
                        s = s + "/" + bs;
                        }
                  }
            else
                  s = s.mid(idx);   // don't simplify; keep leading space before extension if present
            }

      _userName = s;
      const ChordList* cl = score()->style().chordList();
      const ChordDescription* cd = 0;
      if (useLiteral)
            cd = descr(s);
      else {
            _parsedForm = new ParsedChord();
            _parsedForm->parse(s, cl, syntaxOnly, preferMinor);
            // parser prepends "=" to name of implied minor chords
            // use this here as well
            if (preferMinor)
                  s = _parsedForm->name();
            // look up to see if we already have a descriptor (chord has been used before)
            cd = descr(s, _parsedForm);
            }
      if (cd) {
            // descriptor found; use its information
            _id = cd->id;
            if (!cd->names.empty())
                  _textName = cd->names.front();
            }
      else {
            // no descriptor yet; just set textname
            // we will generate descriptor later if necessary (when we are done editing this chord)
            _textName = s;
            }
      return cd;
      }

//---------------------------------------------------------
//   startEdit
//---------------------------------------------------------

void Harmony::startEdit(EditData& ed)
      {
      if (!textList.empty()) {
            // convert chord symbol to plain text
            setPlainText(harmonyName());
            // clear rendering
            for (const TextSegment* t : textList)
                  delete t;
            textList.clear();
            }

      // layout as text, without position reset
      TextBase::layout1();
      triggerLayout();

      TextBase::startEdit(ed);
      }

//---------------------------------------------------------
//   edit
//---------------------------------------------------------

bool Harmony::edit(EditData& ed)
      {
      if (ed.key == Qt::Key_Return)
            return true; // Harmony only single line

      bool rv = TextBase::edit(ed);

      // layout as text, without position reset
      TextBase::layout1();
      triggerLayout();

      // check spelling
      int root = TPC_INVALID;
      int base = TPC_INVALID;
      QString str = xmlText();
      showSpell = !str.isEmpty() && !parseHarmony(str, &root, &base, true) && root == TPC_INVALID && _harmonyType == HarmonyType::STANDARD;
      if (showSpell)
            qDebug("bad spell");

      return rv;
      }

//---------------------------------------------------------
//   endEdit
//---------------------------------------------------------

void Harmony::endEdit(EditData& ed)
      {
      // complete editing: generate xml text, set Pid::TEXT, perform initial layout
      // if text has changed, this also triggers setHarmony() which renders chord symbol
      // but any rendering or layout performed here is tenative,
      // we may still need to substitute special characters,
      // and that cannot be until after editing is completed
      TextBase::endEdit(ed);

      // get plain text
      QString s = plainText();

      // if user explicitly added symbols to the text,
      // convert them back to their respective replacement texts
      if (harmonyType() != HarmonyType::ROMAN) {
            s.replace("\u1d12b", "bb"); // double-flat
            s.replace("\u266d",  "b");  // flat
            s.replace("\ue260",  "b");  // flat
            // do not replace natural sign
            // (right now adding the symbol explicitly is the only way to force a natural sign to appear at all)
            //s.replace("\u266e",  "n");  // natural, if one day we support that too
            //s.replace("\ue261",  "n");  // natural, if one day we support that too
            s.replace("\u266f",  "#");  // sharp
            s.replace("\ue262",  "#");  // sharp
            s.replace("\u1d12a", "x");  // double-sharp
            s.replace("\u0394",  "^");  // &Delta;
            s.replace("\u00d0",  "o");  // &deg;
            s.replace("\u00f8",  "0");  // &oslash;
            s.replace("\u00d8",  "0");  // &Oslash;
            }
      else {
            s.replace("\ue260",  "\u266d");     // flat
            s.replace("\ue261",  "\u266e");     // natural
            s.replace("\ue262",  "\u266f");     // sharp
            }

      // render and layout chord symbol
      // (needs to be done here if text hasn't changed, or redone if replacemens were performed above)
      score()->startCmd();
      setHarmony(s);
      layout1();
      triggerLayout();
      score()->endCmd();

      // disable spell check
      showSpell = false;

      if (links()) {
            for (ScoreElement* e : *links()) {
                  if (e == this)
                        continue;
                  Harmony* h = toHarmony(e);
                  // transpose if necessary
                  // at this point chord will already have been rendered in same key as original
                  // (as a result of TextBase::endEdit() calling setText() for linked elements)
                  // we may now need to change the TPC's and the text, and re-render
                  if (score()->styleB(Sid::concertPitch) != h->score()->styleB(Sid::concertPitch)) {
                        Part* partDest = h->part();
                        Segment* segment = toSegment(parent());
                        Fraction tick = segment ? segment->tick() : Fraction(-1,1);
                        Interval interval = partDest->instrument(tick)->transpose();
                        if (!interval.isZero()) {
                              if (!h->score()->styleB(Sid::concertPitch))
                                    interval.flip();
                              int rootTpc = transposeTpc(h->rootTpc(), interval, true);
                              int baseTpc = transposeTpc(h->baseTpc(), interval, true);
                              //score()->undoTransposeHarmony(h, rootTpc, baseTpc);
                              h->setRootTpc(rootTpc);
                              h->setBaseTpc(baseTpc);
                              h->setPlainText(h->harmonyName());
                              h->setHarmony(h->plainText());
                              h->triggerLayout();
                              }
                        }
                  }
            }
      }

//---------------------------------------------------------
//   setHarmony
//---------------------------------------------------------

void Harmony::setHarmony(const QString& s)
      {
      int r, b;
      const ChordDescription* cd = parseHarmony(s, &r, &b);
      if (!cd && _parsedForm && _parsedForm->parseable()) {
            // our first time encountering this chord
            // generate a descriptor and use it
            cd = generateDescription();
            _id = cd->id;
            }
      if (cd) {
            setRootTpc(r);
            setBaseTpc(b);
            render();
            }
      else {
            // unparseable chord, render as plain text
            for (const TextSegment* ts : textList)
                  delete ts;
            textList.clear();
            setRootTpc(Tpc::TPC_INVALID);
            setBaseTpc(Tpc::TPC_INVALID);
            _id = -1;
            render();
            }
      }

//---------------------------------------------------------
//   baseLine
//---------------------------------------------------------

qreal Harmony::baseLine() const
      {
      return (textList.empty()) ? TextBase::baseLine() : 0.0;
      }

//---------------------------------------------------------
//   text
//---------------------------------------------------------

QString HDegree::text() const
      {
      if (_type == HDegreeType::UNDEF)
            return QString();
      const char* d = 0;
      switch(_type) {
            case HDegreeType::UNDEF: break;
            case HDegreeType::ADD:         d= "add"; break;
            case HDegreeType::ALTER:       d= "alt"; break;
            case HDegreeType::SUBTRACT:    d= "sub"; break;
            }
      QString degree(d);
      switch(_alter) {
            case -1:          degree += "b"; break;
            case 1:           degree += "#"; break;
            default:          break;
            }
      QString s = QString("%1").arg(_value);
      QString ss = degree + s;
      return ss;
      }

//---------------------------------------------------------
//   fromXml
//    lookup harmony in harmony data base
//    using musicXml "kind" string and degree list
//---------------------------------------------------------

const ChordDescription* Harmony::fromXml(const QString& kind, const QList<HDegree>& dl)
      {
      QStringList degrees;

      foreach(const HDegree& d, dl)
            degrees.append(d.text());

      QString lowerCaseKind = kind.toLower();
      const ChordList* cl = score()->style().chordList();
      foreach(const ChordDescription& cd, *cl) {
            QString k     = cd.xmlKind;
            QString lowerCaseK = k.toLower(); // required for xmlKind Tristan
            QStringList d = cd.xmlDegrees;
            if ((lowerCaseKind == lowerCaseK) && (d == degrees)) {
//                  qDebug("harmony found in db: %s %s -> %d", qPrintable(kind), qPrintable(degrees), cd->id);
                  return &cd;
                  }
            }
      return 0;
      }

//---------------------------------------------------------
//   fromXml
//    lookup harmony in harmony data base
//    using musicXml "kind" string only
//---------------------------------------------------------

const ChordDescription* Harmony::fromXml(const QString& kind)
      {
      QString lowerCaseKind = kind.toLower();
      const ChordList* cl = score()->style().chordList();
      foreach(const ChordDescription& cd, *cl) {
            if (lowerCaseKind == cd.xmlKind)
                  return &cd;
            }
      return 0;
      }

//---------------------------------------------------------
//   fromXml
//    construct harmony directly from XML
//    build name first
//    then generate chord description from that
//---------------------------------------------------------

const ChordDescription* Harmony::fromXml(const QString& kind, const QString& kindText, const QString& symbols, const QString& parens, const QList<HDegree>& dl)
      {
      ParsedChord* pc = new ParsedChord;
      _textName = pc->fromXml(kind, kindText, symbols, parens, dl, score()->style().chordList());
      _parsedForm = pc;
      const ChordDescription* cd = getDescription(_textName,pc);
      return cd;
      }

//---------------------------------------------------------
//   descr
//    look up id in chord list
//    return chord description if found, or null
//---------------------------------------------------------

const ChordDescription* Harmony::descr() const
      {
      return score()->style().chordDescription(_id);
      }

//---------------------------------------------------------
//   descr
//    look up name in chord list
//    optionally look up by parsed chord as fallback
//    return chord description if found, or null
//---------------------------------------------------------

const ChordDescription* Harmony::descr(const QString& name, const ParsedChord* pc) const
      {
      const ChordList* cl = score()->style().chordList();
      const ChordDescription* match = 0;
      if (cl) {
            foreach (const ChordDescription& cd, *cl) {
                  for (const QString& s : cd.names) {
                        if (s == name)
                              return &cd;
                        else if (pc) {
                              for (const ParsedChord& sParsed : cd.parsedChords) {
                                    if (sParsed == *pc)
                                          match = &cd;
                                    }
                              }
                        }
                  }
            }
      // exact match failed, so fall back on parsed match if one was found
      return match;
      }

//---------------------------------------------------------
//   getDescription
//    look up id in chord list
//    return chord description if found
//    if not found, and chord is parseable,
//    generate a new chord description
//    and add to chord list
//---------------------------------------------------------

const ChordDescription* Harmony::getDescription()
      {
      const ChordDescription* cd = descr();
      if (cd && !cd->names.empty())
            _textName = cd->names.front();
      else if (_textName != "") {
            cd = generateDescription();
            _id = cd->id;
            }
      return cd;
      }

//---------------------------------------------------------
//   getDescription
//    same but lookup by name and optionally parsed chord
//---------------------------------------------------------

const ChordDescription* Harmony::getDescription(const QString& name, const ParsedChord* pc)
      {
      const ChordDescription* cd = descr(name, pc);
      if (cd)
            _id = cd->id;
      else {
            cd = generateDescription();
            _id = cd->id;
            }
      return cd;
      }

//---------------------------------------------------------
//   generateDescription
//    generate new chord description from _textName
//    add to chord list using private id
//---------------------------------------------------------

const ChordDescription* Harmony::generateDescription()
      {
      ChordList* cl = score()->style().chordList();
      ChordDescription cd(_textName);
      cd.complete(_parsedForm, cl);
      // remove parsed chord from description
      // so we will only match it literally in the future
      cd.parsedChords.clear();
      return &*cl->insert(cd.id, cd);
      }

//---------------------------------------------------------
//   layout
//---------------------------------------------------------

void Harmony::layout()
      {
      if (!parent()) {
            setPos(0.0, 0.0);
            setOffset(0.0, 0.0);
            layout1();
            return;
            }
      //if (isStyled(Pid::OFFSET))
      //      setOffset(propertyDefault(Pid::OFFSET).toPointF());

      if (placeBelow())
            rypos() = staff() ? staff()->height() : 0.0;
      else
            rypos() = 0.0;
      layout1();

      qreal yy = ipos().y();
      qreal xx = 0.0;

      if (parent()->isFretDiagram()) {
            if (isStyled(Pid::ALIGN))
                  setAlign(Align::HCENTER | Align::BASELINE);
            yy = -score()->styleP(Sid::harmonyFretDist);
            }

      qreal hb = lineHeight() - TextBase::baseLine();
      if (align() & Align::BOTTOM)
            yy -= hb;
      else if (align() & Align::VCENTER) {
            yy -= hb;
            yy += (height() * .5);
            }
      else if (align() & Align::BASELINE) {
            }
      else { // Align::TOP
            yy -= hb;
            yy += height();
            }

      qreal cw = symWidth(SymId::noteheadBlack);
      if (align() & Align::RIGHT) {
            xx += cw;
            xx -= width();
            }
      else if (align() & Align::HCENTER) {
            if (parent()->isFretDiagram()) {
                  FretDiagram* fd = toFretDiagram(parent());
                  xx += fd->centerX();
                  xx -= width() * .5;
                  }
            else {
                  xx += (cw * .5);
                  xx -= (width() * .5);
                  }
            }

      setPos(xx, yy);
      }

//---------------------------------------------------------
//   layout1
//---------------------------------------------------------

void Harmony::layout1()
      {
      if (isLayoutInvalid())
            createLayout();
      if (textBlockList().empty())
            textBlockList().append(TextBlock());
      calculateBoundingRect();    // for normal symbols this is called in layout: computeMinWidth()
      if (hasFrame())
            layoutFrame();
      score()->addRefresh(canvasBoundingRect());
      }

//---------------------------------------------------------
//   calculateBoundingRect
//---------------------------------------------------------

void Harmony::calculateBoundingRect()
      {
      if (textList.empty())
            TextBase::layout1();
      else {
            QRectF bb;
            for (const TextSegment* ts : textList)
                  bb |= ts->tightBoundingRect().translated(ts->x, ts->y);
            setbbox(bb);
            for (int i = 0; i < rows(); ++i) {
                  TextBlock& t = textBlockList()[i];

                  // when MS switch to editing Harmony MS draws text defined by textBlockList().
                  // When MS switches back to normal state it draws text from textList
                  // To correct placement of text in editing we need to layout textBlockList() elements
                  t.layout(this);
                  for (auto& s : t.fragments()) {
                        s.pos = { 0, 0 };
                        }
                  }
            }
      }

//---------------------------------------------------------
//   draw
//---------------------------------------------------------

void Harmony::draw(QPainter* painter) const
      {
      // painter->setPen(curColor());
      if (textList.empty()) {
            TextBase::draw(painter);
            return;
            }
      if (hasFrame()) {
            if (frameWidth().val() != 0.0) {
                  QColor color = frameColor();
                  QPen pen(color, frameWidth().val() * spatium(), Qt::SolidLine,
                     Qt::SquareCap, Qt::MiterJoin);
                  painter->setPen(pen);
                  }
            else
                  painter->setPen(Qt::NoPen);
            QColor bg(bgColor());
            painter->setBrush(bg.alpha() ? QBrush(bg) : Qt::NoBrush);
            if (circle())
                  painter->drawArc(frame, 0, 5760);
            else {
                  int r2 = frameRound();
                  if (r2 > 99)
                        r2 = 99;
                  painter->drawRoundedRect(frame, frameRound(), r2);
                  }
            }
      painter->setBrush(Qt::NoBrush);
      QColor color = textColor();
      painter->setPen(color);
      for (const TextSegment* ts : textList) {
            QFont f(ts->font);
            f.setPointSizeF(f.pointSizeF() * MScore::pixelRatio);
            painter->setFont(f);
            painter->drawText(QPointF(ts->x, ts->y), ts->text);
            }
      }

//---------------------------------------------------------
//   drawEditMode
//---------------------------------------------------------

void Harmony::drawEditMode(QPainter* p, EditData& ed)
      {
      TextBase::drawEditMode(p, ed);

      QColor originalColor = color();
      if (showSpell) {
            setColor(QColor(Qt::red));
            setSelected(false);
            }
      QPointF pos(canvasPos());
      p->translate(pos);
      TextBase::draw(p);
      p->translate(-pos);
      if (showSpell) {
            setColor(originalColor);
            setSelected(true);
            }
      }

//---------------------------------------------------------
//   TextSegment
//---------------------------------------------------------

TextSegment::TextSegment(const QString& s, const QFont& f, qreal x, qreal y)
      {
      set(s, f, x, y);
      select = false;
      }

//---------------------------------------------------------
//   width
//---------------------------------------------------------

qreal TextSegment::width() const
      {
      QFontMetricsF fm(font, MScore::paintDevice());
#if 1
      return fm.width(text);
#else
      qreal w = 0.0;
      foreach(QChar c, text) {
            // if we calculate width by character, at least skip high surrogates
            if (c.isHighSurrogate())
                  continue;
            w += fm.width(c);
            }
      return w;
#endif
      }

//---------------------------------------------------------
//   boundingRect
//---------------------------------------------------------

QRectF TextSegment::boundingRect() const
      {
      QFontMetricsF fm(font, MScore::paintDevice());
      return fm.boundingRect(text);
      }

//---------------------------------------------------------
//   tightBoundingRect
//---------------------------------------------------------

QRectF TextSegment::tightBoundingRect() const
      {
      QFontMetricsF fm(font, MScore::paintDevice());
      return fm.tightBoundingRect(text);
      }

//---------------------------------------------------------
//   set
//---------------------------------------------------------

void TextSegment::set(const QString& s, const QFont& f, qreal _x, qreal _y)
      {
      font = f;
      x    = _x;
      y    = _y;
      setText(s);
      }

//---------------------------------------------------------
//   render
//---------------------------------------------------------

void Harmony::render(const QString& s, qreal& x, qreal& y)
      {
      int fontIdx = 0;
      if (!s.isEmpty()) {
            QFont f = _harmonyType != HarmonyType::ROMAN ? fontList[fontIdx] : font();
            TextSegment* ts = new TextSegment(s, f, x, y);
            textList.append(ts);
            x += ts->width();
            }
      }

//---------------------------------------------------------
//   render
//---------------------------------------------------------

void Harmony::render(const QList<RenderAction>& renderList, qreal& x, qreal& y, int tpc, NoteSpellingType noteSpelling, NoteCaseType noteCase)
      {
      ChordList* chordList = score()->style().chordList();
      QStack<QPointF> stack;
      int fontIdx    = 0;
      qreal _spatium = spatium();
      qreal mag      = magS();

// qDebug("===");
      for (const RenderAction& a : renderList) {
// a.print();
            if (a.type == RenderAction::RenderActionType::SET) {
                  TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
                  ChordSymbol cs = chordList->symbol(a.text);
                  if (cs.isValid()) {
                        ts->font = fontList[cs.fontIdx];
                        ts->setText(cs.value);
                        }
                  else
                        ts->setText(a.text);
                  if (_harmonyType == HarmonyType::NASHVILLE) {
                        qreal nmag = chordList->nominalMag();
                        ts->font.setPointSizeF(ts->font.pointSizeF() * nmag);
                        }
                  textList.append(ts);
                  x += ts->width();
                  }
            else if (a.type == RenderAction::RenderActionType::MOVE) {
                  x += a.movex * mag * _spatium * .2;
                  y += a.movey * mag * _spatium * .2;
                  }
            else if (a.type == RenderAction::RenderActionType::PUSH)
                  stack.push(QPointF(x,y));
            else if (a.type == RenderAction::RenderActionType::POP) {
                  if (!stack.empty()) {
                        QPointF pt = stack.pop();
                        x = pt.x();
                        y = pt.y();
                        }
                  else
                        qDebug("RenderAction::RenderActionType::POP: stack empty");
                  }
            else if (a.type == RenderAction::RenderActionType::NOTE) {
                  QString c;
                  int acc;
                  if (tpcIsValid(tpc))
                        tpc2name(tpc, noteSpelling, noteCase, c, acc);
                  else if (_function.size() > 0)
                        c = _function.at(_function.size() - 1);
                  TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
                  QString lookup = "note" + c;
                  ChordSymbol cs = chordList->symbol(lookup);
                  if (!cs.isValid())
                        cs = chordList->symbol(c);
                  if (cs.isValid()) {
                        ts->font = fontList[cs.fontIdx];
                        ts->setText(cs.value);
                        }
                  else {
                        ts->setText(c);
                        }
                  textList.append(ts);
                  x += ts->width();
                  }
            else if (a.type == RenderAction::RenderActionType::ACCIDENTAL) {
                  QString c;
                  QString acc;
                  QString context = "accidental";
                  if (tpcIsValid(tpc))
                        tpc2name(tpc, noteSpelling, noteCase, c, acc);
                  else if (_function.size() > 1)
                        acc = _function.at(0);
                  // German spelling - use special symbol for accidental in TPC_B_B
                  // to allow it to be rendered as either Bb or B
                  if (tpc == Tpc::TPC_B_B && noteSpelling == NoteSpellingType::GERMAN)
                        context = "german_B";
                  if (acc != "") {
                        TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
                        QString lookup = context + acc;
                        ChordSymbol cs = chordList->symbol(lookup);
                        if (!cs.isValid())
                              cs = chordList->symbol(acc);
                        if (cs.isValid()) {
                              ts->font = fontList[cs.fontIdx];
                              ts->setText(cs.value);
                              }
                        else
                              ts->setText(acc);
                        textList.append(ts);
                        x += ts->width();
                        }
                  }
            else
                  qDebug("unknown render action %d", static_cast<int>(a.type));
            }
      }

//---------------------------------------------------------
//   render
//    construct Chord Symbol
//---------------------------------------------------------

void Harmony::render()
      {
      int capo = score()->styleI(Sid::capoPosition);

      ChordList* chordList = score()->style().chordList();

      fontList.clear();
      for (const ChordFont& cf : chordList->fonts) {
            QFont ff(font());
            ff.setPointSizeF(ff.pointSizeF() * cf.mag);
            if (!(cf.family.isEmpty() || cf.family == "default"))
                  ff.setFamily(cf.family);
            fontList.append(ff);
            }
      if (fontList.empty())
            fontList.append(font());

      for (const TextSegment* s : textList)
            delete s;
      textList.clear();
      qreal x = 0.0, y = 0.0;

      determineRootBaseSpelling();

      if (_leftParen)
            render("( ", x, y);

      if (_rootTpc != Tpc::TPC_INVALID) {
            // render root
            render(chordList->renderListRoot, x, y, _rootTpc, _rootSpelling, _rootRenderCase);
            // render extension
            const ChordDescription* cd = getDescription();
            if (cd)
                  render(cd->renderList, x, y, 0);
            }
      else if (_harmonyType == HarmonyType::NASHVILLE) {
            // render function
            render(chordList->renderListFunction, x, y, _rootTpc, _rootSpelling, _rootRenderCase);
            qreal adjust = chordList->nominalAdjust();
            y += adjust * magS() * spatium() * .2;
            // render extension
            const ChordDescription* cd = getDescription();
            if (cd)
                  render(cd->renderList, x, y, 0);
            }
      else {
            render(_textName, x, y);
            }

      // render bass
      if (_baseTpc != Tpc::TPC_INVALID)
            render(chordList->renderListBase, x, y, _baseTpc, _baseSpelling, _baseRenderCase);

      if (_rootTpc != Tpc::TPC_INVALID && capo > 0 && capo < 12) {
            int tpcOffset[] = { 0, 5, -2, 3, -4, 1, 6, -1, 4, -3, 2, -5 };
            int capoRootTpc = _rootTpc + tpcOffset[capo];
            int capoBassTpc = _baseTpc;

            if (capoBassTpc != Tpc::TPC_INVALID)
                  capoBassTpc += tpcOffset[capo];

            /*
             * For guitarists, avoid x and bb in Root or Bass,
             * and also avoid E#, B#, Cb and Fb in Root.
             */
            if (capoRootTpc < 8 || (capoBassTpc != Tpc::TPC_INVALID && capoBassTpc < 6)) {
                  capoRootTpc += 12;
                  if (capoBassTpc != Tpc::TPC_INVALID)
                        capoBassTpc += 12;
                  }
            else if (capoRootTpc > 24 || (capoBassTpc != Tpc::TPC_INVALID && capoBassTpc > 26)) {
                  capoRootTpc -= 12;
                  if (capoBassTpc != Tpc::TPC_INVALID)
                        capoBassTpc -= 12;
                  }

            render("(", x, y);
            render(chordList->renderListRoot, x, y, capoRootTpc, _rootSpelling, _rootRenderCase);

            // render extension
            const ChordDescription* cd = getDescription();
            if (cd)
                  render(cd->renderList, x, y, 0);

            if (capoBassTpc != Tpc::TPC_INVALID)
                  render(chordList->renderListBase, x, y, capoBassTpc, _baseSpelling, _baseRenderCase);
            render(")", x, y);
            }

      if (_rightParen)
            render(" )", x, y);
      }

//---------------------------------------------------------
//   spatiumChanged
//---------------------------------------------------------

void Harmony::spatiumChanged(qreal oldValue, qreal newValue)
      {
      TextBase::spatiumChanged(oldValue, newValue);
      render();
      }

//---------------------------------------------------------
//   localSpatiumChanged
//---------------------------------------------------------

void Harmony::localSpatiumChanged(qreal oldValue, qreal newValue)
      {
      TextBase::localSpatiumChanged(oldValue, newValue);
      render();
      }

//---------------------------------------------------------
//   extensionName
//---------------------------------------------------------

const QString& Harmony::extensionName() const
      {
      return _textName;
      }

//---------------------------------------------------------
//   xmlKind
//---------------------------------------------------------

QString Harmony::xmlKind() const
      {
      const ChordDescription* cd = descr();
      return cd ? cd->xmlKind : QString();
      }

//---------------------------------------------------------
//   musicXmlText
//---------------------------------------------------------

QString Harmony::musicXmlText() const
      {
      const ChordDescription* cd = descr();
      return cd ? cd->xmlText : QString();
      }

//---------------------------------------------------------
//   xmlSymbols
//---------------------------------------------------------

QString Harmony::xmlSymbols() const
      {
      const ChordDescription* cd = descr();
      return cd ? cd->xmlSymbols : QString();
      }

//---------------------------------------------------------
//   xmlParens
//---------------------------------------------------------

QString Harmony::xmlParens() const
      {
      const ChordDescription* cd = descr();
      return cd ? cd->xmlParens : QString();
      }

//---------------------------------------------------------
//   xmlDegrees
//---------------------------------------------------------

QStringList Harmony::xmlDegrees() const
      {
      const ChordDescription* cd = descr();
      return cd ? cd->xmlDegrees : QStringList();
      }

//---------------------------------------------------------
//   degree
//---------------------------------------------------------

HDegree Harmony::degree(int i) const
      {
      return _degreeList.value(i);
      }

//---------------------------------------------------------
//   addDegree
//---------------------------------------------------------

void Harmony::addDegree(const HDegree& d)
      {
      _degreeList << d;
      }

//---------------------------------------------------------
//   numberOfDegrees
//---------------------------------------------------------

int Harmony::numberOfDegrees() const
      {
      return _degreeList.size();
      }

//---------------------------------------------------------
//   clearDegrees
//---------------------------------------------------------

void Harmony::clearDegrees()
      {
      _degreeList.clear();
      }

//---------------------------------------------------------
//   degreeList
//---------------------------------------------------------

const QList<HDegree>& Harmony::degreeList() const
      {
      return _degreeList;
      }

//---------------------------------------------------------
//   parsedForm
//---------------------------------------------------------

const ParsedChord* Harmony::parsedForm()
      {
      if (!_parsedForm) {
            ChordList* cl = score()->style().chordList();
            _parsedForm = new ParsedChord();
            _parsedForm->parse(_textName, cl, false);
            }
      return _parsedForm;
      }

//---------------------------------------------------------
//   setHarmonyType
//---------------------------------------------------------

void Harmony::setHarmonyType(HarmonyType val)
      {
      _harmonyType = val;
      setPlacement(Placement(propertyDefault(Pid::PLACEMENT).toInt()));
      switch (_harmonyType) {
            case HarmonyType::STANDARD:
                  initTid(Tid::HARMONY_A);
                  break;
            case HarmonyType::ROMAN:
                  initTid(Tid::HARMONY_ROMAN);
                  break;
            case HarmonyType::NASHVILLE:
                  initTid(Tid::HARMONY_NASHVILLE);
                  break;
            }
      // TODO: convert text
      }

//---------------------------------------------------------
//   userName
//---------------------------------------------------------

QString Harmony::userName() const
      {
      switch (_harmonyType) {
            case HarmonyType::ROMAN:
                  return QObject::tr("Roman numeral");
            case HarmonyType::NASHVILLE:
                  return QObject::tr("Nashville number");
            case HarmonyType::STANDARD:
                  break;
            }
      return Element::userName();
      }

//---------------------------------------------------------
//   accessibleInfo
//---------------------------------------------------------

QString Harmony::accessibleInfo() const
      {
      return QString("%1: %2").arg(userName()).arg(harmonyName());
      }

//---------------------------------------------------------
//   screenReaderInfo
//---------------------------------------------------------

QString Harmony::screenReaderInfo() const
      {
      QString rez = Element::accessibleInfo();
      if (_rootTpc != Tpc::TPC_INVALID)
            rez = QString("%1 %2").arg(rez).arg(tpc2name(_rootTpc, NoteSpellingType::STANDARD, NoteCaseType::AUTO, true));

      if (const_cast<Harmony*>(this)->parsedForm() && !hTextName().isEmpty()) {
            QString aux = const_cast<Harmony*>(this)->parsedForm()->handle();
            aux = aux.replace("#", QObject::tr("♯")).replace("<", "");
            QString extension = "";

            foreach (QString s, aux.split(">", QString::SkipEmptyParts)) {
                  if(!s.contains("blues"))
                        s.replace("b", QObject::tr("♭"));
                  extension += s + " ";
                  }
            rez = QString("%1 %2").arg(rez).arg(extension);
            }
      else {
            rez = QString("%1 %2").arg(rez).arg(hTextName());
            }

      if (_baseTpc != Tpc::TPC_INVALID)
            rez = QString("%1 / %2").arg(rez).arg(tpc2name(_baseTpc, NoteSpellingType::STANDARD, NoteCaseType::AUTO, true));

      return rez;
      }

//---------------------------------------------------------
//   acceptDrop
//---------------------------------------------------------

bool Harmony::acceptDrop(EditData& data) const
      {
      Element* e = data.dropElement;
      if (e->isFretDiagram()) {
            return true;
            }
      else if (e->isSymbol() || e->isFSymbol()) {
            // symbols can be added in edit mode
            if (data.getData(this))
                  return true;
            else
                  return false;
            }
      return false;
      }

//---------------------------------------------------------
//   drop
//---------------------------------------------------------

Element* Harmony::drop(EditData& data)
      {
      Element* e = data.dropElement;
      if (e->isFretDiagram()) {
            FretDiagram* fd = toFretDiagram(e);
            fd->setParent(parent());
            fd->setTrack(track());
            score()->undoAddElement(fd);
            }
      else if (e->isSymbol() || e->isFSymbol()) {
            TextBase::drop(data);
            layout1();
            e = 0;      // cannot select
            }
      else {
            qWarning("Harmony: cannot drop <%s>\n", e->name());
            delete e;
            e = 0;
            }
      return e;
      }

//---------------------------------------------------------
//   getProperty
//---------------------------------------------------------

QVariant Harmony::getProperty(Pid pid) const
      {
      if (pid == Pid::HARMONY_TYPE)
            return QVariant(int(_harmonyType));
      return TextBase::getProperty(pid);
      }

//---------------------------------------------------------
//   setProperty
//---------------------------------------------------------

bool Harmony::setProperty(Pid pid, const QVariant& v)
      {
      if (pid == Pid::HARMONY_TYPE) {
            setHarmonyType(HarmonyType(v.toInt()));
            return true;
            }
      else if (TextBase::setProperty(pid, v)) {
            if (pid == Pid::TEXT)
                  setHarmony(plainText());
            render();
            return true;
            }
      return false;
      }

//---------------------------------------------------------
//   propertyDefault
//---------------------------------------------------------

QVariant Harmony::propertyDefault(Pid id) const
      {
      QVariant v;
      switch (id) {
            case Pid::HARMONY_TYPE:
                  v = int(HarmonyType::STANDARD);
                  break;
            case Pid::SUB_STYLE: {
                  switch (_harmonyType) {
                        case HarmonyType::STANDARD:
                              v = int(Tid::HARMONY_A);
                              break;
                        case HarmonyType::ROMAN:
                              v = int(Tid::HARMONY_ROMAN);
                              break;
                        case HarmonyType::NASHVILLE:
                              v = int(Tid::HARMONY_NASHVILLE);
                              break;
                        }
                  }
                  break;
            case Pid::OFFSET:
                  if (parent() && parent()->isFretDiagram()) {
                        v = QVariant(QPointF(0.0, 0.0));
                        break;
                        }
                  // fall-through
            default:
                  v = TextBase::propertyDefault(id);
                  break;
            }
      return v;
      }

//---------------------------------------------------------
//   getPropertyStyle
//---------------------------------------------------------

Sid Harmony::getPropertyStyle(Pid pid) const
      {
      if (pid == Pid::OFFSET) {
            if (parent() && parent()->isFretDiagram())
                  return Sid::NOSTYLE;
            else if (tid() == Tid::HARMONY_A)
                  return placeAbove() ? Sid::chordSymbolAPosAbove : Sid::chordSymbolAPosBelow;
            else if (tid() == Tid::HARMONY_B)
                  return placeAbove() ? Sid::chordSymbolBPosAbove : Sid::chordSymbolBPosBelow;
            else if (tid() == Tid::HARMONY_ROMAN)
                  return placeAbove() ? Sid::romanNumeralPosAbove : Sid::romanNumeralPosBelow;
            else if (tid() == Tid::HARMONY_NASHVILLE)
                  return placeAbove() ? Sid::nashvilleNumberPosAbove : Sid::nashvilleNumberPosBelow;
            }
      if (pid == Pid::PLACEMENT) {
            switch (_harmonyType) {
                  case HarmonyType::STANDARD:
                        return Sid::harmonyPlacement;
                  case HarmonyType::ROMAN:
                        return Sid::romanNumeralPlacement;
                  case HarmonyType::NASHVILLE:
                        return Sid::nashvilleNumberPlacement;
                  }
            }
      return TextBase::getPropertyStyle(pid);
      }

}
