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

#include "barline.h"
#include "chord.h"
#include "lyrics.h"
#include "measure.h"
#include "note.h"
#include "part.h"
#include "score.h"
#include "segment.h"
#include "staff.h"
#include "sym.h"
#include "system.h"
#include "textline.h"
#include "utils.h"
#include "xml.h"

namespace Ms {

//---------------------------------------------------------
//   LineSegment
//---------------------------------------------------------

LineSegment::LineSegment(const LineSegment& s)
   : SpannerSegment(s)
      {
      }

//---------------------------------------------------------
//   readProperties
//---------------------------------------------------------

bool LineSegment::readProperties(XmlReader& e)
      {
      const QStringRef& tag(e.name());
      if (tag == "subtype")
            setSpannerSegmentType(SpannerSegmentType(e.readInt()));
      else if (tag == "off2") {
            setUserOff2(e.readPoint() * score()->spatium());
            }
/*      else if (tag == "pos") {
            setOffset(QPointF());
            e.readNext();
            }
      */
      else if (!SpannerSegment::readProperties(e)) {
            e.unknown();
            return false;
            }
      return true;
      }

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

void LineSegment::read(XmlReader& e)
      {
      while (e.readNextStartElement())
            readProperties(e);
      }

//---------------------------------------------------------
//   gripsPositions
//---------------------------------------------------------

std::vector<QPointF> LineSegment::gripsPositions(const EditData&) const
      {
      std::vector<QPointF> grips(gripsCount());
      QPointF pp(pagePos());
      grips[int(Grip::START)] = pp;
      grips[int(Grip::END)] = pos2() + pp;
      grips[int(Grip::MIDDLE)] = pos2() * .5 + pp;
      return grips;
      }

//---------------------------------------------------------
//   leftAnchorPosition
//---------------------------------------------------------

QPointF LineSegment::leftAnchorPosition(const qreal& systemPositionY) const
      {

      if (isMiddleType() || isEndType())
            return QPointF(system()->firstMeasure()->abbox().left(), systemPositionY);

      QPointF result;

      System* s;
      result = line()->linePos(Grip::START, &s);
      result.ry() += systemPositionY - system()->pos().y();

      if (s)
            result += s->pos();  // to page coordinates

      return result;
      }

//---------------------------------------------------------
//   rightAnchorPosition
//---------------------------------------------------------

QPointF LineSegment::rightAnchorPosition(const qreal& systemPositionY) const
    {

    if (isMiddleType() || isBeginType())
          return QPointF(system()->lastMeasure()->abbox().right(), systemPositionY);

    QPointF result;

    System* s;
    result = line()->linePos(Grip::END, &s);
    result.ry() += systemPositionY - system()->pos().y();

    if (s)
          result += s->pos();  // to page coordinates

    return result;
    }

//---------------------------------------------------------
//   gripAnchorLines
//    return page coordinates
//---------------------------------------------------------

QVector<QLineF> LineSegment::gripAnchorLines(Grip grip) const
      {
      QVector<QLineF> result;

      // Middle or aperture grip have no anchor
      if (!system() || grip == Grip::APERTURE)
            return result;

      // note-anchored spanners are relative to the system
      qreal y;
      if (spanner()->anchor() == Spanner::Anchor::NOTE)
            y = system()->pos().y();
      else {
            const int stIdx = staffIdx();
            y = system()->staffYpage(stIdx);
            if (line()->placement() == Placement::BELOW)
                  y += system()->staff(stIdx)->bbox().height();
            }

      switch (grip) {
      case Grip::START:
            result << QLineF(leftAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::START)));
            break;
      case Grip::END:
            result << QLineF(rightAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::END)));
            break;
      case Grip::MIDDLE:
            result << QLineF(leftAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::START)));
            result << QLineF(rightAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::END)));
            break;
      default:
            break;
            }

      return result;
      }

//---------------------------------------------------------
//   startDrag
//---------------------------------------------------------

void LineSegment::startDrag(EditData& ed)
      {
      SpannerSegment::startDrag(ed);
      ElementEditData* eed = ed.getData(this);
      eed->pushProperty(Pid::OFFSET2);
      }

//---------------------------------------------------------
//   startEditDrag
//---------------------------------------------------------

void LineSegment::startEditDrag(EditData& ed)
      {
      ElementEditData* eed = ed.getData(this);
      eed->pushProperty(Pid::OFFSET);
      eed->pushProperty(Pid::OFFSET2);
      eed->pushProperty(Pid::AUTOPLACE);
      if (ed.modifiers & Qt::AltModifier)
            setAutoplace(false);
      }

//---------------------------------------------------------
//   edit
//    return true if event is accepted
//---------------------------------------------------------

