//============================================================================= // 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) { _p2 = s._p2; _userOff2 = s._userOff2; } //--------------------------------------------------------- // startEdit //--------------------------------------------------------- void LineSegment::startEdit(EditData& ed) { ed.grips = 3; ed.curGrip = Grip::END; Element::startEdit(ed); } //--------------------------------------------------------- // 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() * spatium()); if (!userOff2().isNull()) setAutoplace(false); } else if (tag == "pos") { qreal _spatium = score()->spatium(); setUserOff(QPointF()); setReadPos(e.readPoint() * _spatium); if (e.pasteMode()) // x position will be wrong setReadPos(QPointF()); setAutoplace(false); } 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(EditData& ed) const { QPointF pp(pagePos()); ed.grip[int(Grip::START)].translate(pp); ed.grip[int(Grip::END)].translate(pos2() + pp); ed.grip[int(Grip::MIDDLE)].translate(pos2() * .5 + pp); } //--------------------------------------------------------- // gripAnchor // return page coordinates //--------------------------------------------------------- QPointF LineSegment::gripAnchor(Grip grip) const { // Middle or aperture grip have no anchor if (!system() || grip == Grip::MIDDLE || grip == Grip::APERTURE) return QPointF(0, 0); // note-anchored spanners are relative to the system qreal y = spanner()->anchor() == Spanner::Anchor::NOTE ? system()->pos().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: x = 0; // No Anchor y = 0; break; } return QPointF(x, y); } else { if ((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(); // to page coordinates return p; } } } //--------------------------------------------------------- // startEditDrag //--------------------------------------------------------- void LineSegment::startEditDrag(EditData& ed) { ElementEditData* eed = ed.getData(this); eed->pushProperty(P_ID::USER_OFF); eed->pushProperty(P_ID::USER_OFF2); } //--------------------------------------------------------- // edit // return true if event is accepted //--------------------------------------------------------- bool LineSegment::edit(EditData& ed) { if (!((ed.modifiers & Qt::ShiftModifier) && ((spannerSegmentType() == SpannerSegmentType::SINGLE) || (spannerSegmentType() == SpannerSegmentType::BEGIN && ed.curGrip == Grip::START) || (spannerSegmentType() == SpannerSegmentType::END && ed.curGrip == Grip::END)))) 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 (ed.curGrip == Grip::START) s1 = prevSeg1(s1, track); else if (ed.curGrip == Grip::END || ed.curGrip == Grip::MIDDLE) s2 = prevSeg1(s2, track2); } else if (ed.key == Qt::Key_Right) { if (ed.curGrip == Grip::START) s1 = nextSeg1(s1, track); else if (ed.curGrip == Grip::END || ed.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; spanner()->undoChangeProperty(P_ID::SPANNER_TICK, s1->tick()); spanner()->undoChangeProperty(P_ID::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 (ed.curGrip == Grip::START) note1 = prevChordNote(note1); else if (ed.curGrip == Grip::END || ed.curGrip == Grip::MIDDLE) note2 = prevChordNote(note2); break; case Qt::Key_Right: if (ed.curGrip == Grip::START) note1 = nextChordNote(note1); else if (ed.curGrip == Grip::END || ed.curGrip == Grip::MIDDLE) note2 = nextChordNote(note2); break; case Qt::Key_Up: if (ed.curGrip == Grip::START) note1 = toNote(score()->upAlt(note1)); else if (ed.curGrip == Grip::END || ed.curGrip == Grip::MIDDLE) note2 = toNote(score()->upAlt(note2)); break; case Qt::Key_Down: if (ed.curGrip == Grip::START) note1 = toNote(score()->downAlt(note1)); else if (ed.curGrip == Grip::END || ed.curGrip == Grip::MIDDLE) 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) spanner()->setNoteSpan(note1, note2); // set new spanner span } break; default: { Measure* m1 = l->startMeasure(); Measure* m2 = l->endMeasure(); if (ed.key == Qt::Key_Left) { if (ed.curGrip == Grip::START) { if (m1->prevMeasure()) m1 = m1->prevMeasure(); } else if (ed.curGrip == Grip::END || ed.curGrip == Grip::MIDDLE) { Measure* m = m2->prevMeasure(); if (m) m2 = m; } } else if (ed.key == Qt::Key_Right) { if (ed.curGrip == Grip::START) { if (m1->nextMeasure()) m1 = m1->nextMeasure(); } else if (ed.curGrip == Grip::END || ed.curGrip == Grip::MIDDLE) { if (m2->nextMeasure()) m2 = m2->nextMeasure(); } } if (m1->tick() > m2->tick()) return true; if (l->startElement() != m1) { spanner()->undoChangeProperty(P_ID::SPANNER_TICK, m1->tick()); spanner()->undoChangeProperty(P_ID::SPANNER_TICKS, m2->endTick() - m1->tick()); } else if (l->endElement() != m2) { spanner()->undoChangeProperty(P_ID::SPANNER_TICKS, m2->endTick() - m1->tick()); } } } triggerLayout(); l->layout(); // recompute segment list, segment type may change LineSegment* nls = 0; if (st == SpannerSegmentType::SINGLE) { if (ed.curGrip == Grip::START) nls = l->frontSegment(); else if (ed.curGrip == Grip::END) 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; } //--------------------------------------------------------- // editDrag //--------------------------------------------------------- void LineSegment::editDrag(EditData& ed) { // Only for resizing according to the diagonal properties QPointF deltaResize(ed.delta.x(), line()->diagonal() ? ed.delta.y() : 0.0); switch (ed.curGrip) { case Grip::START: // Resize the begin of element (left grip) setUserOff(userOff() + deltaResize); _userOff2 -= deltaResize; undoChangeProperty(P_ID::AUTOPLACE, false); break; case Grip::END: // Resize the end of element (rigth grip) _userOff2 += deltaResize; undoChangeProperty(P_ID::AUTOPLACE, false); break; case Grip::MIDDLE: { // Move the element (middle grip) // Only for moving, no y limitaion QPointF deltaMove(ed.delta.x(), ed.delta.y()); setUserOff(userOff() + deltaMove); undoChangeProperty(P_ID::AUTOPLACE, false); } 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()) { 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.)"); } } triggerLayout(); } //--------------------------------------------------------- // 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: case P_ID::DASH_LINE_LEN: case P_ID::DASH_GAP_LEN: 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: case P_ID::DASH_LINE_LEN: case P_ID::DASH_GAP_LEN: return line()->setProperty(id, val); default: return SpannerSegment::setProperty(id, val); } } //--------------------------------------------------------- // propertyDefault //--------------------------------------------------------- QVariant LineSegment::propertyDefault(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: case P_ID::DASH_LINE_LEN: case P_ID::DASH_GAP_LEN: return line()->propertyDefault(id); default: return SpannerSegment::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->staffCanvasYpage(line()->staffIdx())); return QLineF(p, canvasPos()); } //--------------------------------------------------------- // SLine //--------------------------------------------------------- SLine::SLine(Score* s) : Spanner(s) { setTrack(0); } 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 // Other: return (x position (relative to what?), 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 (type() == ElementType::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 Segment* s = cr->segment(); qreal width = s->staffShape(staffIdx()).right(); 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().val() * sp; if (x > nextNoteDistance) x = qMax(width, nextNoteDistance); } } } else if (isLyricsLine() && toLyrics(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 = toChordRest(e); } // layout to right edge of CR if (cr) { qreal maxRight = 0.0; if (cr->type() == ElementType::CHORD) { // chord bbox() is unreliable, look at notes // this also allows us to more easily ignore ledger lines for (Note* n : toChord(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() == ElementType::HAIRPIN || type() == ElementType::TRILL || type() == ElementType::VIBRATO || type() == ElementType::TEXTLINE || type() == ElementType::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() == 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; 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(SegmentType::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(); if (score()->styleB(StyleIdx::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(); qreal mwidth = seg ? seg->x() : m->bbox().right(); x = m->pos().x() + mwidth; // align to barline if (seg && seg->segmentType() == SegmentType::EndBarLine) { 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(StyleIdx::endBarDistance).val() * _spatium; // fall through case BarLineType::DOUBLE: // center on leftmost (thinner) barline x += score()->styleS(StyleIdx::doubleBarWidth).val() * _spatium * 0.5; break; case BarLineType::START_REPEAT: // center on leftmost (thicker) barline x += score()->styleS(StyleIdx::endBarWidth).val() * _spatium * 0.5; break; default: // center on barline x += score()->styleS(StyleIdx::barWidth).val() * _spatium * 0.5; break; } } } } if (score()->styleB(StyleIdx::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(); System* s = toNote(e)->chord()->segment()->system(); if (s == 0) { qFatal("no system: %s start %s chord parent %s\n", name(), e->name(), toNote(e)->chord()->parent()->name()); } *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()); return e->pagePos() - s->pagePos(); } 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) { int stick = system->firstMeasure()->tick(); int etick = system->lastMeasure()->endTick(); LineSegment* lineSegm = 0; for (SpannerSegment* ss : segments) { if (!ss->system()) { lineSegm = toLineSegment(ss); break; } } if (!lineSegm) { lineSegm = createLineSegment(); add(lineSegm); } lineSegm->setSystem(system); lineSegm->setSpanner(this); 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)); #if 1 QList sl; for (SpannerSegment* ss : segments) { if (ss->system()) sl.push_back(ss); else { qDebug("delete spanner segment %s", ss->name()); score()->selection().remove(ss); delete ss; } } segments.swap(sl); #endif } break; } lineSegm->layout(); #if 0 QList sl; for (SpannerSegment* ss : segments) { if (ss->system()) sl.push_back(ss); else { qDebug("delete spanner segment %s", ss->name()); delete ss; } } segments.swap(sl); #endif return lineSegm; } //--------------------------------------------------------- // layout // compute segments from tick1 tick2 // (obsolete) //--------------------------------------------------------- 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 // 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& 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 = 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().empty()) { 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->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 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(XmlWriter& xml) const { if (!endElement()) xml.tag("ticks", ticks()); Spanner::writeProperties(xml); if (_diagonal) xml.tag("diagonal", _diagonal); writeProperty(xml, P_ID::LINE_WIDTH); writeProperty(xml, P_ID::LINE_STYLE); writeProperty(xml, P_ID::LINE_COLOR); writeProperty(xml, P_ID::ANCHOR); writeProperty(xml, P_ID::DASH_LINE_LEN); writeProperty(xml, P_ID::DASH_GAP_LEN); if (score() == gscore) { // when used as icon if (!spannerSegments().empty()) { 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()) { modified = true; break; } } if (!modified) return; // // write user modified layout // qreal _spatium = spatium(); for (const SpannerSegment* seg : spannerSegments()) { 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 == "dashLineLength") _dashLineLen = e.readDouble(); else if (tag == "dashGapLength") _dashGapLen = e.readDouble(); else if (tag == "lineColor") _lineColor = e.readColor(); else if (!Element::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 { 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; case P_ID::LINE_STYLE: return QVariant(int(_lineStyle)); case P_ID::DASH_LINE_LEN: return dashLineLen(); case P_ID::DASH_GAP_LEN: return dashGapLen(); 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(); break; case P_ID::LINE_WIDTH: _lineWidth = v.value(); break; case P_ID::LINE_STYLE: _lineStyle = Qt::PenStyle(v.toInt()); break; case P_ID::DASH_LINE_LEN: setDashLineLen(v.toDouble()); break; case P_ID::DASH_GAP_LEN: setDashGapLen(v.toDouble()); break; default: return Spanner::setProperty(id, v); } triggerLayout(); 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 Spatium(0.15); case P_ID::LINE_STYLE: return int(Qt::SolidLine); case P_ID::DASH_LINE_LEN: case P_ID::DASH_GAP_LEN: return 5.0; default: return Spanner::propertyDefault(id); } } //--------------------------------------------------------- // getPropertyStyle //--------------------------------------------------------- StyleIdx SLine::getPropertyStyle(P_ID id) const { return Spanner::getPropertyStyle(id); } }