1546 lines
52 KiB
C++
1546 lines
52 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 "note.h"
|
||
#include "part.h"
|
||
#include "score.h"
|
||
#include "segment.h"
|
||
#include "staff.h"
|
||
#include "sym.h"
|
||
#include "system.h"
|
||
#include "textline.h"
|
||
#include "utils.h"
|
||
#include "xml.h"
|
||
|
||
namespace Ms {
|
||
//---------------------------------------------------------
|
||
// LineSegment
|
||
//---------------------------------------------------------
|
||
|
||
LineSegment::LineSegment(const LineSegment& s)
|
||
: SpannerSegment(s)
|
||
{
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// readProperties
|
||
//---------------------------------------------------------
|
||
|
||
bool LineSegment::readProperties(XmlReader& e)
|
||
{
|
||
const QStringRef& tag(e.name());
|
||
if (tag == "subtype") {
|
||
setSpannerSegmentType(SpannerSegmentType(e.readInt()));
|
||
} else if (tag == "off2") {
|
||
setUserOff2(e.readPoint() * score()->spatium());
|
||
}
|
||
/* else if (tag == "pos") {
|
||
setOffset(QPointF());
|
||
e.readNext();
|
||
}
|
||
*/
|
||
else if (!SpannerSegment::readProperties(e)) {
|
||
e.unknown();
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// read
|
||
//---------------------------------------------------------
|
||
|
||
void LineSegment::read(XmlReader& e)
|
||
{
|
||
while (e.readNextStartElement()) {
|
||
readProperties(e);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// gripsPositions
|
||
//---------------------------------------------------------
|
||
|
||
std::vector<QPointF> LineSegment::gripsPositions(const EditData&) const
|
||
{
|
||
std::vector<QPointF> grips(gripsCount());
|
||
QPointF pp(pagePos());
|
||
grips[int(Grip::START)] = pp;
|
||
grips[int(Grip::END)] = pos2() + pp;
|
||
grips[int(Grip::MIDDLE)] = pos2() * .5 + pp;
|
||
return grips;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// leftAnchorPosition
|
||
//---------------------------------------------------------
|
||
|
||
QPointF LineSegment::leftAnchorPosition(const qreal& systemPositionY) const
|
||
{
|
||
if (isMiddleType() || isEndType()) {
|
||
return QPointF(system()->firstMeasure()->abbox().left(), systemPositionY);
|
||
}
|
||
|
||
QPointF result;
|
||
|
||
System* s;
|
||
result = line()->linePos(Grip::START, &s);
|
||
result.ry() += systemPositionY - system()->pos().y();
|
||
|
||
if (s) {
|
||
result += s->pos(); // to page coordinates
|
||
}
|
||
return result;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// rightAnchorPosition
|
||
//---------------------------------------------------------
|
||
|
||
QPointF LineSegment::rightAnchorPosition(const qreal& systemPositionY) const
|
||
{
|
||
if (isMiddleType() || isBeginType()) {
|
||
return QPointF(system()->lastMeasure()->abbox().right(), systemPositionY);
|
||
}
|
||
|
||
QPointF result;
|
||
|
||
System* s;
|
||
result = line()->linePos(Grip::END, &s);
|
||
result.ry() += systemPositionY - system()->pos().y();
|
||
|
||
if (s) {
|
||
result += s->pos(); // to page coordinates
|
||
}
|
||
return result;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// gripAnchorLines
|
||
// return page coordinates
|
||
//---------------------------------------------------------
|
||
|
||
QVector<QLineF> LineSegment::gripAnchorLines(Grip grip) const
|
||
{
|
||
QVector<QLineF> result;
|
||
|
||
// Middle or aperture grip have no anchor
|
||
if (!system() || grip == Grip::APERTURE) {
|
||
return result;
|
||
}
|
||
|
||
// note-anchored spanners are relative to the system
|
||
qreal y;
|
||
if (spanner()->anchor() == Spanner::Anchor::NOTE) {
|
||
y = system()->pos().y();
|
||
} else {
|
||
const int stIdx = staffIdx();
|
||
y = system()->staffYpage(stIdx);
|
||
if (line()->placement() == Placement::BELOW) {
|
||
y += system()->staff(stIdx)->bbox().height();
|
||
}
|
||
}
|
||
|
||
switch (grip) {
|
||
case Grip::START:
|
||
result << QLineF(leftAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::START)));
|
||
break;
|
||
case Grip::END:
|
||
result << QLineF(rightAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::END)));
|
||
break;
|
||
case Grip::MIDDLE:
|
||
result << QLineF(leftAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::START)));
|
||
result << QLineF(rightAnchorPosition(y), gripsPositions().at(static_cast<int>(Grip::END)));
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// startDrag
|
||
//---------------------------------------------------------
|
||
|
||
void LineSegment::startDrag(EditData& ed)
|
||
{
|
||
SpannerSegment::startDrag(ed);
|
||
ElementEditData* eed = ed.getData(this);
|
||
eed->pushProperty(Pid::OFFSET2);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// startEditDrag
|
||
//---------------------------------------------------------
|
||
|
||
void LineSegment::startEditDrag(EditData& ed)
|
||
{
|
||
ElementEditData* eed = ed.getData(this);
|
||
eed->pushProperty(Pid::OFFSET);
|
||
eed->pushProperty(Pid::OFFSET2);
|
||
eed->pushProperty(Pid::AUTOPLACE);
|
||
if (ed.modifiers & Qt::AltModifier) {
|
||
setAutoplace(false);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// edit
|
||
// return true if event is accepted
|
||
//---------------------------------------------------------
|
||
|
||
bool LineSegment::edit(EditData& ed)
|
||
{
|
||
const bool moveStart = ed.curGrip == Grip::START;
|
||
const bool moveEnd = ed.curGrip == Grip::END || ed.curGrip == Grip::MIDDLE;
|
||
|
||
if (!((ed.modifiers & Qt::ShiftModifier)
|
||
&& ((isSingleBeginType() && moveStart) || (isSingleEndType() && moveEnd))
|
||
)) {
|
||
return false;
|
||
}
|
||
|
||
LineSegment* ls = 0;
|
||
SpannerSegmentType st = spannerSegmentType(); // may change later
|
||
SLine* l = line();
|
||
int track = l->track();
|
||
int track2 = l->track2(); // assumed to be same as track
|
||
|
||
switch (l->anchor()) {
|
||
case Spanner::Anchor::SEGMENT:
|
||
{
|
||
Segment* s1 = spanner()->startSegment();
|
||
Segment* s2 = spanner()->endSegment();
|
||
// check for line going to end of score
|
||
if (spanner()->tick2() >= score()->lastSegment()->tick()) {
|
||
// endSegment calculated above will be the last chord/rest of score
|
||
// but that is not correct - it should be an imaginary note *after* the end of the score
|
||
// best we can do is set s2 to lastSegment (probably the end barline)
|
||
s2 = score()->lastSegment();
|
||
}
|
||
if (!s1 && !s2) {
|
||
qDebug("LineSegment::edit: no start/end segment");
|
||
return true;
|
||
}
|
||
if (ed.key == Qt::Key_Left) {
|
||
if (moveStart) {
|
||
s1 = prevSeg1(s1, track);
|
||
} else if (moveEnd) {
|
||
s2 = prevSeg1(s2, track2);
|
||
}
|
||
} else if (ed.key == Qt::Key_Right) {
|
||
if (moveStart) {
|
||
s1 = nextSeg1(s1, track);
|
||
} else if (moveEnd) {
|
||
Segment* ns2 = nextSeg1(s2, track2);
|
||
if (ns2) {
|
||
s2 = ns2;
|
||
} else {
|
||
s2 = score()->lastSegment();
|
||
}
|
||
}
|
||
}
|
||
if (s1 == 0 || s2 == 0 || s1->tick() >= s2->tick()) {
|
||
return true;
|
||
}
|
||
spanner()->undoChangeProperty(Pid::SPANNER_TICK, s1->tick());
|
||
spanner()->undoChangeProperty(Pid::SPANNER_TICKS, s2->tick() - s1->tick());
|
||
}
|
||
break;
|
||
case Spanner::Anchor::NOTE:
|
||
{
|
||
Note* note1 = toNote(l->startElement());
|
||
Note* note2 = toNote(l->endElement());
|
||
Note* oldNote1 = note1;
|
||
Note* oldNote2 = note2;
|
||
if (!note1 && !note2) {
|
||
qDebug("LineSegment::edit: no start/end note");
|
||
return true; // accept the event without doing anything
|
||
}
|
||
|
||
switch (ed.key) {
|
||
case Qt::Key_Left:
|
||
if (moveStart) {
|
||
note1 = prevChordNote(note1);
|
||
} else if (moveEnd) {
|
||
note2 = prevChordNote(note2);
|
||
}
|
||
break;
|
||
case Qt::Key_Right:
|
||
if (moveStart) {
|
||
note1 = nextChordNote(note1);
|
||
} else if (moveEnd) {
|
||
note2 = nextChordNote(note2);
|
||
}
|
||
break;
|
||
case Qt::Key_Up:
|
||
if (moveStart) {
|
||
note1 = toNote(score()->upAlt(note1));
|
||
} else if (moveEnd) {
|
||
note2 = toNote(score()->upAlt(note2));
|
||
}
|
||
break;
|
||
case Qt::Key_Down:
|
||
if (moveStart) {
|
||
note1 = toNote(score()->downAlt(note1));
|
||
} else if (moveEnd) {
|
||
note2 = toNote(score()->downAlt(note2));
|
||
}
|
||
break;
|
||
default:
|
||
return true;
|
||
}
|
||
|
||
// check prevChordNote() and nextchordNote() didn't return null
|
||
// OR Score::upAlt() and Score::downAlt() didn't return non-Note (notably rests)
|
||
// OR spanner duration is > 0
|
||
// OR note1 and note2 didn't end up in different instruments
|
||
// if this is the case, accepts the event and return without doing nothing
|
||
if (!note1 || !note2
|
||
|| !note1->isNote() || !note2->isNote()
|
||
|| note1->chord()->tick() >= note2->chord()->tick()
|
||
|| note1->chord()->staff()->part()->instrument(note1->chord()->tick())
|
||
!= note2->chord()->staff()->part()->instrument(note2->chord()->tick())) {
|
||
return true;
|
||
}
|
||
if (note1 != oldNote1 || note2 != oldNote2) {
|
||
score()->undoChangeSpannerElements(spanner(), note1, note2);
|
||
}
|
||
}
|
||
break;
|
||
case Spanner::Anchor::MEASURE:
|
||
case Spanner::Anchor::CHORD:
|
||
{
|
||
Measure* m1 = l->startMeasure();
|
||
Measure* m2 = l->endMeasure();
|
||
|
||
if (ed.key == Qt::Key_Left) {
|
||
if (moveStart) {
|
||
if (m1->prevMeasure()) {
|
||
m1 = m1->prevMeasure();
|
||
}
|
||
} else if (moveEnd) {
|
||
Measure* m = m2->prevMeasure();
|
||
if (m) {
|
||
m2 = m;
|
||
}
|
||
}
|
||
} else if (ed.key == Qt::Key_Right) {
|
||
if (moveStart) {
|
||
if (m1->nextMeasure()) {
|
||
m1 = m1->nextMeasure();
|
||
}
|
||
} else if (moveEnd) {
|
||
if (m2->nextMeasure()) {
|
||
m2 = m2->nextMeasure();
|
||
}
|
||
}
|
||
}
|
||
if (m1->tick() > m2->tick()) {
|
||
return true;
|
||
}
|
||
if (l->startElement() != m1) {
|
||
spanner()->undoChangeProperty(Pid::SPANNER_TICK, m1->tick());
|
||
spanner()->undoChangeProperty(Pid::SPANNER_TICKS, m2->endTick() - m1->tick());
|
||
} else if (l->endElement() != m2) {
|
||
spanner()->undoChangeProperty(Pid::SPANNER_TICKS, m2->endTick() - m1->tick());
|
||
}
|
||
}
|
||
}
|
||
triggerLayout();
|
||
l->layout(); // recompute segment list, segment type may change
|
||
|
||
LineSegment* nls = 0;
|
||
if (st == SpannerSegmentType::SINGLE) {
|
||
if (moveStart) {
|
||
nls = l->frontSegment();
|
||
} else if (moveEnd) {
|
||
nls = l->backSegment();
|
||
}
|
||
} else if (st == SpannerSegmentType::BEGIN) {
|
||
nls = l->frontSegment();
|
||
} else if (st == SpannerSegmentType::END) {
|
||
nls = l->backSegment();
|
||
} else {
|
||
qDebug("spannerSegmentType %d", int(spannerSegmentType()));
|
||
}
|
||
|
||
if (nls && (nls != this)) {
|
||
ed.view->changeEditElement(nls);
|
||
}
|
||
if (ls) {
|
||
score()->undoRemoveElement(ls);
|
||
}
|
||
|
||
triggerLayout();
|
||
return true;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// findSegmentForGrip
|
||
//---------------------------------------------------------
|
||
|
||
Segment* LineSegment::findSegmentForGrip(Grip grip, QPointF pos) const
|
||
{
|
||
if (grip != Grip::START && grip != Grip::END) {
|
||
return nullptr;
|
||
}
|
||
|
||
SLine* l = line();
|
||
const bool left = (grip == Grip::START);
|
||
|
||
Segment* const oldSeg = left ? l->startSegment() : score()->tick2leftSegmentMM(l->tick2() - Fraction::eps());
|
||
const int oldStaffIndex = left ? staffIdx() : track2staff(l->effectiveTrack2());
|
||
|
||
const qreal spacingFactor = left ? 0.5 : 1.0; // defines the point where canvas is divided between segments, systems etc.
|
||
|
||
System* sys = oldSeg->system();
|
||
const QList<System*> foundSystems = score()->searchSystem(pos, sys, spacingFactor);
|
||
|
||
if (!foundSystems.empty() && !foundSystems.contains(sys)) {
|
||
sys = foundSystems[0];
|
||
}
|
||
|
||
// Restrict searching segment to the correct staff
|
||
pos.setY(sys->staffCanvasYpage(oldStaffIndex));
|
||
|
||
Segment* seg = nullptr; // don't prefer any segment while searching line position
|
||
int staffIndex = oldStaffIndex;
|
||
score()->dragPosition(pos, &staffIndex, &seg, spacingFactor);
|
||
|
||
return seg;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// deltaRebaseLeft
|
||
/// Helper function for anchors rebasing when dragging.
|
||
//---------------------------------------------------------
|
||
|
||
QPointF LineSegment::deltaRebaseLeft(const Segment* oldSeg, const Segment* newSeg)
|
||
{
|
||
if (oldSeg == newSeg) {
|
||
return QPointF();
|
||
}
|
||
return oldSeg->canvasPos() - newSeg->canvasPos();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// deltaRebaseRight
|
||
/// Helper function for anchors rebasing when dragging.
|
||
//---------------------------------------------------------
|
||
|
||
QPointF LineSegment::deltaRebaseRight(const Segment* oldSeg, const Segment* newSeg, int staffIndex)
|
||
{
|
||
if (oldSeg == newSeg) {
|
||
return QPointF();
|
||
}
|
||
|
||
const QPointF oldBase = oldSeg->canvasPos() + QPointF(oldSeg->width(), 0);
|
||
const QPointF newBase = newSeg->canvasPos() + QPointF(newSeg->widthInStaff(staffIndex), 0);
|
||
return oldBase - newBase;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// lastSegmentEndTick
|
||
/// Helper function for anchors rebasing when dragging.
|
||
//---------------------------------------------------------
|
||
|
||
Fraction LineSegment::lastSegmentEndTick(const Segment* seg, const Spanner* s)
|
||
{
|
||
return seg->tick() + seg->ticksInStaff(track2staff(s->effectiveTrack2()));
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// rebaseAnchor
|
||
/// If system has changed, returns the new line segment
|
||
/// (may appear to be reused \c this segment), otherwise
|
||
/// returns nullptr.
|
||
//---------------------------------------------------------
|
||
|
||
LineSegment* LineSegment::rebaseAnchor(Grip grip, Segment* newSeg)
|
||
{
|
||
switch (grip) {
|
||
case Grip::START:
|
||
if (!isSingleBeginType()) {
|
||
return nullptr;
|
||
}
|
||
break;
|
||
case Grip::END:
|
||
if (!isSingleEndType()) {
|
||
return nullptr;
|
||
}
|
||
break;
|
||
default:
|
||
return nullptr;
|
||
}
|
||
|
||
SLine* l = line();
|
||
const bool left = (grip == Grip::START);
|
||
Segment* const oldSeg = left ? l->startSegment() : score()->tick2leftSegmentMM(l->tick2() - Fraction::eps());
|
||
System* const oldSystem = system();
|
||
|
||
if (!newSeg || oldSeg == newSeg) {
|
||
return nullptr;
|
||
}
|
||
|
||
Fraction startTick = left ? newSeg->tick() : l->tick();
|
||
Fraction endTick = left ? l->tick2() : lastSegmentEndTick(newSeg, l);
|
||
|
||
if (endTick <= startTick) {
|
||
if (left) {
|
||
endTick = lastSegmentEndTick(newSeg, l);
|
||
} else {
|
||
startTick = newSeg->tick();
|
||
}
|
||
}
|
||
|
||
if (l->tick() != startTick) {
|
||
l->undoChangeProperty(Pid::SPANNER_TICK, startTick);
|
||
}
|
||
|
||
l->undoChangeProperty(Pid::SPANNER_TICKS, endTick - startTick);
|
||
|
||
if (newSeg->system() == oldSystem) {
|
||
const QPointF delta
|
||
= left ? deltaRebaseLeft(oldSeg, newSeg) : deltaRebaseRight(oldSeg, newSeg, track2staff(
|
||
l->effectiveTrack2()));
|
||
if (left) {
|
||
setOffset(offset() + delta);
|
||
_offset2 -= delta;
|
||
setOffsetChanged(true);
|
||
} else {
|
||
_offset2 += delta;
|
||
}
|
||
} else {
|
||
l->layout();
|
||
return left ? l->frontSegment() : l->backSegment();
|
||
}
|
||
|
||
return nullptr;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// rebaseAnchors
|
||
//---------------------------------------------------------
|
||
|
||
void LineSegment::rebaseAnchors(EditData& ed, Grip grip)
|
||
{
|
||
if (line()->anchor() != Spanner::Anchor::SEGMENT) {
|
||
return;
|
||
}
|
||
|
||
switch (grip) {
|
||
case Grip::START:
|
||
case Grip::END: {
|
||
const bool left = (grip == Grip::START);
|
||
|
||
if (left && !isSingleBeginType()) {
|
||
return;
|
||
}
|
||
if (!left && !isSingleEndType()) {
|
||
return;
|
||
}
|
||
|
||
// Find an appropriate segment to bind from actual mouse
|
||
// position. This allows changing systems while dragging
|
||
// while not setting line position to something
|
||
// inappropriate.
|
||
Segment* seg = findSegmentForGrip(grip, ed.pos);
|
||
LineSegment* newLineSegment = rebaseAnchor(grip, seg);
|
||
|
||
if (newLineSegment) {
|
||
// If new line segment is not the same as this one,
|
||
// switch to dragging that new segment.
|
||
if (newLineSegment != this) {
|
||
// Reset offset for the old line segment
|
||
if (left) {
|
||
_offset2.rx() -= offset().x();
|
||
setOffset(QPointF());
|
||
} else {
|
||
setUserOff2(QPointF());
|
||
}
|
||
|
||
// Switch to dragging the new line segment
|
||
ed.view->changeEditElement(newLineSegment);
|
||
}
|
||
|
||
// Set offset for the new line segment for grip to appear under the mouse cursor
|
||
System* sys;
|
||
const QPointF lp = line()->linePos(grip, &sys);
|
||
const qreal xoff = sys->mapFromCanvas(ed.pos).x() - lp.x();
|
||
|
||
if (left) {
|
||
newLineSegment->rxoffset() = xoff;
|
||
newLineSegment->setUserOff2(QPointF(-xoff, 0.0));
|
||
} else {
|
||
newLineSegment->setUserXoffset2(xoff);
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
case Grip::MIDDLE: {
|
||
if (!isSingleType()) {
|
||
return;
|
||
}
|
||
|
||
SLine* l = line();
|
||
|
||
// If dragging middle grip (or the entire hairpin), mouse position
|
||
// does not directly correspond to any sensible position, so use
|
||
// actual line coordinates instead. This method doesn't allow for
|
||
// system changes, but that seems OK when dragging the entire line:
|
||
// the line will just push away other systems according to autoplacement
|
||
// rules if necessary.
|
||
QPointF cpos = canvasPos();
|
||
cpos.setY(l->startSegment()->system()->staffCanvasYpage(l->staffIdx())); // prevent cross-system move
|
||
|
||
Segment* seg1 = findSegmentForGrip(Grip::START, cpos);
|
||
Segment* seg2 = findSegmentForGrip(Grip::END, cpos + pos2());
|
||
|
||
if (!(seg1 && seg2 && seg1->system() == seg2->system() && seg1->system() == system())) {
|
||
return;
|
||
}
|
||
|
||
rebaseAnchor(Grip::START, seg1);
|
||
rebaseAnchor(Grip::END, seg2);
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// editDrag
|
||
//---------------------------------------------------------
|
||
|
||
void LineSegment::editDrag(EditData& ed)
|
||
{
|
||
// Only for resizing according to the diagonal properties
|
||
const QPointF deltaResize(ed.evtDelta.x(), line()->diagonal() ? ed.evtDelta.y() : 0.0);
|
||
|
||
switch (ed.curGrip) {
|
||
case Grip::START: // Resize the begin of element (left grip)
|
||
setOffset(offset() + deltaResize);
|
||
_offset2 -= deltaResize;
|
||
|
||
if (isStyled(Pid::OFFSET)) {
|
||
setPropertyFlags(Pid::OFFSET, PropertyFlags::UNSTYLED);
|
||
}
|
||
|
||
rebaseAnchors(ed, ed.curGrip);
|
||
break;
|
||
case Grip::END: // Resize the end of element (right grip)
|
||
_offset2 += deltaResize;
|
||
rebaseAnchors(ed, ed.curGrip);
|
||
break;
|
||
case Grip::MIDDLE: { // Move the element (middle grip)
|
||
// Only for moving, no y limitation
|
||
const QPointF deltaMove(ed.evtDelta);
|
||
setOffset(offset() + deltaMove);
|
||
setOffsetChanged(true);
|
||
if (isStyled(Pid::OFFSET)) {
|
||
setPropertyFlags(Pid::OFFSET, PropertyFlags::UNSTYLED);
|
||
}
|
||
rebaseAnchors(ed, ed.curGrip);
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
if (line()->anchor() == Spanner::Anchor::NOTE && ed.isStartEndGrip()) {
|
||
//
|
||
// if we touch a different note, change anchor
|
||
//
|
||
Element* e = ed.view->elementNear(ed.pos);
|
||
if (e && e->isNote()) {
|
||
SLine* l = line();
|
||
if (ed.curGrip == Grip::END && e != line()->endElement()) {
|
||
qDebug("LineSegment: move end anchor");
|
||
Note* noteOld = toNote(l->endElement());
|
||
Note* noteNew = toNote(e);
|
||
Note* sNote = toNote(l->startElement());
|
||
// do not change anchor if new note is before start note
|
||
if (sNote && sNote->chord() && noteNew->chord() && sNote->chord()->tick() < noteNew->chord()->tick()) {
|
||
score()->undoChangeSpannerElements(l, sNote, noteNew);
|
||
|
||
_offset2 += noteOld->canvasPos() - noteNew->canvasPos();
|
||
}
|
||
} else if (ed.curGrip == Grip::START && e != l->startElement()) {
|
||
qDebug("LineSegment: move start anchor (not impl.)");
|
||
}
|
||
}
|
||
}
|
||
triggerLayout();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// spatiumChanged
|
||
//---------------------------------------------------------
|
||
|
||
void LineSegment::spatiumChanged(qreal ov, qreal nv)
|
||
{
|
||
Element::spatiumChanged(ov, nv);
|
||
_offset2 *= nv / ov;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// localSpatiumChanged
|
||
//---------------------------------------------------------
|
||
|
||
void LineSegment::localSpatiumChanged(qreal ov, qreal nv)
|
||
{
|
||
Element::localSpatiumChanged(ov, nv);
|
||
_offset2 *= nv / ov;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// propertyDelegate
|
||
//---------------------------------------------------------
|
||
|
||
Element* LineSegment::propertyDelegate(Pid pid)
|
||
{
|
||
if (pid == Pid::DIAGONAL
|
||
|| pid == Pid::COLOR
|
||
|| pid == Pid::LINE_WIDTH
|
||
|| pid == Pid::LINE_STYLE
|
||
|| pid == Pid::DASH_LINE_LEN
|
||
|| pid == Pid::DASH_GAP_LEN) {
|
||
return spanner();
|
||
}
|
||
return SpannerSegment::propertyDelegate(pid);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// dragAnchorLines
|
||
//---------------------------------------------------------
|
||
|
||
QVector<QLineF> LineSegment::dragAnchorLines() const
|
||
{
|
||
return gripAnchorLines(Grip::MIDDLE);
|
||
}
|
||
|
||
QRectF LineSegment::drag(EditData& ed)
|
||
{
|
||
setOffset(offset() + ed.evtDelta);
|
||
setOffsetChanged(true);
|
||
|
||
if (isStyled(Pid::OFFSET)) {
|
||
setPropertyFlags(Pid::OFFSET, PropertyFlags::UNSTYLED);
|
||
}
|
||
|
||
rebaseAnchors(ed, Grip::MIDDLE);
|
||
|
||
return canvasBoundingRect();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// SLine
|
||
//---------------------------------------------------------
|
||
|
||
SLine::SLine(Score* s, ElementFlags f)
|
||
: Spanner(s, f)
|
||
{
|
||
setTrack(0);
|
||
_lineWidth = 0.15 * spatium();
|
||
}
|
||
|
||
SLine::SLine(const SLine& s)
|
||
: Spanner(s)
|
||
{
|
||
_diagonal = s._diagonal;
|
||
_lineWidth = s._lineWidth;
|
||
_lineColor = s._lineColor;
|
||
_lineStyle = s._lineStyle;
|
||
_dashLineLen = s._dashLineLen;
|
||
_dashGapLen = s._dashGapLen;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// linePos
|
||
/// - Anchor::NOTE: return anchor note position in system
|
||
/// coordinates
|
||
/// - Anchor::CHORD: not implemented
|
||
/// - Other: return (x position in system coordinates, 0)
|
||
//---------------------------------------------------------
|
||
|
||
QPointF SLine::linePos(Grip grip, System** sys) const
|
||
{
|
||
qreal x = 0.0;
|
||
qreal sp = staff()->spatium(tick());
|
||
switch (anchor()) {
|
||
case Spanner::Anchor::SEGMENT:
|
||
{
|
||
ChordRest* cr;
|
||
if (grip == Grip::START) {
|
||
cr = toChordRest(startElement());
|
||
if (cr && type() == ElementType::OTTAVA) {
|
||
// some sources say to center the text over the notehead
|
||
// others say to start the text just to left of notehead
|
||
// some say to include accidental, others don't
|
||
// our compromise - left align, but account for accidental
|
||
if (cr->durationType() == TDuration::DurationType::V_MEASURE && !cr->measure()->hasVoices(
|
||
cr->staffIdx())) {
|
||
x = cr->x(); // center for measure rests
|
||
}
|
||
//TODO else if (cr->spaceLw > 0.0)
|
||
// x = -cr->spaceLw; // account for accidentals, etc
|
||
}
|
||
} else {
|
||
cr = toChordRest(endElement());
|
||
if (isOttava()) {
|
||
if (cr && cr->durationType() == TDuration::DurationType::V_MEASURE) {
|
||
x = cr->x() + cr->width() + sp;
|
||
} else if (cr) {
|
||
// lay out just past right edge of all notes for this segment on this staff
|
||
|
||
Segment* s = cr->segment();
|
||
|
||
int startTrack = staffIdx() * VOICES;
|
||
int endTrack = startTrack + VOICES;
|
||
qreal width = 0.0;
|
||
|
||
// don’t consider full measure rests, which are centered
|
||
// (TODO: what if there is only a full measure rest?)
|
||
|
||
for (int track = startTrack; track < endTrack; ++track) {
|
||
ChordRest* cr1 = toChordRest(s->element(track));
|
||
if (!cr1) {
|
||
continue;
|
||
}
|
||
if (cr1->isChord()) {
|
||
for (Note* n : toChord(cr1)->notes()) {
|
||
width = qMax(width, n->shape().right() + n->pos().x() + cr1->pos().x());
|
||
}
|
||
} else if (cr1->isRest() && (cr1->actualDurationType() != TDuration::DurationType::V_MEASURE)) {
|
||
width = qMax(width, cr1->bbox().right() + cr1->pos().x());
|
||
}
|
||
}
|
||
|
||
x = width + sp;
|
||
|
||
// extend past chord/rest
|
||
// but don't overlap next chord/rest
|
||
|
||
bool crFound = false;
|
||
int n = staffIdx() * VOICES;
|
||
Segment* ns = s->next();
|
||
while (ns) {
|
||
for (int i = 0; i < VOICES; ++i) {
|
||
if (ns->element(n + i)) {
|
||
crFound = true;
|
||
break;
|
||
}
|
||
}
|
||
if (crFound) {
|
||
break;
|
||
}
|
||
ns = ns->next();
|
||
}
|
||
if (crFound) {
|
||
qreal nextNoteDistance = ns->x() - s->x() + lineWidth();
|
||
if (x > nextNoteDistance) {
|
||
x = qMax(width, nextNoteDistance);
|
||
}
|
||
}
|
||
}
|
||
} else if (isLyricsLine() && toLyrics(parent())->ticks() > Fraction(0,1)) {
|
||
// melisma line
|
||
// it is possible CR won't be in correct track
|
||
// prefer element in current track if available
|
||
if (!cr) {
|
||
qDebug("no end for lyricsline segment - start %d, ticks %d", tick().ticks(), ticks().ticks());
|
||
} else if (cr->track() != track()) {
|
||
Element* e = cr->segment()->element(track());
|
||
if (e) {
|
||
cr = toChordRest(e);
|
||
}
|
||
}
|
||
|
||
// layout to right edge of CR
|
||
bool extendToEnd = true; // extend to end or start element?
|
||
if (cr == toChordRest(startElement())) {
|
||
extendToEnd = false;
|
||
} else if (cr) {
|
||
// if next segment is a chord with lyrics which spans to the left
|
||
// then do not extend to the right edge of end element.
|
||
Segment* seg = cr->segment();
|
||
seg = seg->next(SegmentType::ChordRest);
|
||
if (seg) {
|
||
ChordRest* cr2 = seg->cr(cr->track());
|
||
if (cr2 && !cr2->lyrics().empty()) {
|
||
extendToEnd = false;
|
||
}
|
||
}
|
||
}
|
||
ChordRest* right = extendToEnd ? cr : toChordRest(startElement());
|
||
if (right) {
|
||
qreal maxRight = 0.0;
|
||
if (right->isChord()) {
|
||
// chord bbox() is unreliable, look at notes
|
||
// this also allows us to more easily ignore ledger lines
|
||
for (Note* n : toChord(right)->notes()) {
|
||
maxRight = qMax(maxRight, right->x() + n->x() + n->bboxRightPos());
|
||
}
|
||
} else {
|
||
// rest - won't normally happen
|
||
maxRight = right->x() + right->width();
|
||
}
|
||
x = maxRight;
|
||
}
|
||
} else if (isHairpin() || isTrill() || isVibrato() || isTextLine() || isLyricsLine()) {
|
||
// (for LYRICSLINE, this is hyphen; melisma line is handled above)
|
||
// lay out to just before next chordrest on this staff, or barline
|
||
// tick2 actually tells us the right chordrest to look for
|
||
if (cr && endElement()->parent() && endElement()->parent()->type() == ElementType::SEGMENT) {
|
||
qreal x2 = cr->x() /* TODO + cr->space().rw() */;
|
||
Segment* currentSeg = toSegment(endElement()->parent());
|
||
Segment* seg = score()->tick2segmentMM(tick2(), false, SegmentType::ChordRest);
|
||
if (!seg) {
|
||
// no end segment found, use measure width
|
||
x2 = endElement()->parent()->parent()->width() - sp;
|
||
} else if (currentSeg->measure() == seg->measure()) {
|
||
// next chordrest found in same measure;
|
||
// end line 1sp to left
|
||
x2 = qMax(x2, seg->x() - sp);
|
||
} else {
|
||
// next chordrest is in next measure
|
||
// lay out to end (barline) of current measure instead
|
||
seg = currentSeg->next(SegmentType::EndBarLine);
|
||
if (!seg) {
|
||
seg = currentSeg->measure()->last();
|
||
}
|
||
// allow lyrics hyphen to extend to barline
|
||
// other lines stop 1sp short
|
||
qreal gap = (type() == ElementType::LYRICSLINE) ? 0.0 : sp;
|
||
qreal x3 = seg->enabled() ? seg->x() : seg->measure()->width();
|
||
x2 = qMax(x2, x3 - gap);
|
||
}
|
||
x = x2 - endElement()->parent()->x();
|
||
}
|
||
}
|
||
}
|
||
|
||
Fraction t = grip == Grip::START ? tick() : tick2();
|
||
Measure* m = cr ? cr->measure() : score()->tick2measure(t);
|
||
|
||
if (m) {
|
||
x += cr ? cr->segment()->pos().x() + m->pos().x() : m->tick2pos(t);
|
||
*sys = m->system();
|
||
} else {
|
||
*sys = 0;
|
||
}
|
||
}
|
||
break;
|
||
|
||
case Spanner::Anchor::MEASURE:
|
||
{
|
||
// anchor() == Anchor::MEASURE
|
||
const Measure* m;
|
||
if (grip == Grip::START) {
|
||
m = startMeasure();
|
||
// start after clef/keysig/timesig/barline
|
||
qreal offset = 0.0;
|
||
Segment* s = m->first(SegmentType::ChordRest);
|
||
if (s) {
|
||
s = s->prev();
|
||
if (s && s->enabled()) {
|
||
offset = s->x();
|
||
Element* e = s->element(staffIdx() * VOICES);
|
||
if (e) {
|
||
offset += e->width();
|
||
}
|
||
}
|
||
}
|
||
x = m->pos().x() + offset;
|
||
if (score()->styleB(Sid::createMultiMeasureRests) && m->hasMMRest()) {
|
||
x = m->mmRest()->pos().x();
|
||
}
|
||
} else {
|
||
qreal _spatium = spatium();
|
||
|
||
if (score()->styleB(Sid::createMultiMeasureRests)) {
|
||
// find the actual measure where the volta should stop
|
||
m = startMeasure();
|
||
if (m->hasMMRest()) {
|
||
m = m->mmRest();
|
||
}
|
||
while (m->nextMeasureMM() && (m->endTick() < tick2())) {
|
||
m = m->nextMeasureMM();
|
||
}
|
||
} else {
|
||
m = endMeasure();
|
||
}
|
||
|
||
// back up to barline (skip courtesy elements)
|
||
Segment* seg = m->last();
|
||
while (seg && seg->segmentType() != SegmentType::EndBarLine) {
|
||
seg = seg->prev();
|
||
}
|
||
if (!seg || !seg->enabled()) {
|
||
// no end bar line; look for BeginBarLine or StartRepeatBarLine of next measure
|
||
Measure* nm = m->nextMeasure();
|
||
if (nm->system() == m->system()) {
|
||
seg = nm->first(SegmentType::BeginBarLine | SegmentType::StartRepeatBarLine);
|
||
}
|
||
}
|
||
qreal mwidth = seg && seg->measure() == m ? seg->x() : m->bbox().right();
|
||
x = m->pos().x() + mwidth;
|
||
// align to barline
|
||
if (seg && (seg->segmentType() & SegmentType::BarLineType)) {
|
||
Element* e = seg->element(0);
|
||
if (e && e->type() == ElementType::BAR_LINE) {
|
||
BarLineType blt = toBarLine(e)->barLineType();
|
||
switch (blt) {
|
||
case BarLineType::END_REPEAT:
|
||
// skip dots
|
||
x += symWidth(SymId::repeatDot);
|
||
x += score()->styleS(Sid::endBarDistance).val() * _spatium;
|
||
// fall through
|
||
case BarLineType::DOUBLE:
|
||
// center on leftmost (thinner) barline
|
||
x += score()->styleS(Sid::doubleBarWidth).val() * _spatium * 0.5;
|
||
break;
|
||
case BarLineType::START_REPEAT:
|
||
// center on leftmost (thicker) barline
|
||
x += score()->styleS(Sid::endBarWidth).val() * _spatium * 0.5;
|
||
break;
|
||
default:
|
||
// center on barline
|
||
x += score()->styleS(Sid::barWidth).val() * _spatium * 0.5;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (score()->styleB(Sid::createMultiMeasureRests)) {
|
||
m = m->mmRest1();
|
||
}
|
||
Q_ASSERT(m->system());
|
||
*sys = m->system();
|
||
}
|
||
break;
|
||
|
||
case Spanner::Anchor::NOTE: {
|
||
Element* e = grip == Grip::START ? startElement() : endElement();
|
||
if (!e) {
|
||
return QPointF();
|
||
}
|
||
Note* n = toNote(e);
|
||
System* s = n->chord()->segment()->system();
|
||
if (s == 0) {
|
||
qDebug("no system: %s start %s chord parent %s\n", name(), n->name(), n->chord()->parent()->name());
|
||
return QPointF();
|
||
}
|
||
*sys = s;
|
||
// return the position of the anchor note relative to the system
|
||
// QPointF elemPagePos = e->pagePos(); // DEBUG
|
||
// QPointF systPagePos = s->pagePos();
|
||
// qreal staffYPage = s->staffYpage(e->staffIdx());
|
||
QPointF p = n->pagePos() - s->pagePos();
|
||
if (!isGlissando()) {
|
||
p.rx() += n->headWidth() * 0.5;
|
||
}
|
||
return p;
|
||
}
|
||
|
||
case Spanner::Anchor::CHORD:
|
||
qFatal("Sline::linePos(): anchor not implemented");
|
||
break;
|
||
}
|
||
return QPointF(x, 0.0);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// layoutSystem
|
||
// layout spannersegment for system
|
||
//---------------------------------------------------------
|
||
|
||
SpannerSegment* SLine::layoutSystem(System* system)
|
||
{
|
||
Fraction stick = system->firstMeasure()->tick();
|
||
Fraction etick = system->lastMeasure()->endTick();
|
||
|
||
LineSegment* lineSegm = toLineSegment(getNextLayoutSystemSegment(system, [this]() { return createLineSegment(); }));
|
||
|
||
SpannerSegmentType sst;
|
||
if (tick() >= stick) {
|
||
//
|
||
// this is the first call to layoutSystem,
|
||
// processing the first line segment
|
||
//
|
||
computeStartElement();
|
||
computeEndElement();
|
||
sst = tick2() <= etick ? SpannerSegmentType::SINGLE : SpannerSegmentType::BEGIN;
|
||
} else if (tick() < stick && tick2() > etick) {
|
||
sst = SpannerSegmentType::MIDDLE;
|
||
} else {
|
||
//
|
||
// this is the last call to layoutSystem
|
||
// processing the last line segment
|
||
//
|
||
sst = SpannerSegmentType::END;
|
||
}
|
||
lineSegm->setSpannerSegmentType(sst);
|
||
|
||
switch (sst) {
|
||
case SpannerSegmentType::SINGLE: {
|
||
System* s;
|
||
QPointF p1 = linePos(Grip::START, &s);
|
||
QPointF p2 = linePos(Grip::END, &s);
|
||
qreal len = p2.x() - p1.x();
|
||
lineSegm->setPos(p1);
|
||
lineSegm->setPos2(QPointF(len, p2.y() - p1.y()));
|
||
}
|
||
break;
|
||
case SpannerSegmentType::BEGIN: {
|
||
System* s;
|
||
QPointF p1 = linePos(Grip::START, &s);
|
||
lineSegm->setPos(p1);
|
||
qreal x2 = system->bbox().right();
|
||
lineSegm->setPos2(QPointF(x2 - p1.x(), 0.0));
|
||
}
|
||
break;
|
||
case SpannerSegmentType::MIDDLE: {
|
||
Measure* firstMeasure = system->firstMeasure();
|
||
Segment* firstCRSeg = firstMeasure->first(SegmentType::ChordRest);
|
||
qreal x1 = (firstCRSeg ? firstCRSeg->pos().x() : 0) + firstMeasure->pos().x();
|
||
qreal x2 = system->bbox().right();
|
||
System* s;
|
||
QPointF p1 = linePos(Grip::START, &s);
|
||
lineSegm->setPos(QPointF(x1, p1.y()));
|
||
lineSegm->setPos2(QPointF(x2 - x1, 0.0));
|
||
}
|
||
break;
|
||
case SpannerSegmentType::END: {
|
||
qreal offset = 0.0;
|
||
System* s;
|
||
QPointF p2 = linePos(Grip::END, &s);
|
||
Measure* firstMeas = system->firstMeasure();
|
||
Segment* firstCRSeg = firstMeas->first(SegmentType::ChordRest);
|
||
if (anchor() == Anchor::SEGMENT || anchor() == Anchor::MEASURE) {
|
||
// start line just after previous element (eg, key signature)
|
||
firstCRSeg = firstCRSeg->prev();
|
||
Element* e = firstCRSeg ? firstCRSeg->element(staffIdx() * VOICES) : nullptr;
|
||
if (e) {
|
||
offset = e->width();
|
||
}
|
||
}
|
||
qreal x1 = (firstCRSeg ? firstCRSeg->pos().x() : 0) + firstMeas->pos().x() + offset;
|
||
qreal len = p2.x() - x1;
|
||
lineSegm->setPos(QPointF(p2.x() - len, p2.y()));
|
||
lineSegm->setPos2(QPointF(len, 0.0));
|
||
}
|
||
break;
|
||
}
|
||
lineSegm->layout();
|
||
return lineSegm;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// layout
|
||
// compute segments from tick1 tick2
|
||
// (used for palette, edit mode, and layout of note lines and glissandi)
|
||
//---------------------------------------------------------
|
||
|
||
void SLine::layout()
|
||
{
|
||
if (score() == gscore || (tick() == Fraction(-1,1)) || (tick2() == Fraction::fromTicks(1))) {
|
||
//
|
||
// when used in a palette or while dragging from palette,
|
||
// SLine has no parent and
|
||
// tick and tick2 has no meaning so no layout is
|
||
// possible and needed
|
||
//
|
||
setLen(gscore->spatium() * 7);
|
||
if (!spannerSegments().empty()) {
|
||
LineSegment* lineSegm = frontSegment();
|
||
lineSegm->layout();
|
||
setbbox(lineSegm->bbox());
|
||
}
|
||
return;
|
||
}
|
||
|
||
computeStartElement();
|
||
computeEndElement();
|
||
|
||
System* s1;
|
||
System* s2;
|
||
QPointF p1(linePos(Grip::START, &s1));
|
||
QPointF p2(linePos(Grip::END, &s2));
|
||
|
||
const QList<System*>& systems = score()->systems();
|
||
int sysIdx1 = systems.indexOf(s1);
|
||
int sysIdx2 = systems.indexOf(s2);
|
||
int segmentsNeeded = 0;
|
||
|
||
if (sysIdx1 == -1 || sysIdx2 == -1) {
|
||
return;
|
||
}
|
||
|
||
for (int i = sysIdx1; i <= sysIdx2; ++i) {
|
||
if (systems.at(i)->vbox()) {
|
||
continue;
|
||
}
|
||
++segmentsNeeded;
|
||
}
|
||
|
||
int segCount = int(spannerSegments().size());
|
||
|
||
if (segmentsNeeded != segCount) {
|
||
fixupSegments(segmentsNeeded, [this]() { return createLineSegment(); });
|
||
if (segmentsNeeded > segCount) {
|
||
for (int i = segCount; i < segmentsNeeded; ++i) {
|
||
LineSegment* lineSegm = segmentAt(i);
|
||
// set user offset to previous segment's offset
|
||
if (segCount > 0) {
|
||
lineSegm->setOffset(QPointF(0, segmentAt(i - 1)->offset().y()));
|
||
} else {
|
||
lineSegm->setOffset(QPointF(0, offset().y()));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
int segIdx = 0;
|
||
for (int i = sysIdx1; i <= sysIdx2; ++i) {
|
||
System* system = systems.at(i);
|
||
if (system->vbox()) {
|
||
continue;
|
||
}
|
||
LineSegment* lineSegm = segmentAt(segIdx++);
|
||
lineSegm->setTrack(track()); // DEBUG
|
||
lineSegm->setSystem(system);
|
||
|
||
Measure* firstMeas = system->firstMeasure();
|
||
Segment* firstCRSeg = firstMeas->first(SegmentType::ChordRest);
|
||
|
||
if (sysIdx1 == sysIdx2) {
|
||
// single segment
|
||
lineSegm->setSpannerSegmentType(SpannerSegmentType::SINGLE);
|
||
qreal len = p2.x() - p1.x();
|
||
// enforcing a minimum length would be possible but inadvisable
|
||
// the line length calculations are tuned well enough that this should not be needed
|
||
//if (anchor() == Anchor::SEGMENT && type() != ElementType::PEDAL)
|
||
// len = qMax(1.0 * spatium(), len);
|
||
lineSegm->setPos(p1);
|
||
lineSegm->setPos2(QPointF(len, p2.y() - p1.y()));
|
||
} else if (i == sysIdx1) {
|
||
// start segment
|
||
lineSegm->setSpannerSegmentType(SpannerSegmentType::BEGIN);
|
||
lineSegm->setPos(p1);
|
||
qreal x2 = system->bbox().right();
|
||
lineSegm->setPos2(QPointF(x2 - p1.x(), 0.0));
|
||
} else if (i > 0 && i != sysIdx2) {
|
||
// middle segment
|
||
lineSegm->setSpannerSegmentType(SpannerSegmentType::MIDDLE);
|
||
qreal x1 = (firstCRSeg ? firstCRSeg->pos().x() : 0) + firstMeas->pos().x();
|
||
qreal x2 = system->bbox().right();
|
||
lineSegm->setPos(QPointF(x1, p1.y()));
|
||
lineSegm->setPos2(QPointF(x2 - x1, 0.0));
|
||
} else if (i == sysIdx2) {
|
||
// end segment
|
||
qreal offset = 0.0;
|
||
qreal minLen = 0.0;
|
||
if (anchor() == Anchor::SEGMENT || anchor() == Anchor::MEASURE) {
|
||
// start line just after previous element (eg, key signature)
|
||
firstCRSeg = firstCRSeg->prev();
|
||
Element* e = firstCRSeg ? firstCRSeg->element(staffIdx() * VOICES) : nullptr;
|
||
if (e) {
|
||
offset = e->width();
|
||
}
|
||
// enforcing a minimum length would be possible but inadvisable
|
||
// the line length calculations are tuned well enough that this should not be needed
|
||
//if (type() != ElementType::PEDAL)
|
||
// minLen = 1.0 * spatium();
|
||
}
|
||
qreal x1 = (firstCRSeg ? firstCRSeg->pos().x() : 0) + firstMeas->pos().x() + offset;
|
||
qreal len = qMax(minLen, p2.x() - x1);
|
||
lineSegm->setSpannerSegmentType(SpannerSegmentType::END);
|
||
lineSegm->setPos(QPointF(p2.x() - len, p2.y()));
|
||
lineSegm->setPos2(QPointF(len, 0.0));
|
||
}
|
||
lineSegm->layout();
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// writeProperties
|
||
// write properties different from prototype
|
||
//---------------------------------------------------------
|
||
|
||
void SLine::writeProperties(XmlWriter& xml) const
|
||
{
|
||
if (!endElement()) {
|
||
((Spanner*)this)->computeEndElement(); // HACK
|
||
if (!endElement()) {
|
||
xml.tag("ticks", ticks());
|
||
}
|
||
}
|
||
Spanner::writeProperties(xml);
|
||
if (_diagonal) {
|
||
xml.tag("diagonal", _diagonal);
|
||
}
|
||
writeProperty(xml, Pid::LINE_WIDTH);
|
||
writeProperty(xml, Pid::LINE_STYLE);
|
||
writeProperty(xml, Pid::COLOR);
|
||
writeProperty(xml, Pid::ANCHOR);
|
||
writeProperty(xml, Pid::DASH_LINE_LEN);
|
||
writeProperty(xml, Pid::DASH_GAP_LEN);
|
||
if (score() == gscore) {
|
||
// when used as icon
|
||
if (!spannerSegments().empty()) {
|
||
const LineSegment* s = frontSegment();
|
||
xml.tag("length", s->pos2().x());
|
||
} else {
|
||
xml.tag("length", spatium() * 4);
|
||
}
|
||
return;
|
||
}
|
||
//
|
||
// check if user has modified the default layout
|
||
//
|
||
bool modified = false;
|
||
for (const SpannerSegment* seg : spannerSegments()) {
|
||
if (!seg->autoplace() || !seg->visible()
|
||
|| (seg->propertyFlags(Pid::MIN_DISTANCE) == PropertyFlags::UNSTYLED
|
||
|| seg->getProperty(Pid::MIN_DISTANCE) != seg->propertyDefault(Pid::MIN_DISTANCE))
|
||
|| (!seg->isStyled(Pid::OFFSET) && (!seg->offset().isNull() || !seg->userOff2().isNull()))) {
|
||
modified = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!modified) {
|
||
return;
|
||
}
|
||
|
||
//
|
||
// write user modified layout and other segment properties
|
||
//
|
||
qreal _spatium = score()->spatium();
|
||
for (const SpannerSegment* seg : spannerSegments()) {
|
||
xml.stag("Segment", seg);
|
||
xml.tag("subtype", int(seg->spannerSegmentType()));
|
||
// TODO:
|
||
// NOSTYLE offset written in Element::writeProperties,
|
||
// so we probably don't need to duplicate it here
|
||
// see https://musescore.org/en/node/286848
|
||
//if (seg->propertyFlags(Pid::OFFSET) & PropertyFlags::UNSTYLED)
|
||
xml.tag("offset", seg->offset() / _spatium);
|
||
xml.tag("off2", seg->userOff2() / _spatium);
|
||
seg->writeProperty(xml, Pid::MIN_DISTANCE);
|
||
seg->Element::writeProperties(xml);
|
||
xml.etag();
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// readProperties
|
||
//---------------------------------------------------------
|
||
|
||
bool SLine::readProperties(XmlReader& e)
|
||
{
|
||
const QStringRef& tag(e.name());
|
||
|
||
if (tag == "tick2") { // obsolete
|
||
if (tick() == Fraction(-1,1)) { // not necessarily set (for first note of score?) #30151
|
||
setTick(e.tick());
|
||
}
|
||
setTick2(Fraction::fromTicks(e.readInt()));
|
||
} else if (tag == "tick") { // obsolete
|
||
setTick(Fraction::fromTicks(e.readInt()));
|
||
} else if (tag == "ticks") {
|
||
setTicks(Fraction::fromTicks(e.readInt()));
|
||
} else if (tag == "Segment") {
|
||
LineSegment* ls = createLineSegment();
|
||
ls->setTrack(track()); // needed in read to get the right staff mag
|
||
ls->read(e);
|
||
add(ls);
|
||
ls->setVisible(visible());
|
||
} else if (tag == "length") {
|
||
setLen(e.readDouble());
|
||
} else if (tag == "diagonal") {
|
||
setDiagonal(e.readInt());
|
||
} else if (tag == "anchor") {
|
||
setAnchor(Anchor(e.readInt()));
|
||
} else if (tag == "lineWidth") {
|
||
_lineWidth = e.readDouble() * spatium();
|
||
} else if (tag == "lineStyle") {
|
||
_lineStyle = Qt::PenStyle(e.readInt());
|
||
} else if (tag == "dashLineLength") {
|
||
_dashLineLen = e.readDouble();
|
||
} else if (tag == "dashGapLength") {
|
||
_dashGapLen = e.readDouble();
|
||
} else if (tag == "lineColor") {
|
||
_lineColor = e.readColor();
|
||
} else if (tag == "color") {
|
||
_lineColor = e.readColor();
|
||
} else if (!Spanner::readProperties(e)) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// setLen
|
||
// used to create an element suitable for palette
|
||
//---------------------------------------------------------
|
||
|
||
void SLine::setLen(qreal l)
|
||
{
|
||
if (spannerSegments().empty()) {
|
||
add(createLineSegment());
|
||
}
|
||
LineSegment* s = frontSegment();
|
||
s->setPos(QPointF());
|
||
s->setPos2(QPointF(l, 0));
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// bbox
|
||
// used by palette: only one segment
|
||
//---------------------------------------------------------
|
||
|
||
const QRectF& SLine::bbox() const
|
||
{
|
||
if (spannerSegments().empty()) {
|
||
setbbox(QRectF());
|
||
} else {
|
||
setbbox(segmentAt(0)->bbox());
|
||
}
|
||
return Element::bbox();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// write
|
||
//---------------------------------------------------------
|
||
|
||
void SLine::write(XmlWriter& xml) const
|
||
{
|
||
xml.stag(this);
|
||
SLine::writeProperties(xml);
|
||
xml.etag();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// read
|
||
//---------------------------------------------------------
|
||
|
||
void SLine::read(XmlReader& e)
|
||
{
|
||
eraseSpannerSegments();
|
||
|
||
if (score()->mscVersion() < 301) {
|
||
e.addSpanner(e.intAttribute("id", -1), this);
|
||
}
|
||
|
||
while (e.readNextStartElement()) {
|
||
if (!SLine::readProperties(e)) {
|
||
e.unknown();
|
||
}
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// getProperty
|
||
//---------------------------------------------------------
|
||
|
||
QVariant SLine::getProperty(Pid id) const
|
||
{
|
||
switch (id) {
|
||
case Pid::DIAGONAL:
|
||
return _diagonal;
|
||
case Pid::COLOR:
|
||
return _lineColor;
|
||
case Pid::LINE_WIDTH:
|
||
return _lineWidth;
|
||
case Pid::LINE_STYLE:
|
||
return QVariant(int(_lineStyle));
|
||
case Pid::DASH_LINE_LEN:
|
||
return dashLineLen();
|
||
case Pid::DASH_GAP_LEN:
|
||
return dashGapLen();
|
||
default:
|
||
return Spanner::getProperty(id);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// setProperty
|
||
//---------------------------------------------------------
|
||
|
||
bool SLine::setProperty(Pid id, const QVariant& v)
|
||
{
|
||
switch (id) {
|
||
case Pid::DIAGONAL:
|
||
_diagonal = v.toBool();
|
||
break;
|
||
case Pid::COLOR:
|
||
_lineColor = v.value<QColor>();
|
||
break;
|
||
case Pid::LINE_WIDTH:
|
||
_lineWidth = v.toReal();
|
||
break;
|
||
case Pid::LINE_STYLE:
|
||
_lineStyle = Qt::PenStyle(v.toInt());
|
||
break;
|
||
case Pid::DASH_LINE_LEN:
|
||
setDashLineLen(v.toDouble());
|
||
break;
|
||
case Pid::DASH_GAP_LEN:
|
||
setDashGapLen(v.toDouble());
|
||
break;
|
||
default:
|
||
return Spanner::setProperty(id, v);
|
||
}
|
||
triggerLayout();
|
||
return true;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// propertyDefault
|
||
//---------------------------------------------------------
|
||
|
||
QVariant SLine::propertyDefault(Pid pid) const
|
||
{
|
||
switch (pid) {
|
||
case Pid::DIAGONAL:
|
||
return false;
|
||
case Pid::COLOR:
|
||
return MScore::defaultColor;
|
||
case Pid::LINE_WIDTH:
|
||
if (propertyFlags(pid) != PropertyFlags::NOSTYLE) {
|
||
return Spanner::propertyDefault(pid);
|
||
}
|
||
return 0.15 * spatium();
|
||
case Pid::LINE_STYLE:
|
||
if (propertyFlags(pid) != PropertyFlags::NOSTYLE) {
|
||
return Spanner::propertyDefault(pid);
|
||
}
|
||
return int(Qt::SolidLine);
|
||
case Pid::DASH_LINE_LEN:
|
||
case Pid::DASH_GAP_LEN:
|
||
if (propertyFlags(pid) != PropertyFlags::NOSTYLE) {
|
||
return Spanner::propertyDefault(pid);
|
||
}
|
||
return 5.0;
|
||
default:
|
||
return Spanner::propertyDefault(pid);
|
||
}
|
||
}
|
||
}
|