bool LineSegment::edit(EditData& ed)
      {
      const bool moveStart = ed.curGrip == Grip::START;
      const bool moveEnd = ed.curGrip == Grip::END || ed.curGrip == Grip::MIDDLE;

      if (!((ed.modifiers & Qt::ShiftModifier)
         && ((isSingleBeginType() && moveStart) || (isSingleEndType() && moveEnd))
         ))
            return false;

      LineSegment* ls       = 0;
      SpannerSegmentType st = spannerSegmentType(); // may change later
      SLine* l              = line();
      int track             = l->track();
      int track2            = l->track2();    // assumed to be same as track

      switch (l->anchor()) {
            case Spanner::Anchor::SEGMENT:
                  {
                  Segment* s1 = spanner()->startSegment();
                  Segment* s2 = spanner()->endSegment();
                  // check for line going to end of score
                  if (spanner()->tick2() >= score()->lastSegment()->tick()) {
                        // endSegment calculated above will be the last chord/rest of score
                        // but that is not correct - it should be an imaginary note *after* the end of the score
                        // best we can do is set s2 to lastSegment (probably the end barline)
                        s2 = score()->lastSegment();
                        }
                  if (!s1 && !s2) {
                        qDebug("LineSegment::edit: no start/end segment");
                        return true;
                        }
                  if (ed.key == Qt::Key_Left) {
                        if (moveStart)
                              s1 = prevSeg1(s1, track);
                        else if (moveEnd)
                              s2 = prevSeg1(s2, track2);
                        }
                  else if (ed.key == Qt::Key_Right) {
                        if (moveStart)
                              s1 = nextSeg1(s1, track);
                        else if (moveEnd) {
                              Segment* ns2 = nextSeg1(s2, track2);
                              if (ns2)
                                    s2 = ns2;
                              else
                                    s2 = score()->lastSegment();
                              }
                        }
                  if (s1 == 0 || s2 == 0 || s1->tick() >= s2->tick())
                        return true;
                  spanner()->undoChangeProperty(Pid::SPANNER_TICK, s1->tick());
                  spanner()->undoChangeProperty(Pid::SPANNER_TICKS, s2->tick() - s1->tick());
                  }
                  break;
            case Spanner::Anchor::NOTE:
                  {
                  Note* note1       = toNote(l->startElement());
                  Note* note2       = toNote(l->endElement());
                  Note* oldNote1    = note1;
                  Note* oldNote2    = note2;
                  if (!note1 && !note2) {
                        qDebug("LineSegment::edit: no start/end note");
                        return true;            // accept the event without doing anything
                        }

                  switch (ed.key) {
                        case Qt::Key_Left:
                              if (moveStart)
                                    note1 = prevChordNote(note1);
                              else if (moveEnd)
                                    note2 = prevChordNote(note2);
                              break;
                        case Qt::Key_Right:
                              if (moveStart)
                                    note1 = nextChordNote(note1);
                              else if (moveEnd)
                                    note2 = nextChordNote(note2);
                              break;
                        case Qt::Key_Up:
                              if (moveStart)
                                    note1 = toNote(score()->upAlt(note1));
                              else if (moveEnd)
                                    note2 = toNote(score()->upAlt(note2));
                              break;
                        case Qt::Key_Down:
                              if (moveStart)
                                    note1 = toNote(score()->downAlt(note1));
                              else if (moveEnd)
                                    note2 = toNote(score()->downAlt(note2));
                              break;
                        default:
                              return true;
                        }

                  // check prevChordNote() and nextchordNote() didn't return null
                  // OR Score::upAlt() and Score::downAlt() didn't return non-Note (notably rests)
                  // OR spanner duration is > 0
                  // OR note1 and note2 didn't end up in different instruments
                  // if this is the case, accepts the event and return without doing nothing
                  if (!note1 || !note2
                     || !note1->isNote() || !note2->isNote()
                     || note1->chord()->tick() >= note2->chord()->tick()
                     || note1->chord()->staff()->part()->instrument(note1->chord()->tick())
                     != note2->chord()->staff()->part()->instrument(note2->chord()->tick()) )
                        return true;
                  if (note1 != oldNote1 || note2 != oldNote2)
                        score()->undoChangeSpannerElements(spanner(), note1, note2);
                  }
                  break;
            case Spanner::Anchor::MEASURE:
            case Spanner::Anchor::CHORD:
                  {
                  Measure* m1 = l->startMeasure();
                  Measure* m2 = l->endMeasure();

                  if (ed.key == Qt::Key_Left) {
                        if (moveStart) {
                              if (m1->prevMeasure())
                                    m1 = m1->prevMeasure();
                              }
                        else if (moveEnd) {
                              Measure* m = m2->prevMeasure();
                              if (m)
                                    m2 = m;
                              }
                        }
                  else if (ed.key == Qt::Key_Right) {
                        if (moveStart) {
                              if (m1->nextMeasure())
                                    m1 = m1->nextMeasure();
                              }
                        else if (moveEnd) {
                              if (m2->nextMeasure())
                                    m2 = m2->nextMeasure();
                              }
                        }
                  if (m1->tick() > m2->tick())
                        return true;
                  if (l->startElement() != m1) {
                        spanner()->undoChangeProperty(Pid::SPANNER_TICK,  m1->tick());
                        spanner()->undoChangeProperty(Pid::SPANNER_TICKS, m2->endTick() - m1->tick());
                        }
                  else if (l->endElement() != m2) {
                        spanner()->undoChangeProperty(Pid::SPANNER_TICKS, m2->endTick() - m1->tick());
                        }
                  }
            }
      triggerLayout();
      l->layout();            // recompute segment list, segment type may change

      LineSegment* nls = 0;
      if (st == SpannerSegmentType::SINGLE) {
            if (moveStart)
                  nls = l->frontSegment();
            else if (moveEnd)
                  nls = l->backSegment();
            }
      else if (st == SpannerSegmentType::BEGIN)
            nls = l->frontSegment();
      else if (st == SpannerSegmentType::END)
            nls = l->backSegment();
      else
            qDebug("spannerSegmentType %d", int(spannerSegmentType()));

      if (nls && (nls != this))
            ed.view->changeEditElement(nls);
      if (ls)
            score()->undoRemoveElement(ls);

      triggerLayout();
      return true;
      }

//---------------------------------------------------------
//   findSegmentForGrip
//---------------------------------------------------------

Segment* LineSegment::findSegmentForGrip(Grip grip, QPointF pos) const
      {
      if (grip != Grip::START && grip != Grip::END)
            return nullptr;

      SLine* l = line();
      const bool left = (grip == Grip::START);

      Segment* const oldSeg = left ? l->startSegment() : score()->tick2leftSegmentMM(l->tick2() - Fraction::eps());
      const int oldStaffIndex = left ? staffIdx() : track2staff(l->effectiveTrack2());

      const qreal spacingFactor = left ? 0.5 : 1.0; // defines the point where canvas is divided between segments, systems etc.

      System* sys = oldSeg->system();
      const QList<System*> foundSystems = score()->searchSystem(pos, sys, spacingFactor);

      if (!foundSystems.empty() && !foundSystems.contains(sys))
            sys = foundSystems[0];

      // Restrict searching segment to the correct staff
      pos.setY(sys->staffCanvasYpage(oldStaffIndex));

      Segment* seg = nullptr; // don't prefer any segment while searching line position
      int staffIndex = oldStaffIndex;
      score()->dragPosition(pos, &staffIndex, &seg, spacingFactor);

      return seg;
      }

//---------------------------------------------------------
//   deltaRebaseLeft
///   Helper function for anchors rebasing when dragging.
//---------------------------------------------------------

QPointF LineSegment::deltaRebaseLeft(const Segment* oldSeg, const Segment* newSeg)
      {
      if (oldSeg == newSeg)
            return QPointF();
      return oldSeg->canvasPos() - newSeg->canvasPos();
      }

