MuseScore/libmscore/line.cpp
2020-05-29 21:15:25 +02:00

1546 lines
52 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//=============================================================================
// 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;
// dont 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);
}
}
}