    //=============================================================================
    //  MuseScore
    //  Music Composition & Notation
    //
    //  Copyright (C) 2002-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 "lyrics.h"

    #include "chord.h"
    #include "score.h"
    #include "sym.h"
    #include "system.h"
    #include "xml.h"
    #include "staff.h"
    #include "segment.h"

    namespace Ms {

    // some useful values:
    static const qreal HALF  = 0.5;
    static const qreal TWICE = 2.0;

    //---------------------------------------------------------
    //   searchNextLyrics
    //---------------------------------------------------------

    static Lyrics* searchNextLyrics(Segment* s, int staffIdx, int verse, Element::Placement p)
          {
          Lyrics* l = 0;
          while ((s = s->next1(SegmentType::ChordRest))) {
                int strack = staffIdx * VOICES;
                int etrack = strack + VOICES;
                // search through all tracks of current staff looking for a lyric in specified verse
                for (int track = strack; track < etrack; ++track) {
                      ChordRest* cr = toChordRest(s->element(track));
                      if (cr) {
                            // cr with lyrics found, but does it have a syllable in specified verse?
                            l = cr->lyrics(verse, p);
                            if (l)
                                  break;
                            }
                      }
                if (l)
                      break;
                }
          return l;
          }

    //---------------------------------------------------------
    //   Lyrics
    //---------------------------------------------------------

    Lyrics::Lyrics(Score* s)
       : Text(SubStyle::LYRIC1, s)
          {
          _no         = 0;
          _ticks      = 0;
          _syllabic   = Syllabic::SINGLE;
          _separator  = 0;
          placementStyle = PropertyFlags::STYLED;
          setPlacement(Placement(s->styleI(StyleIdx::lyricsPlacement)));
          }

    Lyrics::Lyrics(const Lyrics& l)
       : Text(l)
          {
          _no        = l._no;
          _ticks     = l._ticks;
          _syllabic  = l._syllabic;
          _separator = 0;
          }

    Lyrics::~Lyrics()
          {
          if (_separator)
                remove(_separator);
          }

    //---------------------------------------------------------
    //   scanElements
    //---------------------------------------------------------

    void Lyrics::scanElements(void* data, void (*func)(void*, Element*), bool /*all*/)
          {
          func(data, this);
    /* DO NOT ADD EITHER THE LYRICSLINE OR THE SEGMENTS: segments are added through the system each belongs to;
          LyricsLine is not needed, as it is internally manged.
          if (_separator)
                _separator->scanElements(data, func, all); */
          }

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

    void Lyrics::write(XmlWriter& xml) const
          {
          if (!xml.canWrite(this))
                return;
          xml.stag("Lyrics");
          writeProperty(xml, P_ID::VERSE);
          if (_syllabic != Syllabic::SINGLE) {
                static const char* sl[] = {
                      "single", "begin", "end", "middle"
                      };
                xml.tag("syllabic", sl[int(_syllabic)]);
                }
          writeProperty(xml, P_ID::LYRIC_TICKS);

          Text::writeProperties(xml);
          xml.etag();
          }

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

    void Lyrics::read(XmlReader& e)
          {
          int   iEndTick = 0;           // used for backward compatibility
          Text* _verseNumber = 0;

          while (e.readNextStartElement()) {
                const QStringRef& tag(e.name());
                if (tag == "no")
                      _no = e.readInt();
                else if (tag == "syllabic") {
                      QString val(e.readElementText());
                      if (val == "single")
                            _syllabic = Syllabic::SINGLE;
                      else if (val == "begin")
                            _syllabic = Syllabic::BEGIN;
                      else if (val == "end")
                            _syllabic = Syllabic::END;
                      else if (val == "middle")
                            _syllabic = Syllabic::MIDDLE;
                      else
                            qDebug("bad syllabic property");
                      }
                else if (tag == "endTick") {          // obsolete
                      // store <endTick> tag value until a <ticks> tag has been read
                      // which positions this lyrics element in the score
                      iEndTick = e.readInt();
                      }
                else if (tag == "ticks")
                      _ticks = e.readInt();
                else if (tag == "Number") {                           // obsolete
                      _verseNumber = new Text(score());
                      _verseNumber->read(e);
                      _verseNumber->setParent(this);
                      }
                else if (tag == "placement") {
                      placementStyle = PropertyFlags::UNSTYLED;
                      Text::readProperties(e);
                      }
                else if (!Text::readProperties(e))
                      e.unknown();
                }
          // if any endTick, make it relative to current tick
          if (iEndTick) {
                _ticks = iEndTick - e.tick();
                // qDebug("Lyrics::endTick: %d  ticks %d", iEndTick, _ticks);
                }
          if (_verseNumber) {
                // TODO: add text to main text
                }

          delete _verseNumber;
          }

    //---------------------------------------------------------
    //   add
    //---------------------------------------------------------

    void Lyrics::add(Element* el)
          {
          el->setParent(this);
          if (el->type() == ElementType::LINE)
    //            _separator.append((Line*)el);           // ignore! Internally managed
                ;
          else
                qDebug("Lyrics::add: unknown element %s", el->name());
          }

    //---------------------------------------------------------
    //   remove
    //---------------------------------------------------------

    void Lyrics::remove(Element* el)
          {
          if (el->isLyricsLine()) {
                // only if separator still exists and is the right one
                if (_separator != nullptr && el == _separator) {
                      // Lyrics::remove() and LyricsLine::removeUnmanaged() call each other;
                      // be sure each finds a clean context
                      LyricsLine* separ = _separator;
                      _separator = 0;
                      separ->setParent(0);
                      separ->removeUnmanaged();
                      delete separ;
                      }
                }
          else
                qDebug("Lyrics::remove: unknown element %s", el->name());
          }

    //---------------------------------------------------------
    //   isMelisma
    //---------------------------------------------------------

    bool Lyrics::isMelisma() const
          {
          // entered as melisma using underscore?
          if (_ticks > 0)
                return true;

          // hyphenated?
          // if so, it is a melisma only if there is no lyric in same verse on next CR
          if (_syllabic == Syllabic::BEGIN || _syllabic == Syllabic::MIDDLE) {
                // find next CR on same track and check for existence of lyric in same verse
                ChordRest* cr  = chordRest();
                Segment* s     = cr->segment()->next1();
                ChordRest* ncr = s ? s->nextChordRest(cr->track()) : 0;
                if (ncr && !ncr->lyrics(_no, placement()))
                      return true;
                }

          // default - not a melisma
          return false;
          }

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

    void Lyrics::layout()
          {
          layout1();
          adjustReadPos();
          }

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

    #if defined (USE_FONT_DASH_METRIC)
          static QString    g_fontFamily      = QString();
          static qreal      g_fontSize        = -1;
          static qreal      g_cachedDashY;
          static qreal      g_cachedDashLength;
       #if defined(USE_FONT_DASH_TICKNESS)
          static qreal      g_cachedDashThickness;
       #endif
    #endif

    void Lyrics::layout1()
          {
          setPos(QPointF());
          Text::layout1();
          if (!parent()) // palette & clone trick
              return;

          qreal lh = lineSpacing() * score()->styleD(StyleIdx::lyricsLineHeight);
          qreal y = 0;

          if (placeBelow())
    //            y  = lh * (_no+1) + score()->styleP(StyleIdx::lyricsPosBelow) + staff()->height();
                y  = lh * _no + score()->styleP(StyleIdx::lyricsPosBelow) + staff()->height();
          else {
                // we are counting _no from bottom to top for verses above
                y = -lh * _no + score()->styleP(StyleIdx::lyricsPosAbove);
                }

          qreal x  = 0.0;

          //
          // parse leading verse number and/or punctuation, so we can factor it into layout separately
          //
          bool hasNumber     = false; // _verseNumber;
          qreal centerAdjust = 0.0;
          qreal leftAdjust   = 0.0;
          QString s          = plainText();

          // find:
          // 1) string of numbers and non-word characters at start of syllable
          // 2) at least one other character (indicating start of actual lyric)
          // 3) string of non-word characters at end of syllable
          //QRegularExpression leadingPattern("(^[\\d\\W]+)([^\\d\\W]+)");

          if (score()->styleB(StyleIdx::lyricsAlignVerseNumber)) {
                QRegularExpression punctuationPattern("(^[\\d\\W]*)([^\\d\\W].*?)([\\d\\W]*$)", QRegularExpression::UseUnicodePropertiesOption);
                QRegularExpressionMatch punctuationMatch = punctuationPattern.match(s);
                if (punctuationMatch.hasMatch()) {
                      // leading and trailing punctuation
                      QString lp = punctuationMatch.captured(1);
                      QString tp = punctuationMatch.captured(3);
                      // actual lyric
                      //QString actualLyric = punctuationMatch.captured(2);
                      Text leading(*this);
                      leading.setPlainText(lp);
                      leading.layout1();
                      Text trailing(*this);
                      trailing.setPlainText(tp);
                      trailing.layout1();
                      leftAdjust = leading.width();
                      centerAdjust = leading.width() - trailing.width();
                      if (!lp.isEmpty() && lp[0].isDigit())
                            hasNumber = true;
                      }
                }

          ChordRest* cr = chordRest();
          Align ta = align();
          if (ta & Align::HCENTER) {
                //
                // center under notehead, not origin
                // however, lyrics that are melismas or have verse numbers will be forced to left alignment
                // TODO: provide a way to disable the automatic left alignment
                //
                qreal nominalWidth = symWidth(SymId::noteheadBlack);
                if (!isMelisma() && !hasNumber)     // center under notehead
                      x += nominalWidth * .5 - cr->x() - centerAdjust * 0.5;
                else                                // force left alignment
                      x += width() * .5 - cr->x() - leftAdjust;
                }
          else if (!(ta & Align::RIGHT)) {
                // even for left aligned syllables, ignore leading verse numbers and/or punctuation
                x -= leftAdjust;
                }

          rxpos() += x;
          rypos() += y;

          if (_ticks > 0 || _syllabic == Syllabic::BEGIN || _syllabic == Syllabic::MIDDLE) {
                if (_separator == nullptr) {
                      _separator = new LyricsLine(score());
                      _separator->setTick(cr->tick());
                      score()->addUnmanagedSpanner(_separator);
                      }
                _separator->setParent(this);
                _separator->setTick(cr->tick());
                _separator->setTrack(track());
                _separator->setTrack2(track());
    #if defined(USE_FONT_DASH_METRIC)
                // if font parameters different from font cached values, compute new dash values from font metrics
                if (textStyle().family() != g_fontFamily && textStyle().size() != g_fontSize) {
                      QFontMetricsF     fm    = textStyle().fontMetrics(spatium());
                      QRectF            r     = fm.tightBoundingRect("\u2013");   // U+2013 EN DASH
                      g_cachedDashY           = _dashY          = r.y() + (r.height() * HALF);
                      g_cachedDashLength      = _dashLength     = r.width();
       #if defined(USE_FONT_DASH_TICKNESS)
                      g_cachedDashThickness   = _dashThickness  = r.height();
       #endif
                      g_fontFamily            = textStyle().family();
                      g_fontSize              = textStyle().size();
                      }
                // if same font, use cached values
                else {
                      _dashY                  = g_cachedDashY;
                      _dashLength             = g_cachedDashLength;
       #if defined(USE_FONT_DASH_TICKNESS)
                      _dashThickness          = g_cachedDashThickness;
       #endif
                      }
    #endif
                }
          else {
                if (_separator) {
                      _separator->removeUnmanaged();
                      delete _separator;
                      _separator = nullptr;
                      }
                }
          }

    //---------------------------------------------------------
    //   paste
    //---------------------------------------------------------

    void Lyrics::paste(EditData& /*ed*/)
          {
    #if 0 // TODO
          MuseScoreView* scoreview = ed.view;
    #if defined(Q_OS_MAC) || defined(Q_OS_WIN)
          QClipboard::Mode mode = QClipboard::Clipboard;
    #else
          QClipboard::Mode mode = QClipboard::Selection;
    #endif
          QString txt = QApplication::clipboard()->text(mode);
          QStringList sl = txt.split(QRegExp("\\s+"), QString::SkipEmptyParts);
          if (sl.empty())
                return;

          QStringList hyph = sl[0].split("-");
          bool minus = false;
          bool underscore = false;
          if(hyph.length() > 1) {
                insertText(hyph[0]);
                hyph.removeFirst();
                sl[0] =  hyph.join("-");
                minus = true;
                }
          else if (sl.length() > 1 && sl[1] == "-") {
                insertText(sl[0]);
                sl.removeFirst();
                sl.removeFirst();
                minus = true;
                }
          else if (sl[0].startsWith("_")) {
                sl[0].remove(0, 1);
                if (sl[0].isEmpty())
                      sl.removeFirst();
                underscore = true;
                }
          else if (sl[0].contains("_")) {
                int p = sl[0].indexOf("_");
                insertText(sl[0].left(p));
                sl[0] = sl[0].mid(p + 1);
                if (sl[0].isEmpty())
                      sl.removeFirst();
                underscore = true;
                }
          else if (sl.length() > 1 && sl[1] == "_") {
                insertText(sl[0]);
                sl.removeFirst();
                sl.removeFirst();
                underscore = true;
                }
          else {
                insertText(sl[0]);
                sl.removeFirst();
                }

          layout();
          score()->setLayoutAll();
          score()->update();
          txt = sl.join(" ");

          QApplication::clipboard()->setText(txt, mode);
          if (minus)
                scoreview->lyricsMinus();
          else if (underscore)
                scoreview->lyricsUnderscore();
          else
                scoreview->lyricsTab(false, false, true);
    #endif
          }

    //---------------------------------------------------------
    //   endTick
    //---------------------------------------------------------

    int Lyrics::endTick() const
          {
          return segment()->tick() + ticks();
          }

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

    bool Lyrics::acceptDrop(EditData& data) const
          {
          return data.element->isText() || Text::acceptDrop(data);
          }

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

    Element* Lyrics::drop(EditData& data)
          {
          ElementType type = data.element->type();
          if (type == ElementType::SYMBOL || type == ElementType::FSYMBOL) {
                Text::drop(data);
                return 0;
                }
          if (!data.element->isText()) {
                delete data.element;
                return 0;
                }
          Text* e = toText(data.element);
          e->setParent(this);
          score()->undoAddElement(e);
          return e;
          }

    //---------------------------------------------------------
    //   setNo
    //---------------------------------------------------------

    void Lyrics::setNo(int n)
          {
          _no = n;
          // adjust beween LYRICS1 and LYRICS2 only; keep other styles as they are
          // (_no is 0-based, so odd _no means even line and viceversa)
          if (type() == ElementType::LYRICS) {
                if ((_no & 1) && subStyle() == SubStyle::LYRIC1)
                      initSubStyle(SubStyle::LYRIC2);
                if (!(_no & 1) && subStyle() == SubStyle::LYRIC2)
                      initSubStyle(SubStyle::LYRIC1);
                }
          }

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

    void Lyrics::endEdit(EditData& ed)
          {
          Text::endEdit(ed);
          score()->setLayoutAll();
          }

    //---------------------------------------------------------
    //   removeFromScore
    //---------------------------------------------------------

    void Lyrics::removeFromScore()
          {
          if (_separator) {
                _separator->removeUnmanaged();
                delete _separator;
                _separator = nullptr;
                }
          }

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

    QVariant Lyrics::getProperty(P_ID propertyId) const
          {
          switch (propertyId) {
                case P_ID::SYLLABIC:
                      return int(_syllabic);
                case P_ID::LYRIC_TICKS:
                      return _ticks;
                case P_ID::VERSE:
                      return _no;
                default:
                      return Text::getProperty(propertyId);
                }
          }

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

    bool Lyrics::setProperty(P_ID propertyId, const QVariant& v)
          {
          switch (propertyId) {
                case P_ID::PLACEMENT:
                      placementStyle = PropertyFlags::UNSTYLED;
                      setPlacement(Placement(v.toInt()));
                      break;
                case P_ID::SYLLABIC:
                      _syllabic = Syllabic(v.toInt());
                      break;
                case P_ID::LYRIC_TICKS:
                      _ticks = v.toInt();
                      break;
                case P_ID::VERSE:
                      _no = v.toInt();
                      break;
                default:
                      if (!Text::setProperty(propertyId, v))
                            return false;
                      break;
                }
          score()->setLayout(tick());
          return true;
          }

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

    QVariant Lyrics::propertyDefault(P_ID id) const
          {
          switch (id) {
                case P_ID::SUB_STYLE:
                      return int(SubStyle::LYRIC1);
                case P_ID::PLACEMENT:
                      return score()->styleI(StyleIdx::lyricsPlacement);
                case P_ID::SYLLABIC:
                      return int(Syllabic::SINGLE);
                case P_ID::LYRIC_TICKS:
                case P_ID::VERSE:
                      return 0;
                default: return Text::propertyDefault(id);
                }
          }

    //=========================================================
    //   LyricsLine
    //=========================================================

    LyricsLine::LyricsLine(Score* s)
      : SLine(s)
          {
          setFlags(0);

          setGenerated(true);           // no need to save it, as it can be re-generated
          setDiagonal(false);
          setLineWidth(Spatium(Lyrics::LYRICS_DASH_DEFAULT_LINE_THICKNESS));
          setAnchor(Spanner::Anchor::SEGMENT);
          _nextLyrics = 0;
          }

    LyricsLine::LyricsLine(const LyricsLine& g)
       : SLine(g)
          {
          _nextLyrics = 0;
          }

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

    void LyricsLine::layout()
          {
          bool tempMelismaTicks = (lyrics()->ticks() == Lyrics::TEMP_MELISMA_TICKS);
          if (lyrics()->ticks()) {              // melisma
                setLineWidth(score()->styleS(StyleIdx::lyricsLineThickness));
                // if lyrics has a temporary one-chord melisma, set to 0 ticks (just its own chord)
                if (tempMelismaTicks)
                      lyrics()->setTicks(0);
                // Lyrics::_ticks points to the beginning of the last spanned segment,
                // but the line shall include it:
                // include the duration of this last segment in the melisma duration
                Segment* lyricsSegment = lyrics()->segment();
                int lyricsStartTick = lyricsSegment->tick();
                int lyricsEndTick = lyrics()->endTick();
                int lyricsTrack = lyrics()->track();
                // find segment with tick >= endTick
                Segment* s = lyricsSegment;
                while (s && s->tick() < lyricsEndTick)
                      s = s->nextCR(lyricsTrack, true);
                if (!s) {
                      // user probably deleted measures at end of score, leaving this melisma too long
                      // set s to last segment and reset lyricsEndTick to trigger FIXUP code below
                      s = score()->lastSegment();
                      lyricsEndTick = -1;
                      }
                Element* se = s->element(lyricsTrack);
                // everything is OK if we have reached a chord at right tick on right track
                if (s->tick() == lyricsEndTick && se && se->type() == ElementType::CHORD) {
                      // advance to next CR, or last segment if no next CR
                      s = s->nextCR(lyricsTrack, true);
                      if (!s)
                            s = score()->lastSegment();
                      }
                else {
                      // FIXUP - lyrics tick count not valid
                      // this happens if edits to score have removed the original end segment
                      // so let's fix it here
                      // s is already pointing to segment past endTick (or to last segment)
                      // we should shorten the lyrics tick count to make this work
                      Segment* ns = s;
                      Segment* ps = s->prev1(SegmentType::ChordRest);
                      while (ps && ps != lyricsSegment) {
                            Element* pe = ps->element(lyricsTrack);
                            // we're looking for an actual chord on this track
                            if (pe && pe->type() == ElementType::CHORD)
                                  break;
                            s = ps;
                            ps = ps->prev1(SegmentType::ChordRest);
                            }
                      if (!ps || ps == lyricsSegment) {
                            // no valid previous CR, so try to lengthen melisma instead
                            ps = ns;
                            s = ps->nextCR(lyricsTrack, true);
                            Element* e = s ? s->element(lyricsTrack) : nullptr;
                            // check to make sure we have a chord
                            if (!e || e->type() != ElementType::CHORD) {
                                  // nothing to do but set ticks to 0
                                  // this will result in melisma being deleted later
                                  lyrics()->undoChangeProperty(P_ID::LYRIC_TICKS, 0);
                                  setTicks(0);
                                  return;
                                  }
                            }
                      lyrics()->undoChangeProperty(P_ID::LYRIC_TICKS, ps->tick() - lyricsStartTick);
                      }
                setTicks(s->tick() - lyricsStartTick);
                }
          else {                                    // dash(es)
    #if defined(USE_FONT_DASH_TICKNESS)
                setLineWidth(Spatium(lyrics()->dashThickness() / spatium()));
    #endif
                _nextLyrics = searchNextLyrics(lyrics()->segment(), staffIdx(), lyrics()->no(), lyrics()->placement());
                setTick2(_nextLyrics ? _nextLyrics->segment()->tick() : tick());
                }
          if (ticks()) {                // only do layout if some time span
                // do layout with non-0 duration
                if (tempMelismaTicks)
                      lyrics()->setTicks(Lyrics::TEMP_MELISMA_TICKS);
                SLine::layout();
                // if temp melisma and there is a first line segment,
                // extend it to be after the lyrics syllable (otherwise
                // the melisma segment will be often covered by the syllable itself)
                if (tempMelismaTicks && segments.size() > 0)
                      segmentAt(0)->rxpos2() += lyrics()->width();
                }
          }

    //---------------------------------------------------------
    //   createLineSegment
    //---------------------------------------------------------

    LineSegment* LyricsLine::createLineSegment()
          {
          LyricsLineSegment* seg = new LyricsLineSegment(score());
          seg->setTrack(track());
          seg->setColor(color());
          return seg;
          }

    //---------------------------------------------------------
    //   removeUnmanaged
    //    same as Spanner::removeUnmanaged(), but in addition, remove from hosting Lyrics
    //---------------------------------------------------------

    void LyricsLine::removeUnmanaged()
          {
          Spanner::removeUnmanaged();
          if (lyrics())
                lyrics()->remove(this);
          }

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

    bool LyricsLine::setProperty(P_ID propertyId, const QVariant& v)
          {
          switch (propertyId) {
                case P_ID::SPANNER_TICKS:
                      {
                      // if parent lyrics has a melisma, change its length too
                      if (parent() && parent()->type() == ElementType::LYRICS
                                  && static_cast<Lyrics*>(parent())->ticks() > 0) {
                            int newTicks   = static_cast<Lyrics*>(parent())->ticks() + v.toInt() - ticks();
                            parent()->undoChangeProperty(P_ID::LYRIC_TICKS, newTicks);
                            }
                      setTicks(v.toInt());
                      }
                      break;
                default:
                      if (!SLine::setProperty(propertyId, v))
                            return false;
                      break;
                }
          score()->setLayoutAll();
          return true;
          }

    //=========================================================
    //   LyricsLineSegment
    //=========================================================

    LyricsLineSegment::LyricsLineSegment(Score* s)
          : LineSegment(s)
          {
          setFlags(ElementFlag::SEGMENT | ElementFlag::ON_STAFF);
          clearFlags(ElementFlag::SELECTABLE | ElementFlag::MOVABLE);
          setGenerated(true);
          }

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

    void LyricsLineSegment::layout()
          {
          bool        endOfSystem       = false;
          bool        isEndMelisma      = lyricsLine()->lyrics()->ticks() > 0;
          Lyrics*     lyr               = 0;
          Lyrics*     nextLyr           = 0;
          qreal       fromX             = 0;
          qreal       toX               = 0;             // start and end point of intra-lyrics room
          qreal       sp                = spatium();
          System*     sys;

          if (lyricsLine()->ticks() <= 0) {   // if no span,
                _numOfDashes = 0;             // nothing to draw
                return;                       // and do nothing
                }

          // HORIZONTAL POSITION
          // A) if line precedes a syllable, advance line end to right before the next syllable text
          // if not a melisma and there is a next syllable;
          if (!isEndMelisma && lyricsLine()->nextLyrics() != nullptr
                      && (spannerSegmentType() == SpannerSegmentType::END
                            || spannerSegmentType() == SpannerSegmentType::SINGLE)) {
                lyr         = nextLyr = lyricsLine()->nextLyrics();
                sys         = lyr->segment()->system();
                endOfSystem = (sys != system());
                // if next lyrics is on a different sytem, this line segment is at the end of its system:
                // do not adjust for next lyrics position
                if (!endOfSystem) {
                      qreal lyrX        = lyr->bbox().x();
                      qreal lyrXp       = lyr->pagePos().x();
                      qreal sysXp       = sys->pagePos().x();
                      toX               = lyrXp - sysXp + lyrX;       // syst.rel. X pos.
                      qreal offsetX     = toX - pos().x() - pos2().x() - Lyrics::LYRICS_DASH_DEFAULT_PAD * sp;
                      //                    delta from current end pos.| ending padding
                      rxpos2()          += offsetX;
                      }
                }
          // B) if line follows a syllable, advance line start to after the syllable text
          lyr   = lyricsLine()->lyrics();
          sys   = lyr->segment()->system();
          if (sys && (spannerSegmentType() == SpannerSegmentType::BEGIN || spannerSegmentType() == SpannerSegmentType::SINGLE)) {
                qreal lyrX        = lyr->bbox().x();
                qreal lyrXp       = lyr->pagePos().x();
                qreal lyrW        = lyr->bbox().width();
                qreal sysXp       = sys->pagePos().x();
                fromX             = lyrXp - sysXp + lyrX + lyrW;
                //               syst.rel. X pos. | lyr.advance
                qreal offsetX     = fromX - pos().x() + (isEndMelisma ? Lyrics::MELISMA_DEFAULT_PAD : Lyrics::LYRICS_DASH_DEFAULT_PAD) * sp;
                //               delta from curr.pos. | add initial padding
                rxpos()           += offsetX;
                rxpos2()          -= offsetX;
                }

          // VERTICAL POSITION: at the base line of the syllable text
          if (spannerSegmentType() != SpannerSegmentType::END)
                rypos() = lyr->y();
          else {
                // use Y position of *next* syllable if there is one on same system
                Lyrics* nextLyr = searchNextLyrics(lyr->segment(), lyr->staffIdx(), lyr->no(), lyr->placement());
                if (nextLyr && nextLyr->segment()->system() == system())
                      rypos() = nextLyr->y();
                else
                      rypos() = lyr->y();
                }

          // MELISMA vs. DASHES
          if (isEndMelisma) {                 // melisma
                _numOfDashes = 1;
                rypos()  -= lyricsLine()->lineWidth().val() * sp * HALF; // let the line 'sit on' the base line
                qreal offsetX = score()->styleP(StyleIdx::minNoteDistance) * mag();
                // if final segment, extend slightly after the chord, otherwise shorten it
                rxpos2() +=
                      (spannerSegmentType() == SpannerSegmentType::BEGIN ||
                            spannerSegmentType() == SpannerSegmentType::MIDDLE)
                      ? -offsetX : +offsetX;

                }
          else {                              // dash(es)
    #if defined(USE_FONT_DASH_METRIC)
                rypos()     += lyr->dashY();
                _dashLength = lyr->dashLength();
    #else
                // set conventional dash Y pos
                rypos() -= MScore::pixelRatio * lyr->fontMetrics().xHeight() * Lyrics::LYRICS_DASH_Y_POS_RATIO;
                _dashLength = score()->styleP(StyleIdx::lyricsDashMaxLength) * mag();  // and dash length
    #endif
                qreal len         = pos2().x();
                qreal minDashLen  = score()->styleS(StyleIdx::lyricsDashMinLength).val() * sp;
                qreal maxDashDist = score()->styleS(StyleIdx::lyricsDashMaxDistance).val() * sp;
                if (len < minDashLen) {                                           // if no room for a dash
                      // if at end of system or dash is forced
                      if (endOfSystem || score()->styleB(StyleIdx::lyricsDashForce)) {
                            rxpos2()          = minDashLen;                       //     draw minimal dash
                            _numOfDashes      = 1;
                            _dashLength       = minDashLen;
                            }
                      else                                                        //   if within system or dash not forced
                            _numOfDashes = 0;                                     //     draw no dash
                      }
                else if (len < (maxDashDist * TWICE)) {                           // if no room for two dashes
                      _numOfDashes = 1;                                           //    draw one dash
                      if (_dashLength > len)                                      // if no room for a full dash
                            _dashLength = len;                                    //    shorten it
                      }
                else
                      _numOfDashes = len / (maxDashDist);                         // draw several dashes

                // adjust next lyrics horiz. position if too little a space forced to skip the dash
                if (_numOfDashes == 0 && nextLyr != nullptr && len > 0)
                      nextLyr->rxpos() -= (toX - fromX);
                }

          // set bounding box
          QRectF r = QRectF(0.0, 0.0, pos2().x(), pos2().y()).normalized();
          qreal lw = spatium() * lyricsLine()->lineWidth().val() * HALF;
          setbbox(r.adjusted(-lw, -lw, lw, lw));
          adjustReadPos();
          }

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

    void LyricsLineSegment::draw(QPainter* painter) const
          {
          if (_numOfDashes < 1)               // nothing to draw
                return;
          qreal _spatium = spatium();

          QPen pen(lyricsLine()->lyrics()->curColor());
          pen.setWidthF(lyricsLine()->lineWidth().val() * _spatium);
          pen.setCapStyle(Qt::FlatCap);
          painter->setPen(pen);
          if (lyricsLine()->lyrics()->ticks() > 0)           // melisma
                painter->drawLine(QPointF(), pos2());
          else {                                          // dash(es)
                qreal step  = pos2().x() / (_numOfDashes+1);
                qreal x     = step - _dashLength * HALF;
                for (int i = 0; i < _numOfDashes; i++, x += step)
                      painter->drawLine(QPointF(x, 0.0), QPointF(x + _dashLength, 0.0));
                }
          }

    //---------------------------------------------------------
    //   propertyStyle
    //---------------------------------------------------------

    PropertyFlags Lyrics::propertyFlags(P_ID id) const
          {
          switch (id) {
                case P_ID::PLACEMENT:
                      return PropertyFlags::NOSTYLE;

                default:
                      return Text::propertyFlags(id);
                }
          }

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

    StyleIdx Lyrics::getPropertyStyle(P_ID id) const
          {
          switch (id) {
                case P_ID::PLACEMENT:
                      return StyleIdx::lyricsPlacement;
                case P_ID::FONT_FACE:
                      return isEven() ? StyleIdx::lyricsEvenFontFace : StyleIdx::lyricsOddFontFace;
                case P_ID::FONT_SIZE:
                      return isEven() ? StyleIdx::lyricsEvenFontSize : StyleIdx::lyricsOddFontSize;
                case P_ID::FONT_BOLD:
                      return isEven() ? StyleIdx::lyricsEvenFontBold : StyleIdx::lyricsOddFontBold;
                case P_ID::FONT_ITALIC:
                      return isEven() ? StyleIdx::lyricsEvenFontItalic : StyleIdx::lyricsOddFontItalic;
                case P_ID::FONT_UNDERLINE:
                      return isEven() ? StyleIdx::lyricsEvenFontUnderline : StyleIdx::lyricsOddFontUnderline;
                case P_ID::ALIGN:
                      return isEven() ? StyleIdx::lyricsEvenAlign : StyleIdx::lyricsOddAlign;
                default:
                      break;
                }
          return StyleIdx::NOSTYLE;
          }

    //---------------------------------------------------------
    //   styleChanged
    //    reset all styled values to actual style
    //---------------------------------------------------------

    void Lyrics::styleChanged()
          {
          if (placementStyle == PropertyFlags::STYLED)
                setPlacement(Placement(score()->styleI(StyleIdx::lyricsPlacement)));
          Text::styleChanged();
          }

    //---------------------------------------------------------
    //   reset
    //---------------------------------------------------------

    void Lyrics::reset()
          {
          undoResetProperty(P_ID::PLACEMENT);
          Text::reset();
          }

    //---------------------------------------------------------
    //   resetProperty
    //---------------------------------------------------------

    void Lyrics::resetProperty(P_ID id)
          {
          switch (id) {
                case P_ID::PLACEMENT:
                      setProperty(id, propertyDefault(id));
                      placementStyle = PropertyFlags::STYLED;
                      break;

                default:
                      return Text::resetProperty(id);
                }
          }

    }

↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: placementStyle.

↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: _numOfDashes, _dashLength.