//---------------------------------------------------------
//   deltaRebaseRight
///   Helper function for anchors rebasing when dragging.
//---------------------------------------------------------

QPointF LineSegment::deltaRebaseRight(const Segment* oldSeg, const Segment* newSeg, int staffIndex)
      {
      if (oldSeg == newSeg)
            return QPointF();

      const QPointF oldBase = oldSeg->canvasPos() + QPointF(oldSeg->width(), 0);
      const QPointF newBase = newSeg->canvasPos() + QPointF(newSeg->widthInStaff(staffIndex), 0);
      return oldBase - newBase;
      }

//---------------------------------------------------------
//   lastSegmentEndTick
///   Helper function for anchors rebasing when dragging.
//---------------------------------------------------------

Fraction LineSegment::lastSegmentEndTick(const Segment* seg, const Spanner* s)
      {
      return seg->tick() + seg->ticksInStaff(track2staff(s->effectiveTrack2()));
      }

//---------------------------------------------------------
//   rebaseAnchor
///   If system has changed, returns the new line segment
///   (may appear to be reused \c this segment), otherwise
///   returns nullptr.
//---------------------------------------------------------

LineSegment* LineSegment::rebaseAnchor(Grip grip, Segment* newSeg)
      {
      switch (grip) {
            case Grip::START:
                  if (!isSingleBeginType())
                        return nullptr;
                  break;
            case Grip::END:
                  if (!isSingleEndType())
                        return nullptr;
                  break;
            default:
                  return nullptr;
            }

      SLine* l = line();
      const bool left = (grip == Grip::START);
      Segment* const oldSeg = left ? l->startSegment() : score()->tick2leftSegmentMM(l->tick2() - Fraction::eps());
      System* const oldSystem = system();

      if (!newSeg || oldSeg == newSeg)
            return nullptr;

      Fraction startTick = left ? newSeg->tick() : l->tick();
      Fraction endTick = left ? l->tick2() : lastSegmentEndTick(newSeg, l);

      if (endTick <= startTick) {
            if (left)
                  endTick = lastSegmentEndTick(newSeg, l);
            else
                  startTick = newSeg->tick();
            }

      if (l->tick() != startTick)
            l->undoChangeProperty(Pid::SPANNER_TICK, startTick);

      l->undoChangeProperty(Pid::SPANNER_TICKS, endTick - startTick);

      if (newSeg->system() == oldSystem) {
            const QPointF delta = left ? deltaRebaseLeft(oldSeg, newSeg) : deltaRebaseRight(oldSeg, newSeg, track2staff(l->effectiveTrack2()));
            if (left) {
                  setOffset(offset() + delta);
                  _offset2 -= delta;
                  setOffsetChanged(true);
                  }
            else {
                  _offset2 += delta;
                  }
            }
      else {
            l->layout();
            return left ? l->frontSegment() : l->backSegment();
            }

      return nullptr;
      }

//---------------------------------------------------------
//   rebaseAnchors
//---------------------------------------------------------

void LineSegment::rebaseAnchors(EditData& ed, Grip grip)
      {
      if (line()->anchor() != Spanner::Anchor::SEGMENT)
            return;

      switch (grip) {
            case Grip::START:
            case Grip::END: {
                  const bool left = (grip == Grip::START);

                  if (left && !isSingleBeginType())
                        return;
                  if (!left && !isSingleEndType())
                        return;

                  // Find an appropriate segment to bind from actual mouse
                  // position. This allows changing systems while dragging
                  // while not setting line position to something
                  // inappropriate.
                  Segment* seg = findSegmentForGrip(grip, ed.pos);
                  LineSegment* newLineSegment = rebaseAnchor(grip, seg);

                  if (newLineSegment) {
                        // If new line segment is not the same as this one,
                        // switch to dragging that new segment.
                        if (newLineSegment != this) {
                              // Reset offset for the old line segment
                              if (left) {
                                    _offset2.rx() -= offset().x();
                                    setOffset(QPointF());
                                    }
                              else {
                                    setUserOff2(QPointF());
                                    }

                              // Switch to dragging the new line segment
                              ed.view->changeEditElement(newLineSegment);
                              }

                        // Set offset for the new line segment for grip to appear under the mouse cursor
                        System* sys;
                        const QPointF lp = line()->linePos(grip, &sys);
                        const qreal xoff = sys->mapFromCanvas(ed.pos).x() - lp.x();

                        if (left) {
                              newLineSegment->rxoffset() = xoff;
                              newLineSegment->setUserOff2(QPointF(-xoff, 0.0));
                              }
                        else {
                              newLineSegment->setUserXoffset2(xoff);
                              }
                        }
                  }
                  break;
            case Grip::MIDDLE: {
                  if (!isSingleType())
                        return;

                  SLine* l = line();

                  // If dragging middle grip (or the entire hairpin), mouse position
                  // does not directly correspond to any sensible position, so use
                  // actual line coordinates instead. This method doesn't allow for
                  // system changes, but that seems OK when dragging the entire line:
                  // the line will just push away other systems according to autoplacement
                  // rules if necessary.
                  QPointF cpos = canvasPos();
                  cpos.setY(l->startSegment()->system()->staffCanvasYpage(l->staffIdx())); // prevent cross-system move

                  Segment* seg1 = findSegmentForGrip(Grip::START, cpos);
                  Segment* seg2 = findSegmentForGrip(Grip::END, cpos + pos2());

                  if (!(seg1 && seg2 && seg1->system() == seg2->system() && seg1->system() == system()))
                        return;

                  rebaseAnchor(Grip::START, seg1);
                  rebaseAnchor(Grip::END, seg2);
                  }
            default:
                  break;
            }
      }

//---------------------------------------------------------
//   editDrag
//---------------------------------------------------------

