MuseScore/libmscore/line.cpp
Nicolas Froment fd51063ec1 Merge pull request #1753 from MarcSabatella/46736-spanner-voice-2
fix #46736: spanner end cannot be set to note in voice > 1
2015-02-22 09:36:25 +01:00

1057 lines
41 KiB
C++

//=============================================================================
// 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 "score.h"
#include "segment.h"
#include "staff.h"
#include "system.h"
#include "textline.h"
#include "utils.h"
#include "xml.h"
namespace Ms {
//---------------------------------------------------------
// LineSegment
//---------------------------------------------------------
LineSegment::LineSegment(const LineSegment& s)
: SpannerSegment(s)
{
_p2 = s._p2;
_userOff2 = s._userOff2;
}
//---------------------------------------------------------
// readProperties
//---------------------------------------------------------
bool LineSegment::readProperties(XmlReader& e)
{
const QStringRef& tag(e.name());
if (tag == "subtype")
setSpannerSegmentType(SpannerSegmentType(e.readInt()));
else if (tag == "off1") // off1 is obsolete
setUserOff(e.readPoint() * spatium());
else if (tag == "off2")
setUserOff2(e.readPoint() * spatium());
else if (tag == "pos") {
if (score()->mscVersion() > 114) {
qreal _spatium = score()->spatium();
setUserOff(QPointF());
setReadPos(e.readPoint() * _spatium);
if (e.pasteMode()) // x position will be wrong
setReadPos(QPointF());
}
else
e.readNext();
}
else if (!SpannerSegment::readProperties(e)) {
e.unknown();
return false;
}
return true;
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
void LineSegment::read(XmlReader& e)
{
while (e.readNextStartElement())
readProperties(e);
}
//---------------------------------------------------------
// updateGrips
//---------------------------------------------------------
void LineSegment::updateGrips(Grip* defaultGrip, QVector<QRectF>& grip) const
{
*defaultGrip = Grip::END;
QPointF pp(pagePos());
grip[int(Grip::START)].translate(pp);
grip[int(Grip::END)].translate(pos2() + pp);
grip[int(Grip::MIDDLE)].translate(pos2() * .5 + pp);
}
//---------------------------------------------------------
// setGrip
//---------------------------------------------------------
void LineSegment::setGrip(Grip grip, const QPointF& p)
{
QPointF pt(p * spatium());
switch (grip) {
case Grip::START: {
QPointF delta(pt - userOff());
setUserOff(pt);
setUserOff2(userOff2() - delta);
}
break;
case Grip::END:
setUserOff2(pt);
break;
case Grip::MIDDLE:
setUserOff(pt);
break;
case Grip::APERTURE:
default:
break;
}
layout(); // needed?
}
//---------------------------------------------------------
// getGrip
//---------------------------------------------------------
QPointF LineSegment::getGrip(Grip grip) const
{
QPointF p;
switch (grip) {
case Grip::START:
p = userOff();
break;
case Grip::END:
p = userOff2();
break;
case Grip::MIDDLE:
p = userOff();
break;
case Grip::APERTURE:
default:
break;
}
p /= spatium();
return p;
}
//---------------------------------------------------------
// gripAnchor
// return page coordinates
//---------------------------------------------------------
QPointF LineSegment::gripAnchor(Grip grip) const
{
qreal y = system()->staffYpage(staffIdx());
if (spannerSegmentType() == SpannerSegmentType::MIDDLE) {
qreal x;
switch (grip) {
case Grip::START:
x = system()->firstMeasure()->abbox().left();
break;
case Grip::END:
x = system()->lastMeasure()->abbox().right();
break;
default:
case Grip::MIDDLE:
case Grip::APERTURE:
x = 0; // No Anchor
y = 0;
break;
}
return QPointF(x, y);
}
else {
if ((grip == Grip::MIDDLE || grip == Grip::APERTURE) // center grip or aperture grip
|| (grip == Grip::END && spannerSegmentType() == SpannerSegmentType::BEGIN)
|| (grip == Grip::START && spannerSegmentType() == SpannerSegmentType::END)
)
return QPointF(0, 0);
else {
System* s;
QPointF p(line()->linePos(grip, &s));
p.ry() += y - system()->pos().y();
if (s)
p += s->pos();
return p;
}
}
}
//---------------------------------------------------------
// edit
// return true if event is accepted
//---------------------------------------------------------
bool LineSegment::edit(MuseScoreView* sv, Grip curGrip, int key, Qt::KeyboardModifiers modifiers, const QString&)
{
if (!((modifiers & Qt::ShiftModifier)
&& ((spannerSegmentType() == SpannerSegmentType::SINGLE)
|| (spannerSegmentType() == SpannerSegmentType::BEGIN && curGrip == Grip::START)
|| (spannerSegmentType() == SpannerSegmentType::END && curGrip == Grip::END))))
return false;
LineSegment* ls = 0;
SLine* l = line();
SpannerSegmentType st = spannerSegmentType();
int track = l->track();
int track2 = l->track2(); // assumed to be same as track
if (l->anchor() == 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 (key == Qt::Key_Left) {
if (curGrip == Grip::START)
s1 = prevSeg1(s1, track);
else if (curGrip == Grip::END || curGrip == Grip::MIDDLE)
s2 = prevSeg1(s2, track2);
}
else if (key == Qt::Key_Right) {
if (curGrip == Grip::START)
s1 = nextSeg1(s1, track);
else if (curGrip == Grip::END || curGrip == Grip::MIDDLE) {
Segment* ns2 = nextSeg1(s2, track2);
if (ns2)
s2 = ns2;
else
s2 = score()->lastSegment();
}
}
if (s1 == 0 || s2 == 0 || s1->tick() >= s2->tick())
return true;
if (s1->tick() != spanner()->tick())
spanner()->setTick(s1->tick());
if (s2->tick() != spanner()->tick2())
spanner()->setTick2(s2->tick());
}
else {
Measure* m1 = l->startMeasure();
Measure* m2 = l->endMeasure();
if (key == Qt::Key_Left) {
if (curGrip == Grip::START) {
if (m1->prevMeasure())
m1 = m1->prevMeasure();
}
else if (curGrip == Grip::END || curGrip == Grip::MIDDLE) {
Measure* m = m2->prevMeasure();
if (m)
m2 = m;
}
}
else if (key == Qt::Key_Right) {
if (curGrip == Grip::START) {
if (m1->nextMeasure())
m1 = m1->nextMeasure();
}
else if (curGrip == Grip::END || curGrip == Grip::MIDDLE) {
if (m2->nextMeasure())
m2 = m2->nextMeasure();
}
}
if (m1->tick() > m2->tick())
return true;
if (l->startElement() != m1) {
l->setTick(m1->tick());
l->setTicks(m2->endTick() - m1->tick());
}
else if (l->endElement() != m2) {
l->setTicks(m2->endTick() - m1->tick());
}
}
_score->doLayout(); // needed to compute multi measure rests
// l->layout();
LineSegment* nls = 0;
if (st == SpannerSegmentType::SINGLE) {
if (curGrip == Grip::START)
nls = l->frontSegment();
else if (curGrip == Grip::END)
nls = l->backSegment();
}
else if (st == SpannerSegmentType::BEGIN)
nls = l->frontSegment();
else if (st == SpannerSegmentType::END)
nls = l->backSegment();
if (nls && (nls != this))
sv->changeEditElement(nls);
if (ls)
_score->undoRemoveElement(ls);
_score->setLayoutAll(true);
return true;
}
//---------------------------------------------------------
// editDrag
//---------------------------------------------------------
void LineSegment::editDrag(const EditData& ed)
{
// Only for resizing according to the diagonal properties
QPointF deltaResize(ed.delta.x(), line()->diagonal() ? ed.delta.y() : 0.0);
// Only for moving, no y limitaion
QPointF deltaMove(ed.delta.x(), ed.delta.y());
switch (ed.curGrip) {
case Grip::START: // Resize the begin of element (left grip)
setUserOff(userOff() + deltaResize);
_userOff2 -= deltaResize;
break;
case Grip::END: // Resize the end of element (rigth grip)
_userOff2 += deltaResize;
break;
case Grip::MIDDLE: // Move the element (middle grip)
setUserOff(userOff() + deltaMove);
break;
default:
break;
}
if ((line()->anchor() == Spanner::Anchor::NOTE)
&& (ed.curGrip == Grip::START || ed.curGrip == Grip::END)) {
//
// if we touch a different note, change anchor
//
Element* e = ed.view->elementNear(ed.pos);
if (e && e->type() == Element::Type::NOTE) {
SLine* l = line();
if (ed.curGrip == Grip::END && e != line()->endElement()) {
qDebug("LineSegment: move end anchor");
Note* noteOld = static_cast<Note*>(l->endElement());
Note* noteNew = static_cast<Note*>(e);
noteOld->removeSpannerBack(l);
noteNew->addSpannerBack(l);
l->setEndElement(noteNew);
_userOff2 += noteOld->canvasPos() - noteNew->canvasPos();
}
else if (ed.curGrip == Grip::START && e != l->startElement()) {
qDebug("LineSegment: move start anchor (not impl.)");
}
}
}
line()->layout();
}
//---------------------------------------------------------
// spatiumChanged
//---------------------------------------------------------
void LineSegment::spatiumChanged(qreal ov, qreal nv)
{
Element::spatiumChanged(ov, nv);
_userOff2 *= nv / ov;
}
//---------------------------------------------------------
// localSpatiumChanged
//---------------------------------------------------------
void LineSegment::localSpatiumChanged(qreal ov, qreal nv)
{
Element::localSpatiumChanged(ov, nv);
_userOff2 *= nv / ov;
}
//---------------------------------------------------------
// getProperty
//---------------------------------------------------------
QVariant LineSegment::getProperty(P_ID id) const
{
switch (id) {
case P_ID::DIAGONAL:
case P_ID::LINE_COLOR:
case P_ID::LINE_WIDTH:
case P_ID::LINE_STYLE:
return line()->getProperty(id);
default:
return SpannerSegment::getProperty(id);
}
}
//---------------------------------------------------------
// setProperty
//---------------------------------------------------------
bool LineSegment::setProperty(P_ID id, const QVariant& val)
{
switch (id) {
case P_ID::DIAGONAL:
case P_ID::LINE_COLOR:
case P_ID::LINE_WIDTH:
case P_ID::LINE_STYLE:
return line()->setProperty(id, val);
default:
return SpannerSegment::setProperty(id, val);
}
}
//---------------------------------------------------------
// propertyDefault
//---------------------------------------------------------
QVariant LineSegment::propertyDefault(P_ID id) const
{
return line()->propertyDefault(id);
}
//---------------------------------------------------------
// dragAnchor
//---------------------------------------------------------
QLineF LineSegment::dragAnchor() const
{
if (spannerSegmentType() != SpannerSegmentType::SINGLE && spannerSegmentType() != SpannerSegmentType::BEGIN)
return QLineF();
System* s;
QPointF p = line()->linePos(Grip::START, &s);
p += QPointF(s->canvasPos().x(), s->staffYpage(line()->staffIdx()));
return QLineF(p, canvasPos());
}
//---------------------------------------------------------
// SLine
//---------------------------------------------------------
SLine::SLine(Score* s)
: Spanner(s)
{
_diagonal = false;
_lineColor = MScore::defaultColor;
_lineWidth = Spatium(0.15);
_lineStyle = Qt::SolidLine;
setTrack(0);
}
SLine::SLine(const SLine& s)
: Spanner(s)
{
_diagonal = s._diagonal;
_lineWidth = s._lineWidth;
_lineColor = s._lineColor;
_lineStyle = s._lineStyle;
}
//---------------------------------------------------------
// linePos
// return System/Staff coordinates
//---------------------------------------------------------
QPointF SLine::linePos(Grip grip, System** sys) const
{
qreal x = 0.0;
qreal sp = staff()->spatium();
switch (anchor()) {
case Spanner::Anchor::SEGMENT:
{
ChordRest* cr;
if (grip == Grip::START) {
cr = static_cast<ChordRest*>(startElement());
if (cr) {
// some sources say to center the text over the note head
// some say to start the text just to left of notehead
// our simple compromise - left align
if (cr->durationType() == TDuration::DurationType::V_MEASURE && type() == Element::Type::OTTAVA)
x = cr->x(); // center for measure rests
else if (cr->space().lw() > 0.0)
x = -cr->space().lw(); // account for accidentals, etc
}
}
else {
cr = static_cast<ChordRest*>(endElement());
if (type() == Element::Type::OTTAVA) {
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
// note: the cheap solution is to simply use segment width,
// but that would account for notes in other staves unnecessarily
// and in any event, segment bboxes seem unreliable
qreal width = 0;
Segment* s = cr->segment();
int n = staffIdx() * VOICES;
for (int i = 0; i < VOICES; ++i) {
ChordRest* vcr = static_cast<ChordRest*>(s->element(n + i));
if (vcr)
width = qMax(width, vcr->space().rw());
}
// extend past chord/rest
x = width + sp;
// but don't overlap next chord/rest
Segment* ns = s->next();
bool crFound = false;
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().val() * sp;
if (x > nextNoteDistance)
x = qMax(width, nextNoteDistance);
}
}
}
else if (type() == Element::Type::LYRICSLINE && static_cast<Lyrics*>(parent())->ticks() > 0) {
// 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());
else if (cr->track() != track()) {
Element* e = cr->segment()->element(track());
if (e)
cr = static_cast<ChordRest*>(e);
}
// layout to right edge of CR
if (cr) {
qreal maxRight = 0.0;
if (cr->type() == Element::Type::CHORD) {
// chord bbox() is unreliable, look at notes
// this also allows us to more easily ignore ledger lines
for (Note* n : static_cast<Chord*>(cr)->notes())
maxRight = qMax(maxRight, cr->x() + n->x() + n->headWidth());
}
else {
// rest - won't normally happen
maxRight = cr->x() + cr->width();
}
x = maxRight; // cr->width()
}
}
else if (type() == Element::Type::HAIRPIN || type() == Element::Type::TRILL
|| type() == Element::Type::TEXTLINE || type() == Element::Type::LYRICSLINE) {
// (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() == Element::Type::SEGMENT) {
qreal x2 = cr->x() + cr->space().rw();
Segment* currentSeg = static_cast<Segment*>(endElement()->parent());
Segment* seg = score()->tick2segmentMM(tick2(), false, Segment::Type::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(Segment::Type::EndBarLine);
if (!seg)
seg = currentSeg->measure()->last();
// allow lyrics hyphen to extend to barline
// other lines stop 1sp short
qreal gap = (type() == Element::Type::LYRICSLINE) ? 0.0 : sp;
x2 = qMax(x2, seg->x() - gap);
}
x = x2 - endElement()->parent()->x();
}
}
}
int 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/key
qreal offset = 0.0;
Segment* s = m->first(Segment::Type::ChordRest);
if (s) {
s = s->prev();
if (s) {
offset = s->x();
Element* e = s->element(staffIdx() * VOICES);
if (e)
offset += e->width();
}
}
x = m->pos().x() + offset;
if (score()->styleB(StyleIdx::createMultiMeasureRests) && m->hasMMRest()) {
x = m->mmRest()->pos().x();
}
}
else {
qreal _spatium = spatium();
m = endMeasure();
x = m->pos().x() + m->bbox().right();
if (score()->styleB(StyleIdx::createMultiMeasureRests)) {
//find the actual measure where the volta should stop
Measure* sm = startMeasure();
Measure* m = sm;
if (sm->hasMMRest())
m = sm->mmRest();
while (m->nextMeasureMM() && (m->endTick() < tick2()))
m = m->nextMeasureMM();
x = m->pos().x() + m->bbox().right();
}
Segment* seg = m->last();
if (seg->segmentType() == Segment::Type::EndBarLine) {
Element* e = seg->element(0);
if (e && e->type() == Element::Type::BAR_LINE) {
if (static_cast<BarLine*>(e)->barLineType() == BarLineType::START_REPEAT)
x -= e->width() - _spatium * .5;
else
x -= _spatium * .5;
}
}
}
if (score()->styleB(StyleIdx::createMultiMeasureRests))
m = m->mmRest1();
Q_ASSERT(m->system());
*sys = m->system();
}
break;
case Spanner::Anchor::NOTE:
{
// System* s = static_cast<Note*>(startElement())->chord()->segment()->system();
// *sys = s;
Element* e = grip == Grip::START ? startElement() : endElement();
System* s = static_cast<Note*>(e)->chord()->segment()->system();
*sys = s;
// for GLISSANDO returns the position of the anchor note relative to the system
// for others, returns the position of the anchor note relative to the staff
// QPointF elemPagePos = e->pagePos(); // DEBUG
// QPointF systPagePos = s->pagePos();
// qreal staffYPage = s->staffYpage(e->staffIdx());
return e->pagePos() -
(type() == Element::Type::GLISSANDO ? s->pagePos()
: QPointF(s->pagePos().x(), s->staffYpage(e->staffIdx())) );
}
case Spanner::Anchor::CHORD:
qFatal("Sline::linePos(): anchor not implemented");
break;
}
return QPointF(x, 0.0);
}
//---------------------------------------------------------
// layout
// compute segments from tick1 tick2
//---------------------------------------------------------
void SLine::layout()
{
if (score() == gscore || tick() == -1 || tick2() == 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
//
if (!spannerSegments().isEmpty()) {
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));
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+1; ++i) {
if (systems->at(i)->isVbox())
continue;
++segmentsNeeded;
}
int segCount = spannerSegments().size();
if (segmentsNeeded != segCount) {
if (segmentsNeeded > segCount) {
int n = segmentsNeeded - segCount;
for (int i = 0; i < n; ++i) {
LineSegment* lineSegm = createLineSegment();
add(lineSegm);
// set user offset to previous segment's offset
if (segCount > 0)
lineSegm->setUserOff(QPointF(0, segmentAt(segCount+i-1)->userOff().y()));
else
lineSegm->setUserOff(QPointF(0, userOff().y()));
}
}
else {
int n = segCount - segmentsNeeded;
// qDebug("SLine: segments %d needed %d, remove %d", segCount, segmentsNeeded, n);
for (int i = 0; i < n; ++i) {
if (spannerSegments().isEmpty()) {
qDebug("SLine::layout(): no segment %d, %d expected", i, n);
break;
}
else {
/*LineSegment* lineSegm =*/ takeLastSegment();
// delete lineSegm;
}
}
}
}
int segIdx = 0;
for (int i = sysIdx1; i <= sysIdx2; ++i) {
System* system = systems->at(i);
if (system->isVbox())
continue;
LineSegment* lineSegm = segmentAt(segIdx++);
lineSegm->setTrack(track()); // DEBUG
lineSegm->setSystem(system);
Measure* firstMeas = system->firstMeasure();
Segment* firstCRSeg = firstMeas->first(Segment::Type::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() != Element::Type::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() != Element::Type::PEDAL)
// minLen = 1.0 * spatium();
}
// qreal firstCRSegX = firstCRSeg ? firstCRSeg->pos().x() : 0; // DEBUG
// qreal firstMeasX = firstMeas ? firstMeas->pos().x() : 0;
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();
}
adjustReadPos();
}
//---------------------------------------------------------
// writeProperties
// write properties different from prototype
//---------------------------------------------------------
void SLine::writeProperties(Xml& xml) const
{
if (!endElement()) {
xml.tag("ticks", ticks());
}
Spanner::writeProperties(xml);
if (_diagonal)
xml.tag("diagonal", _diagonal);
if (propertyStyle(P_ID::LINE_WIDTH) != PropertyStyle::STYLED)
xml.tag("lineWidth", lineWidth().val());
if (propertyStyle(P_ID::LINE_STYLE) == PropertyStyle::UNSTYLED || (lineStyle() != Qt::SolidLine))
if (propertyStyle(P_ID::LINE_STYLE) != PropertyStyle::STYLED)
xml.tag("lineStyle", int(lineStyle()));
if (propertyStyle(P_ID::LINE_COLOR) == PropertyStyle::UNSTYLED || (lineColor() != MScore::defaultColor))
xml.tag("lineColor", lineColor());
writeProperty(xml, P_ID::ANCHOR);
if (score() == gscore) {
// when used as icon
if (!spannerSegments().isEmpty()) {
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;
int n = spannerSegments().size();
for (int i = 0; i < n; ++i) {
const LineSegment* seg = segmentAt(i);
if (!seg->userOff().isNull()
|| !seg->userOff2().isNull()
|| !seg->visible()) {
modified = true;
break;
}
}
if (!modified)
return;
//
// write user modified layout
//
qreal _spatium = spatium();
for (int i = 0; i < n; ++i) {
const LineSegment* seg = segmentAt(i);
xml.stag("Segment");
xml.tag("subtype", int(seg->spannerSegmentType()));
xml.tag("off2", seg->userOff2() / _spatium);
seg->Element::writeProperties(xml);
xml.etag();
}
}
//---------------------------------------------------------
// readProperties
//---------------------------------------------------------
bool SLine::readProperties(XmlReader& e)
{
const QStringRef& tag(e.name());
if (tag == "tick2") { // obsolete
if (tick() == -1) // not necessarily set (for first note of score?) #30151
setTick(e.tick());
setTick2(e.readInt());
}
else if (tag == "tick") // obsolete
setTick(e.readInt());
else if (tag == "ticks")
setTicks(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);
// in v1.x "visible" is a property of the segment only;
// we must ensure that it propagates also to the parent element.
// That's why the visibility is set after adding the segment
// to the corresponding spanner
if (score()->mscVersion() <= 114)
ls->setVisible(ls->visible());
else
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 = Spatium(e.readDouble());
else if (tag == "lineStyle")
_lineStyle = Qt::PenStyle(e.readInt());
else if (tag == "lineColor")
_lineColor = e.readColor();
else if (Element::readProperties(e))
;
else
return false;
return true;
}
//---------------------------------------------------------
// setLen
// used to create an element suitable for palette
//---------------------------------------------------------
void SLine::setLen(qreal l)
{
if (spannerSegments().isEmpty())
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().isEmpty())
setbbox(QRectF());
else
setbbox(segmentAt(0)->bbox());
return Element::bbox();
}
//---------------------------------------------------------
// write
//---------------------------------------------------------
void SLine::write(Xml& xml) const
{
int id = xml.spannerId(this);
xml.stag(QString("%1 id=\"%2\"").arg(name()).arg(id));
SLine::writeProperties(xml);
xml.etag();
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
void SLine::read(XmlReader& e)
{
foreach(SpannerSegment* seg, spannerSegments())
delete seg;
spannerSegments().clear();
e.addSpanner(e.intAttribute("id", -1), this);
while (e.readNextStartElement()) {
if (!SLine::readProperties(e))
e.unknown();
}
}
//---------------------------------------------------------
// getProperty
//---------------------------------------------------------
QVariant SLine::getProperty(P_ID id) const
{
switch (id) {
case P_ID::DIAGONAL:
return _diagonal;
case P_ID::LINE_COLOR:
return _lineColor;
case P_ID::LINE_WIDTH:
return _lineWidth.val();
case P_ID::LINE_STYLE:
return QVariant(int(_lineStyle));
default:
return Spanner::getProperty(id);
}
}
//---------------------------------------------------------
// setProperty
//---------------------------------------------------------
bool SLine::setProperty(P_ID id, const QVariant& v)
{
switch (id) {
case P_ID::DIAGONAL:
_diagonal = v.toBool();
break;
case P_ID::LINE_COLOR:
_lineColor = v.value<QColor>();
break;
case P_ID::LINE_WIDTH:
_lineWidth = Spatium(v.toDouble());
break;
case P_ID::LINE_STYLE:
_lineStyle = Qt::PenStyle(v.toInt());
break;
default:
return Spanner::setProperty(id, v);
}
return true;
}
//---------------------------------------------------------
// propertyDefault
//---------------------------------------------------------
QVariant SLine::propertyDefault(P_ID id) const
{
switch (id) {
case P_ID::DIAGONAL:
return false;
case P_ID::LINE_COLOR:
return MScore::defaultColor;
case P_ID::LINE_WIDTH:
return 0.15;
case P_ID::LINE_STYLE:
return int(Qt::SolidLine);
default:
return Spanner::propertyDefault(id);
}
}
}