void LineSegment::editDrag(EditData& ed)
      {
      // Only for resizing according to the diagonal properties
      const QPointF deltaResize(ed.evtDelta.x(), line()->diagonal() ? ed.evtDelta.y() : 0.0);

      switch (ed.curGrip) {
            case Grip::START: // Resize the begin of element (left grip)
                  setOffset(offset() + deltaResize);
                  _offset2 -= deltaResize;

                  if (isStyled(Pid::OFFSET))
                        setPropertyFlags(Pid::OFFSET, PropertyFlags::UNSTYLED);

                  rebaseAnchors(ed, ed.curGrip);
                  break;
            case Grip::END: // Resize the end of element (right grip)
                  _offset2 += deltaResize;
                  rebaseAnchors(ed, ed.curGrip);
                  break;
            case Grip::MIDDLE: { // Move the element (middle grip)
                  // Only for moving, no y limitation
                  const QPointF deltaMove(ed.evtDelta);
                  setOffset(offset() + deltaMove);
                  setOffsetChanged(true);
                  if (isStyled(Pid::OFFSET))
                        setPropertyFlags(Pid::OFFSET, PropertyFlags::UNSTYLED);
                  rebaseAnchors(ed, ed.curGrip);
                  }
                  break;
            default:
                  break;
            }
      if (line()->anchor() == Spanner::Anchor::NOTE && ed.isStartEndGrip()) {
            //
            // if we touch a different note, change anchor
            //
            Element* e = ed.view->elementNear(ed.pos);
            if (e && e->isNote()) {
                  SLine* l = line();
                  if (ed.curGrip == Grip::END && e != line()->endElement()) {
                        qDebug("LineSegment: move end anchor");
                        Note* noteOld = toNote(l->endElement());
                        Note* noteNew = toNote(e);
                        Note* sNote   = toNote(l->startElement());
                        // do not change anchor if new note is before start note
                        if (sNote && sNote->chord() && noteNew->chord() && sNote->chord()->tick() < noteNew->chord()->tick()) {
                              score()->undoChangeSpannerElements(l, sNote, noteNew);

                              _offset2 += noteOld->canvasPos() - noteNew->canvasPos();
                              }
                        }
                  else if (ed.curGrip == Grip::START && e != l->startElement())
                        qDebug("LineSegment: move start anchor (not impl.)");
                  }
            }
      triggerLayout();
      }

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

void LineSegment::spatiumChanged(qreal ov, qreal nv)
      {
      Element::spatiumChanged(ov, nv);
      _offset2 *= nv / ov;
      }

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

void LineSegment::localSpatiumChanged(qreal ov, qreal nv)
      {
      Element::localSpatiumChanged(ov, nv);
      _offset2 *= nv / ov;
      }

//---------------------------------------------------------
//   propertyDelegate
//---------------------------------------------------------

Element* LineSegment::propertyDelegate(Pid pid)
      {
      if (pid == Pid::DIAGONAL
         || pid == Pid::COLOR
         || pid ==   Pid::LINE_WIDTH
         || pid ==   Pid::LINE_STYLE
         || pid ==   Pid::DASH_LINE_LEN
         || pid ==   Pid::DASH_GAP_LEN)
            return spanner();
      return SpannerSegment::propertyDelegate(pid);
      }

//---------------------------------------------------------
//   dragAnchorLines
//---------------------------------------------------------

QVector<QLineF> LineSegment::dragAnchorLines() const
      {
      return gripAnchorLines(Grip::MIDDLE);
      }

QRectF LineSegment::drag(EditData& ed)
      {
      setOffset(offset() + ed.evtDelta);
      setOffsetChanged(true);

      if (isStyled(Pid::OFFSET))
            setPropertyFlags(Pid::OFFSET, PropertyFlags::UNSTYLED);

      rebaseAnchors(ed, Grip::MIDDLE);

      return canvasBoundingRect();
      }

//---------------------------------------------------------
//   SLine
//---------------------------------------------------------

SLine::SLine(Score* s, ElementFlags f)
   : Spanner(s, f)
      {
      setTrack(0);
      _lineWidth = 0.15 * spatium();
      }

SLine::SLine(const SLine& s)
   : Spanner(s)
      {
      _diagonal    = s._diagonal;
      _lineWidth   = s._lineWidth;
      _lineColor   = s._lineColor;
      _lineStyle   = s._lineStyle;
      _dashLineLen = s._dashLineLen;
      _dashGapLen  = s._dashGapLen;
      }

//---------------------------------------------------------
//   linePos
///   - Anchor::NOTE:  return anchor note position in system
///                    coordinates
///   - Anchor::CHORD: not implemented
///   - Other:         return (x position in system coordinates, 0)
//---------------------------------------------------------

QPointF SLine::linePos(Grip grip, System** sys) const
      {
      qreal x = 0.0;
      qreal sp = staff()->spatium(tick());
      switch (anchor()) {
            case Spanner::Anchor::SEGMENT:
                  {
                  ChordRest* cr;
                  if (grip == Grip::START) {
                        cr = toChordRest(startElement());
                        if (cr && type() == ElementType::OTTAVA) {
                              // some sources say to center the text over the notehead
                              // others say to start the text just to left of notehead
                              // some say to include accidental, others don't
                              // our compromise - left align, but account for accidental
                              if (cr->durationType() == TDuration::DurationType::V_MEASURE && !cr->measure()->hasVoices(cr->staffIdx()))
                                    x = cr->x();            // center for measure rests
//TODO                              else if (cr->spaceLw > 0.0)
//                                    x = -cr->spaceLw;  // account for accidentals, etc
                              }
                        }
                  else {
                        cr = toChordRest(endElement());
                        if (isOttava()) {
                              if (cr && cr->durationType() == TDuration::DurationType::V_MEASURE) {
                                    x = cr->x() + cr->width() + sp;
                                    }
                              else if (cr) {
                                    // lay out just past right edge of all notes for this segment on this staff

                                    Segment* s = cr->segment();

                                    int startTrack = staffIdx() * VOICES;
                                    int endTrack   = startTrack + VOICES;
                                    qreal width    = 0.0;

                                    // don’t consider full measure rests, which are centered
                                    // (TODO: what if there is only a full measure rest?)

                                    for (int track = startTrack; track < endTrack; ++track) {
                                          ChordRest* cr1 = toChordRest(s->element(track));
                                          if (!cr1)
                                                continue;
                                          if (cr1->isChord()) {
                                                for (Note* n : toChord(cr1)->notes())
                                                      width = qMax(width, n->shape().right() + n->pos().x() + cr1->pos().x());
                                                }
                                          else if (cr1->isRest() && (cr1->actualDurationType() != TDuration::DurationType::V_MEASURE))
                                                width = qMax(width, cr1->bbox().right() + cr1->pos().x());
                                          }

                                    x = width + sp;

                                    // extend past chord/rest
                                    // but don't overlap next chord/rest

                                    bool crFound = false;
                                    int n = staffIdx() * VOICES;
                                    Segment* ns = s->next();
                                    while (ns) {
                                          for (int i = 0; i < VOICES; ++i) {
                                                if (ns->element(n + i)) {
                                                      crFound = true;
                                                      break;
                                                      }
                                                }
                                          if (crFound)
                                                break;
                                          ns = ns->next();
                                          }
                                    if (crFound) {
                                          qreal nextNoteDistance = ns->x() - s->x() + lineWidth();
                                          if (x > nextNoteDistance)
                                                x = qMax(width, nextNoteDistance);
                                          }
                                    }
                              }
                        else if (isLyricsLine() && toLyrics(parent())->ticks() > Fraction(0,1)) {
                              // melisma line
                              // it is possible CR won't be in correct track
                              // prefer element in current track if available
                              if (!cr)
                                    qDebug("no end for lyricsline segment - start %d, ticks %d", tick().ticks(), ticks().ticks());
                              else if (cr->track() != track()) {
                                    Element* e = cr->segment()->element(track());
                                    if (e)
                                          cr = toChordRest(e);
                                    }

                              // layout to right edge of CR
                              bool extendToEnd = true; // extend to end or start element?
                              if (cr == toChordRest(startElement()))
                                    extendToEnd = false;
                              else if (cr) {
                                    // if next segment is a chord with lyrics which spans to the left
                                    // then do not extend to the right edge of end element.
                                    Segment* seg = cr->segment();
                                    seg = seg->next(SegmentType::ChordRest);
                                    if (seg) {
                                          ChordRest* cr2 = seg->cr(cr->track());
                                          if (cr2 && !cr2->lyrics().empty())
                                                extendToEnd = false;
                                          }
                                    }
                              ChordRest* right = extendToEnd ? cr : toChordRest(startElement());
                              if (right) {
                                    qreal maxRight = 0.0;
                                    if (right->isChord()) {
                                          // chord bbox() is unreliable, look at notes
                                          // this also allows us to more easily ignore ledger lines
                                          for (Note* n : toChord(right)->notes())
                                                maxRight = qMax(maxRight, right->x() + n->x() + n->bboxRightPos());
                                          }
                                    else {
                                          // rest - won't normally happen
                                          maxRight = right->x() + right->width();
                                          }
                                    x = maxRight;
                                    }
                             }
                        else if (isHairpin() || isTrill() || isVibrato() || isTextLine() || isLyricsLine()) {
                              // (for LYRICSLINE, this is hyphen; melisma line is handled above)
                              // lay out to just before next chordrest on this staff, or barline
                              // tick2 actually tells us the right chordrest to look for
                              if (cr && endElement()->parent() && endElement()->parent()->type() == ElementType::SEGMENT) {
                                    qreal x2 = cr->x() /* TODO + cr->space().rw() */;
                                    Segment* currentSeg = toSegment(endElement()->parent());
                                    Segment* seg = score()->tick2segmentMM(tick2(), false, SegmentType::ChordRest);
                                    if (!seg) {
                                          // no end segment found, use measure width
                                          x2 = endElement()->parent()->parent()->width() - sp;
                                          }
                                    else if (currentSeg->measure() == seg->measure()) {
                                          // next chordrest found in same measure;
                                          // end line 1sp to left
                                          x2 = qMax(x2, seg->x() - sp);
                                          }
                                    else {
                                          // next chordrest is in next measure
                                          // lay out to end (barline) of current measure instead
                                          seg = currentSeg->next(SegmentType::EndBarLine);
                                          if (!seg)
                                                seg = currentSeg->measure()->last();
                                          // allow lyrics hyphen to extend to barline
                                          // other lines stop 1sp short
                                          qreal gap = (type() == ElementType::LYRICSLINE) ? 0.0 : sp;
                                          qreal x3 = seg->enabled() ? seg->x() : seg->measure()->width();
                                          x2 = qMax(x2, x3 - gap);
                                          }
                                    x = x2 - endElement()->parent()->x();
                                    }
                              }
                        }

                  Fraction t = grip == Grip::START ? tick() : tick2();
                  Measure* m = cr ? cr->measure() : score()->tick2measure(t);

                  if (m) {
                        x += cr ? cr->segment()->pos().x() + m->pos().x() : m->tick2pos(t);
                        *sys = m->system();
                        }
                  else
                        *sys = 0;
                  }
                  break;

            case Spanner::Anchor::MEASURE:
                  {
                  // anchor() == Anchor::MEASURE
                  const Measure* m;
                  if (grip == Grip::START) {
                        m = startMeasure();
                        // start after clef/keysig/timesig/barline
                        qreal offset = 0.0;
                        Segment* s = m->first(SegmentType::ChordRest);
                        if (s) {
                              s = s->prev();
                              if (s && s->enabled()) {
                                    offset = s->x();
                                    Element* e = s->element(staffIdx() * VOICES);
                                    if (e)
                                          offset += e->width();
                                    }
                              }
                        x = m->pos().x() + offset;
                        if (score()->styleB(Sid::createMultiMeasureRests) && m->hasMMRest()) {
                              x = m->mmRest()->pos().x();
                              }
                        }
                  else {
                        qreal _spatium = spatium();

                        if (score()->styleB(Sid::createMultiMeasureRests)) {
                              // find the actual measure where the volta should stop
                              m = startMeasure();
                              if (m->hasMMRest())
                                    m = m->mmRest();
                              while (m->nextMeasureMM() && (m->endTick() < tick2()))
                                    m = m->nextMeasureMM();
                              }
                        else {
                              m = endMeasure();
                              }

                        // back up to barline (skip courtesy elements)
                        Segment* seg = m->last();
                        while (seg && seg->segmentType() != SegmentType::EndBarLine)
                              seg = seg->prev();
                        if (!seg || !seg->enabled()) {
                              // no end bar line; look for BeginBarLine or StartRepeatBarLine of next measure
                              Measure* nm = m->nextMeasure();
                              if (nm->system() == m->system())
                                    seg = nm->first(SegmentType::BeginBarLine|SegmentType::StartRepeatBarLine);
                              }
                        qreal mwidth = seg && seg->measure() == m ? seg->x() : m->bbox().right();
                        x = m->pos().x() + mwidth;
                        // align to barline
                        if (seg && (seg->segmentType() & SegmentType::BarLineType)) {
                              Element* e = seg->element(0);
                              if (e && e->type() == ElementType::BAR_LINE) {
                                    BarLineType blt = toBarLine(e)->barLineType();
                                    switch (blt) {
                                          case BarLineType::END_REPEAT:
                                                // skip dots
                                                x += symWidth(SymId::repeatDot);
                                                x += score()->styleS(Sid::endBarDistance).val() * _spatium;
                                                // fall through
                                          case BarLineType::DOUBLE:
                                                // center on leftmost (thinner) barline
                                                x += score()->styleS(Sid::doubleBarWidth).val() * _spatium * 0.5;
                                                break;
                                          case BarLineType::START_REPEAT:
                                                // center on leftmost (thicker) barline
                                                x += score()->styleS(Sid::endBarWidth).val() * _spatium * 0.5;
                                                break;
                                          default:
                                                // center on barline
                                                x += score()->styleS(Sid::barWidth).val() * _spatium * 0.5;
                                                break;
                                          }
                                    }
                              }
                        }
                  if (score()->styleB(Sid::createMultiMeasureRests))
                        m = m->mmRest1();
                  Q_ASSERT(m->system());
                  *sys = m->system();
                  }
                  break;

            case Spanner::Anchor::NOTE: {
                  Element* e = grip == Grip::START ? startElement() : endElement();
                  if (!e)
                        return QPointF();
                  Note* n = toNote(e);
                  System* s = n->chord()->segment()->system();
                  if (s == 0) {
                        qDebug("no system: %s  start %s chord parent %s\n", name(), n->name(), n->chord()->parent()->name());
                        return QPointF();
                        }
                  *sys = s;
                  // return the position of the anchor note relative to the system
//                  QPointF     elemPagePos = e->pagePos();                   // DEBUG
//                  QPointF     systPagePos = s->pagePos();
//                  qreal       staffYPage  = s->staffYpage(e->staffIdx());
                  QPointF p = n->pagePos() - s->pagePos();
                  if (!isGlissando())
                        p.rx() += n->headWidth() * 0.5;
                  return p;
                  }

            case Spanner::Anchor::CHORD:
                  qFatal("Sline::linePos(): anchor not implemented");
                  break;
            }
      return QPointF(x, 0.0);
      }

//---------------------------------------------------------
//   layoutSystem
//    layout spannersegment for system
//---------------------------------------------------------

SpannerSegment* SLine::layoutSystem(System* system)
      {
      Fraction stick = system->firstMeasure()->tick();
      Fraction etick = system->lastMeasure()->endTick();

      LineSegment* lineSegm = toLineSegment(getNextLayoutSystemSegment(system, [this]() { return createLineSegment(); }));

      SpannerSegmentType sst;
      if (tick() >= stick) {
            //
            // this is the first call to layoutSystem,
            // processing the first line segment
            //
            computeStartElement();
            computeEndElement();
            sst = tick2() <= etick ? SpannerSegmentType::SINGLE : SpannerSegmentType::BEGIN;
            }
      else if (tick() < stick && tick2() > etick) {
            sst = SpannerSegmentType::MIDDLE;
            }
      else {
            //
            // this is the last call to layoutSystem
            // processing the last line segment
            //
            sst = SpannerSegmentType::END;
            }
      lineSegm->setSpannerSegmentType(sst);

      switch (sst) {
            case SpannerSegmentType::SINGLE: {
                  System* s;
                  QPointF p1 = linePos(Grip::START, &s);
                  QPointF p2 = linePos(Grip::END,   &s);
                  qreal len = p2.x() - p1.x();
                  lineSegm->setPos(p1);
                  lineSegm->setPos2(QPointF(len, p2.y() - p1.y()));
                  }
                  break;
            case SpannerSegmentType::BEGIN: {
                  System* s;
                  QPointF p1 = linePos(Grip::START, &s);
                  lineSegm->setPos(p1);
                  qreal x2 = system->bbox().right();
                  lineSegm->setPos2(QPointF(x2 - p1.x(), 0.0));
                  }
                  break;
            case SpannerSegmentType::MIDDLE: {
                  Measure* firstMeasure = system->firstMeasure();
                  Segment* firstCRSeg   = firstMeasure->first(SegmentType::ChordRest);
                  qreal x1              = (firstCRSeg ? firstCRSeg->pos().x() : 0) + firstMeasure->pos().x();
                  qreal x2              = system->bbox().right();
                  System* s;
                  QPointF p1 = linePos(Grip::START, &s);
                  lineSegm->setPos(QPointF(x1, p1.y()));
                  lineSegm->setPos2(QPointF(x2 - x1, 0.0));
                  }
                  break;
            case SpannerSegmentType::END: {
                  qreal offset = 0.0;
                  System* s;
                  QPointF p2 = linePos(Grip::END,   &s);
                  Measure* firstMeas  = system->firstMeasure();
                  Segment* firstCRSeg = firstMeas->first(SegmentType::ChordRest);
                  if (anchor() == Anchor::SEGMENT || anchor() == Anchor::MEASURE) {
                        // start line just after previous element (eg, key signature)
                        firstCRSeg = firstCRSeg->prev();
                        Element* e = firstCRSeg ? firstCRSeg->element(staffIdx() * VOICES) : nullptr;
                        if (e)
                              offset = e->width();
                        }
                  qreal x1  = (firstCRSeg ? firstCRSeg->pos().x() : 0) + firstMeas->pos().x() + offset;
                  qreal len = p2.x() - x1;
                  lineSegm->setPos(QPointF(p2.x() - len, p2.y()));
                  lineSegm->setPos2(QPointF(len, 0.0));
                  }
                  break;
            }
      lineSegm->layout();
      return lineSegm;
      }

//---------------------------------------------------------
//   layout
//    compute segments from tick1 tick2
//    (used for palette, edit mode, and layout of note lines and glissandi)
//---------------------------------------------------------

void SLine::layout()
      {
      if (score() == gscore || (tick() == Fraction(-1,1)) || (tick2() == Fraction::fromTicks(1))) {
            //
            // when used in a palette or while dragging from palette,
            // SLine has no parent and
            // tick and tick2 has no meaning so no layout is
            // possible and needed
            //
            setLen(gscore->spatium() * 7);
            if (!spannerSegments().empty()) {
                  LineSegment* lineSegm = frontSegment();
                  lineSegm->layout();
                  setbbox(lineSegm->bbox());
                  }
            return;
            }

      computeStartElement();
      computeEndElement();

      System* s1;
      System* s2;
      QPointF p1(linePos(Grip::START, &s1));
      QPointF p2(linePos(Grip::END,   &s2));

      const QList<System*>& systems = score()->systems();
      int sysIdx1 = systems.indexOf(s1);
      int sysIdx2 = systems.indexOf(s2);
      int segmentsNeeded = 0;

      if (sysIdx1 == -1 || sysIdx2 == -1)
            return;

      for (int i = sysIdx1; i <= sysIdx2;  ++i) {
            if (systems.at(i)->vbox())
                  continue;
            ++segmentsNeeded;
            }

      int segCount = int(spannerSegments().size());

      if (segmentsNeeded != segCount) {
            fixupSegments(segmentsNeeded, [this]() { return createLineSegment(); });
            if (segmentsNeeded > segCount) {
                  for (int i = segCount; i < segmentsNeeded; ++i) {
                        LineSegment* lineSegm = segmentAt(i);
                        // set user offset to previous segment's offset
                        if (segCount > 0)
                              lineSegm->setOffset(QPointF(0, segmentAt(i-1)->offset().y()));
                        else
                              lineSegm->setOffset(QPointF(0, offset().y()));
                        }
                  }
            }

      int segIdx = 0;
      for (int i = sysIdx1; i <= sysIdx2; ++i) {
            System* system = systems.at(i);
            if (system->vbox())
                  continue;
            LineSegment* lineSegm = segmentAt(segIdx++);
            lineSegm->setTrack(track());       // DEBUG
            lineSegm->setSystem(system);

            Measure* firstMeas = system->firstMeasure();
            Segment* firstCRSeg = firstMeas->first(SegmentType::ChordRest);

            if (sysIdx1 == sysIdx2) {
                  // single segment
                  lineSegm->setSpannerSegmentType(SpannerSegmentType::SINGLE);
                  qreal len = p2.x() - p1.x();
                  // enforcing a minimum length would be possible but inadvisable
                  // the line length calculations are tuned well enough that this should not be needed
                  //if (anchor() == Anchor::SEGMENT && type() != ElementType::PEDAL)
                  //      len = qMax(1.0 * spatium(), len);
                  lineSegm->setPos(p1);
                  lineSegm->setPos2(QPointF(len, p2.y() - p1.y()));
                  }
            else if (i == sysIdx1) {
                  // start segment
                  lineSegm->setSpannerSegmentType(SpannerSegmentType::BEGIN);
                  lineSegm->setPos(p1);
                  qreal x2 = system->bbox().right();
                  lineSegm->setPos2(QPointF(x2 - p1.x(), 0.0));
                  }
            else if (i > 0 && i != sysIdx2) {
                  // middle segment
                  lineSegm->setSpannerSegmentType(SpannerSegmentType::MIDDLE);
                  qreal x1 = (firstCRSeg ? firstCRSeg->pos().x() : 0) + firstMeas->pos().x();
                  qreal x2 = system->bbox().right();
                  lineSegm->setPos(QPointF(x1, p1.y()));
                  lineSegm->setPos2(QPointF(x2 - x1, 0.0));
                  }
            else if (i == sysIdx2) {
                  // end segment
                  qreal offset = 0.0;
                  qreal minLen = 0.0;
                  if (anchor() == Anchor::SEGMENT || anchor() == Anchor::MEASURE) {
                        // start line just after previous element (eg, key signature)
                        firstCRSeg = firstCRSeg->prev();
                        Element* e = firstCRSeg ? firstCRSeg->element(staffIdx() * VOICES) : nullptr;
                        if (e)
                              offset = e->width();
                        // enforcing a minimum length would be possible but inadvisable
                        // the line length calculations are tuned well enough that this should not be needed
                        //if (type() != ElementType::PEDAL)
                        //      minLen = 1.0 * spatium();
                        }
                  qreal x1 = (firstCRSeg ? firstCRSeg->pos().x() : 0) + firstMeas->pos().x() + offset;
                  qreal len = qMax(minLen, p2.x() - x1);
                  lineSegm->setSpannerSegmentType(SpannerSegmentType::END);
                  lineSegm->setPos(QPointF(p2.x() - len, p2.y()));
                  lineSegm->setPos2(QPointF(len, 0.0));
                  }
            lineSegm->layout();
            }
      }

//---------------------------------------------------------
//   writeProperties
//    write properties different from prototype
//---------------------------------------------------------

void SLine::writeProperties(XmlWriter& xml) const
      {
      if (!endElement()) {
            ((Spanner*)this)->computeEndElement();                // HACK
            if (!endElement())
                  xml.tag("ticks", ticks());
            }
      Spanner::writeProperties(xml);
      if (_diagonal)
            xml.tag("diagonal", _diagonal);
      writeProperty(xml, Pid::LINE_WIDTH);
      writeProperty(xml, Pid::LINE_STYLE);
      writeProperty(xml, Pid::COLOR);
      writeProperty(xml, Pid::ANCHOR);
      writeProperty(xml, Pid::DASH_LINE_LEN);
      writeProperty(xml, Pid::DASH_GAP_LEN);
      if (score() == gscore) {
            // when used as icon
            if (!spannerSegments().empty()) {
                  const LineSegment* s = frontSegment();
                  xml.tag("length", s->pos2().x());
                  }
            else
                  xml.tag("length", spatium() * 4);
            return;
            }
      //
      // check if user has modified the default layout
      //
      bool modified = false;
      for (const SpannerSegment* seg : spannerSegments()) {
            if (!seg->autoplace() || !seg->visible() ||
               (seg->propertyFlags(Pid::MIN_DISTANCE) == PropertyFlags::UNSTYLED || seg->getProperty(Pid::MIN_DISTANCE) != seg->propertyDefault(Pid::MIN_DISTANCE)) ||
               (!seg->isStyled(Pid::OFFSET) && (!seg->offset().isNull() || !seg->userOff2().isNull()))) {
                  modified = true;
                  break;
                  }
            }
      if (!modified)
            return;

      //
      // write user modified layout and other segment properties
      //
      qreal _spatium = score()->spatium();
      for (const SpannerSegment* seg : spannerSegments()) {
            xml.stag("Segment", seg);
            xml.tag("subtype", int(seg->spannerSegmentType()));
            // TODO:
            // NOSTYLE offset written in Element::writeProperties,
            // so we probably don't need to duplicate it here
            // see https://musescore.org/en/node/286848
            //if (seg->propertyFlags(Pid::OFFSET) & PropertyFlags::UNSTYLED)
            xml.tag("offset", seg->offset() / _spatium);
            xml.tag("off2", seg->userOff2() / _spatium);
            seg->writeProperty(xml, Pid::MIN_DISTANCE);
            seg->Element::writeProperties(xml);
            xml.etag();
            }
      }

//---------------------------------------------------------
//   readProperties
//---------------------------------------------------------

bool SLine::readProperties(XmlReader& e)
      {
      const QStringRef& tag(e.name());

      if (tag == "tick2") {                // obsolete
            if (tick() == Fraction(-1,1)) // not necessarily set (for first note of score?) #30151
                  setTick(e.tick());
            setTick2(Fraction::fromTicks(e.readInt()));
            }
      else if (tag == "tick")             // obsolete
            setTick(Fraction::fromTicks(e.readInt()));
      else if (tag == "ticks")
            setTicks(Fraction::fromTicks(e.readInt()));
      else if (tag == "Segment") {
            LineSegment* ls = createLineSegment();
            ls->setTrack(track()); // needed in read to get the right staff mag
            ls->read(e);
            add(ls);
            ls->setVisible(visible());
            }
      else if (tag == "length")
            setLen(e.readDouble());
      else if (tag == "diagonal")
            setDiagonal(e.readInt());
      else if (tag == "anchor")
            setAnchor(Anchor(e.readInt()));
      else if (tag == "lineWidth")
            _lineWidth = e.readDouble() * spatium();
      else if (tag == "lineStyle")
            _lineStyle = Qt::PenStyle(e.readInt());
      else if (tag == "dashLineLength")
            _dashLineLen = e.readDouble();
      else if (tag == "dashGapLength")
            _dashGapLen = e.readDouble();
      else if (tag == "lineColor")
            _lineColor = e.readColor();
      else if (tag == "color")
            _lineColor = e.readColor();
      else if (!Spanner::readProperties(e))
            return false;
      return true;
      }

//---------------------------------------------------------
//   setLen
//    used to create an element suitable for palette
//---------------------------------------------------------

void SLine::setLen(qreal l)
      {
      if (spannerSegments().empty())
            add(createLineSegment());
      LineSegment* s = frontSegment();
      s->setPos(QPointF());
      s->setPos2(QPointF(l, 0));
      }

//---------------------------------------------------------
//   bbox
//    used by palette: only one segment
//---------------------------------------------------------

const QRectF& SLine::bbox() const
      {
      if (spannerSegments().empty())
            setbbox(QRectF());
      else
            setbbox(segmentAt(0)->bbox());
      return Element::bbox();
      }

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

void SLine::write(XmlWriter& xml) const
      {
      xml.stag(this);
      SLine::writeProperties(xml);
      xml.etag();
      }

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

void SLine::read(XmlReader& e)
      {
      eraseSpannerSegments();

      if (score()->mscVersion() < 301)
            e.addSpanner(e.intAttribute("id", -1), this);

      while (e.readNextStartElement()) {
            if (!SLine::readProperties(e))
                  e.unknown();
            }
      }

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

QVariant SLine::getProperty(Pid id) const
      {
      switch (id) {
            case Pid::DIAGONAL:
                  return _diagonal;
            case Pid::COLOR:
                  return _lineColor;
            case Pid::LINE_WIDTH:
                  return _lineWidth;
            case Pid::LINE_STYLE:
                  return QVariant(int(_lineStyle));
            case Pid::DASH_LINE_LEN:
                  return dashLineLen();
            case Pid::DASH_GAP_LEN:
                  return dashGapLen();
            default:
                  return Spanner::getProperty(id);
            }
      }

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

bool SLine::setProperty(Pid id, const QVariant& v)
      {
      switch (id) {
            case Pid::DIAGONAL:
                  _diagonal = v.toBool();
                  break;
            case Pid::COLOR:
                  _lineColor = v.value<QColor>();
                  break;
            case Pid::LINE_WIDTH:
                  _lineWidth = v.toReal();
                  break;
            case Pid::LINE_STYLE:
                  _lineStyle = Qt::PenStyle(v.toInt());
                  break;
            case Pid::DASH_LINE_LEN:
                  setDashLineLen(v.toDouble());
                  break;
            case Pid::DASH_GAP_LEN:
                  setDashGapLen(v.toDouble());
                  break;
            default:
                  return Spanner::setProperty(id, v);
            }
      triggerLayout();
      return true;
      }

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

QVariant SLine::propertyDefault(Pid pid) const
      {
      switch (pid) {
            case Pid::DIAGONAL:
                  return false;
            case Pid::COLOR:
                  return MScore::defaultColor;
            case Pid::LINE_WIDTH:
                  if (propertyFlags(pid) != PropertyFlags::NOSTYLE)
                        return Spanner::propertyDefault(pid);
                  return 0.15 * spatium();
            case Pid::LINE_STYLE:
                  if (propertyFlags(pid) != PropertyFlags::NOSTYLE)
                        return Spanner::propertyDefault(pid);
                  return int(Qt::SolidLine);
            case Pid::DASH_LINE_LEN:
            case Pid::DASH_GAP_LEN:
                  if (propertyFlags(pid) != PropertyFlags::NOSTYLE)
                        return Spanner::propertyDefault(pid);
                  return 5.0;
            default:
                  return Spanner::propertyDefault(pid);
            }
      }

}

