MuseScore/libmscore/slur.cpp

1446 lines
53 KiB
C++
Raw Normal View History

2012-05-26 14:26:10 +02:00
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2002-2016 Werner Schweer
2012-05-26 14:26:10 +02:00
//
// 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 "measure.h"
#include "score.h"
#include "system.h"
#include "undo.h"
#include "chord.h"
2012-05-26 14:26:10 +02:00
#include "stem.h"
#include "slur.h"
#include "tie.h"
#include "part.h"
#include "navigate.h"
2017-01-05 11:23:47 +01:00
#include "articulation.h"
2012-05-26 14:26:10 +02:00
2013-05-13 18:49:17 +02:00
namespace Ms {
2014-07-31 16:30:04 +02:00
Element* SlurTie::editEndElement;
Element* SlurTie::editStartElement;
QList<SlurOffsets> SlurTie::editUps;
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// draw
//---------------------------------------------------------
void SlurSegment::draw(QPainter* painter) const
{
QPen pen(curColor());
2013-06-10 11:03:34 +02:00
switch (slurTie()->lineType()) {
case 0:
2014-05-07 12:10:28 +02:00
painter->setBrush(QBrush(pen.color()));
2013-06-10 11:03:34 +02:00
pen.setCapStyle(Qt::RoundCap);
pen.setJoinStyle(Qt::RoundJoin);
2016-03-19 11:41:38 +01:00
pen.setWidthF(score()->styleP(StyleIdx::SlurEndWidth));
2013-06-10 11:03:34 +02:00
break;
case 1:
painter->setBrush(Qt::NoBrush);
2016-03-19 11:41:38 +01:00
pen.setWidthF(score()->styleP(StyleIdx::SlurDottedWidth));
2013-06-10 11:03:34 +02:00
pen.setStyle(Qt::DotLine);
break;
case 2:
painter->setBrush(Qt::NoBrush);
2016-03-19 11:41:38 +01:00
pen.setWidthF(score()->styleP(StyleIdx::SlurDottedWidth));
2013-06-10 11:03:34 +02:00
pen.setStyle(Qt::DashLine);
break;
case 3:
painter->setBrush(Qt::NoBrush);
pen.setWidthF(score()->styleP(StyleIdx::SlurDottedWidth));
pen.setStyle(Qt::CustomDashLine);
QVector<qreal> dashes { 5.0, 5.0 };
pen.setDashPattern(dashes);
break;
}
2012-05-26 14:26:10 +02:00
painter->setPen(pen);
painter->drawPath(path);
}
2017-03-31 13:03:15 +02:00
//---------------------------------------------------------
// startEdit
//---------------------------------------------------------
void SlurSegment::startEdit(EditData& ed)
{
ed.grips = int(Grip::GRIPS);
ed.curGrip = Grip::END;
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// updateGrips
// return grip rectangles in page coordinates
//---------------------------------------------------------
2017-03-31 13:03:15 +02:00
void SlurSegment::updateGrips(EditData& ed) const
2012-05-26 14:26:10 +02:00
{
QPointF p(pagePos());
for (int i = 0; i < ed.grips; ++i)
2017-03-31 13:03:15 +02:00
ed.grip[i].translate(_ups[i].p + _ups[i].off + p);
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// searchCR
//---------------------------------------------------------
static ChordRest* searchCR(Segment* segment, int startTrack, int endTrack)
{
2017-03-08 13:12:26 +01:00
// for (Segment* s = segment; s; s = s->next1MM(SegmentType::ChordRest)) {
for (Segment* s = segment; s; s = s->next(SegmentType::ChordRest)) { // restrict search to measure
if (startTrack > endTrack) {
for (int t = startTrack-1; t >= endTrack; --t) {
if (s->element(t))
2016-07-04 13:21:33 +02:00
return toChordRest(s->element(t));
}
}
else {
for (int t = startTrack; t < endTrack; ++t) {
if (s->element(t))
2016-07-04 13:21:33 +02:00
return toChordRest(s->element(t));
}
}
}
return 0;
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// edit
// return true if event is accepted
//---------------------------------------------------------
2017-03-31 13:03:15 +02:00
bool SlurSegment::edit(EditData& ed)
2012-05-26 14:26:10 +02:00
{
Slur* sl = slur();
2012-05-26 14:26:10 +02:00
2017-03-31 13:03:15 +02:00
if (ed.key == Qt::Key_X) {
2016-03-02 13:20:19 +01:00
sl->setSlurDirection(sl->up() ? Direction::DOWN : Direction::UP);
2012-05-26 14:26:10 +02:00
sl->layout();
return true;
}
2017-03-31 13:03:15 +02:00
if (ed.key == Qt::Key_Home) {
ups(ed.curGrip).off = QPointF();
2013-10-16 18:09:26 +02:00
sl->layout();
return true;
}
2012-05-26 14:26:10 +02:00
2017-03-31 13:03:15 +02:00
if (!((ed.modifiers & Qt::ShiftModifier) && (isSingleType() || (isBeginType() && ed.curGrip == Grip::START) || (isEndType() && ed.curGrip == Grip::END))))
2012-05-26 14:26:10 +02:00
return false;
ChordRest* cr = 0;
2016-07-04 13:21:33 +02:00
ChordRest* e;
ChordRest* e1;
2017-03-31 13:03:15 +02:00
if (ed.curGrip == Grip::START) {
2016-07-04 13:21:33 +02:00
e = sl->startCR();
e1 = sl->endCR();
}
else {
e = sl->endCR();
e1 = sl->startCR();
}
2012-05-26 14:26:10 +02:00
2017-03-31 13:03:15 +02:00
if (ed.key == Qt::Key_Left)
2013-06-12 14:23:57 +02:00
cr = prevChordRest(e);
2017-03-31 13:03:15 +02:00
else if (ed.key == Qt::Key_Right)
2013-06-12 14:23:57 +02:00
cr = nextChordRest(e);
2017-03-31 13:03:15 +02:00
else if (ed.key == Qt::Key_Up) {
Part* part = e->part();
int startTrack = part->startTrack();
int endTrack = e->track();
cr = searchCR(e->segment(), endTrack, startTrack);
}
2017-03-31 13:03:15 +02:00
else if (ed.key == Qt::Key_Down) {
int startTrack = e->track() + 1;
Part* part = e->part();
int endTrack = part->endTrack();
cr = searchCR(e->segment(), startTrack, endTrack);
}
2014-07-10 14:32:04 +02:00
if (cr && cr != e1)
2017-03-31 13:03:15 +02:00
changeAnchor(ed.view, ed.curGrip, cr);
2012-05-26 14:26:10 +02:00
return true;
}
//---------------------------------------------------------
// changeAnchor
//---------------------------------------------------------
void SlurSegment::changeAnchor(MuseScoreView* viewer, Grip curGrip, Element* element)
2012-05-26 14:26:10 +02:00
{
if (curGrip == Grip::START) {
2013-08-22 12:18:14 +02:00
spanner()->setStartElement(element);
switch (spanner()->anchor()) {
2014-07-03 15:02:36 +02:00
case Spanner::Anchor::NOTE: {
2016-07-04 13:21:33 +02:00
Tie* tie = toTie(spanner());
Note* note = toNote(element);
2016-11-16 08:54:10 +01:00
if (note->chord()->tick() <= tie->endNote()->tick()) {
tie->startNote()->setTieFor(0);
tie->setStartNote(note);
note->setTieFor(tie);
}
2014-07-03 15:02:36 +02:00
break;
}
case Spanner::Anchor::CHORD:
2016-11-16 08:54:10 +01:00
spanner()->setTick(element->tick());
2016-07-04 13:21:33 +02:00
spanner()->setTick2(spanner()->endElement()->tick());
2014-10-01 17:02:41 +02:00
spanner()->setTrack(element->track());
2016-07-04 13:21:33 +02:00
if (score()->spannerMap().removeSpanner(spanner()))
score()->addSpanner(spanner());
2014-07-03 15:02:36 +02:00
break;
case Spanner::Anchor::SEGMENT:
case Spanner::Anchor::MEASURE:
qDebug("SlurSegment::changeAnchor: bad anchor");
break;
2013-08-22 12:18:14 +02:00
}
2012-05-26 14:26:10 +02:00
}
else {
2013-08-22 12:18:14 +02:00
spanner()->setEndElement(element);
switch (spanner()->anchor()) {
2014-07-03 15:02:36 +02:00
case Spanner::Anchor::NOTE: {
2016-07-04 13:21:33 +02:00
Tie* tie = toTie(spanner());
Note* note = toNote(element);
// do not allow backward ties
2016-11-16 08:54:10 +01:00
if (note->chord()->tick() >= tie->startNote()->tick()) {
tie->endNote()->setTieBack(0);
tie->setEndNote(note);
note->setTieBack(tie);
}
2014-07-03 15:02:36 +02:00
break;
}
case Spanner::Anchor::CHORD:
2016-11-16 08:54:10 +01:00
spanner()->setTick2(element->tick());
2014-07-03 15:02:36 +02:00
spanner()->setTrack2(element->track());
break;
case Spanner::Anchor::SEGMENT:
case Spanner::Anchor::MEASURE:
qDebug("SlurSegment::changeAnchor: bad anchor");
break;
}
2012-05-26 14:26:10 +02:00
}
2013-08-22 12:18:14 +02:00
int segments = spanner()->spannerSegments().size();
ups(curGrip).off = QPointF();
2013-08-22 12:18:14 +02:00
spanner()->layout();
if (spanner()->spannerSegments().size() != segments) {
QList<SpannerSegment*>& ss = spanner()->spannerSegments();
2016-07-04 13:21:33 +02:00
SlurSegment* newSegment = toSlurSegment(curGrip == Grip::END ? ss.back() : ss.front());
// score()->endCmd();
// score()->startCmd();
2012-05-26 14:26:10 +02:00
viewer->startEdit(newSegment, curGrip);
// score()->setLayoutAll();
triggerLayout();
2012-05-26 14:26:10 +02:00
}
}
//---------------------------------------------------------
// gripAnchor
//---------------------------------------------------------
QPointF SlurSegment::gripAnchor(Grip grip) const
2012-05-26 14:26:10 +02:00
{
SlurPos spos;
slurTie()->slurPos(&spos);
QPointF pp(pagePos());
QPointF p1(ups(Grip::START).p + pp);
QPointF p2(ups(Grip::END).p + pp);
switch (spannerSegmentType()) {
case SpannerSegmentType::SINGLE:
if (grip == Grip::START)
2012-05-26 14:26:10 +02:00
return p1;
else if (grip == Grip::END)
2012-05-26 14:26:10 +02:00
return p2;
break;
case SpannerSegmentType::BEGIN:
if (grip == Grip::START)
2012-05-26 14:26:10 +02:00
return p1;
else if (grip == Grip::END)
2012-05-26 14:26:10 +02:00
return system()->abbox().topRight();
break;
case SpannerSegmentType::MIDDLE:
if (grip == Grip::START)
return system()->pagePos();
else if (grip == Grip::END)
2012-05-26 14:26:10 +02:00
return system()->abbox().topRight();
break;
case SpannerSegmentType::END:
if (grip == Grip::START)
return system()->pagePos();
else if (grip == Grip::END)
2012-05-26 14:26:10 +02:00
return p2;
break;
}
return QPointF();
}
//---------------------------------------------------------
// editDrag
//---------------------------------------------------------
2017-03-31 13:03:15 +02:00
void SlurSegment::editDrag(EditData& ed)
2012-05-26 14:26:10 +02:00
{
2016-07-04 13:21:33 +02:00
Grip g = ed.curGrip;
ups(g).off += ed.delta;
2016-07-04 13:21:33 +02:00
if (g == Grip::START || g == Grip::END) {
computeBezier();
2012-05-26 14:26:10 +02:00
//
2014-03-16 13:38:57 +01:00
// move anchor for slurs/ties
2012-05-26 14:26:10 +02:00
//
2016-07-04 13:21:33 +02:00
if ((g == Grip::START && isSingleBeginType()) || (g == Grip::END && isSingleEndType())) {
2014-05-05 12:25:17 +02:00
Spanner* spanner = slurTie();
Qt::KeyboardModifiers km = qApp->keyboardModifiers();
2014-05-06 14:00:32 +02:00
Note* note = static_cast<Note*>(ed.view->elementNear(ed.pos));
2016-07-04 13:21:33 +02:00
if (note && note->isNote()
&& ((g == Grip::END && note->tick() > slurTie()->tick()) || (g == Grip::START && note->tick() < slurTie()->tick2()))
2014-05-06 14:00:32 +02:00
) {
if (km != (Qt::ShiftModifier | Qt::ControlModifier)) {
2014-05-05 12:25:17 +02:00
Chord* c = note->chord();
ed.view->setDropTarget(note);
if (c != spanner->endCR()) {
2016-07-04 13:21:33 +02:00
changeAnchor(ed.view, g, c);
}
}
}
2014-05-05 12:25:17 +02:00
else
ed.view->setDropTarget(0);
2012-05-26 14:26:10 +02:00
}
}
else if (ed.curGrip == Grip::BEZIER1 || ed.curGrip == Grip::BEZIER2) {
computeBezier();
}
else if (ed.curGrip == Grip::SHOULDER) {
ups(ed.curGrip).off = QPointF();
computeBezier(ed.delta);
2012-05-26 14:26:10 +02:00
}
else if (ed.curGrip == Grip::DRAG) {
ups(Grip::DRAG).off = QPointF();
2012-05-26 14:26:10 +02:00
setUserOff(userOff() + ed.delta);
}
2016-06-09 09:26:13 +02:00
undoChangeProperty(P_ID::AUTOPLACE, false);
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// computeBezier
// compute help points of slur bezier segment
//---------------------------------------------------------
void SlurSegment::computeBezier(QPointF p6o)
2012-05-26 14:26:10 +02:00
{
qreal _spatium = spatium();
qreal shoulderW; // height as fraction of slur-length
qreal shoulderH;
//
// p1 and p2 are the end points of the slur
//
QPointF pp1 = ups(Grip::START).p + ups(Grip::START).off;
QPointF pp2 = ups(Grip::END).p + ups(Grip::END).off;
2012-05-26 14:26:10 +02:00
QPointF p2 = pp2 - pp1;
if ((p2.x() == 0.0) && (p2.y() == 0.0)) {
Measure* m1 = slur()->startCR()->segment()->measure();
Measure* m2 = slur()->endCR()->segment()->measure();
2016-07-15 19:57:02 +02:00
qDebug("zero slur at tick %d(%d) track %d in measure %d-%d tick %d ticks %d",
m1->tick(), tick(), track(), m1->no(), m2->no(), slur()->tick(), slur()->ticks());
slur()->setBroken(true);
2012-05-26 14:26:10 +02:00
return;
}
qreal sinb = atan(p2.y() / p2.x());
QTransform t;
t.rotateRadians(-sinb);
p2 = t.map(p2);
p6o = t.map(p6o);
double smallH = 0.5;
qreal d = p2.x() / _spatium;
if (d <= 2.0) {
shoulderH = d * 0.5 * smallH * _spatium;
shoulderW = .6;
}
else {
qreal dd = log10(1.0 + (d - 2.0) * .5) * 2.0;
if (dd > 3.0)
dd = 3.0;
shoulderH = (dd + smallH) * _spatium;
if (d > 18.0)
2012-11-11 14:24:02 +01:00
shoulderW = 0.7; // 0.8;
2012-05-26 14:26:10 +02:00
else if (d > 10)
2012-11-11 14:24:02 +01:00
shoulderW = 0.6; // 0.7;
2012-05-26 14:26:10 +02:00
else
2012-11-11 14:24:02 +01:00
shoulderW = 0.5; // 0.6;
2012-05-26 14:26:10 +02:00
}
shoulderH -= p6o.y();
if (!slur()->up())
2012-05-26 14:26:10 +02:00
shoulderH = -shoulderH;
qreal c = p2.x();
qreal c1 = (c - c * shoulderW) * .5 + p6o.x();
qreal c2 = c1 + c * shoulderW + p6o.x();
QPointF p5 = QPointF(c * .5, 0.0);
QPointF p3(c1, -shoulderH);
QPointF p4(c2, -shoulderH);
2016-03-19 11:41:38 +01:00
qreal w = score()->styleP(StyleIdx::SlurMidWidth) - score()->styleP(StyleIdx::SlurEndWidth);
if ((c2 - c1) <= _spatium)
2012-05-26 14:26:10 +02:00
w *= .5;
QPointF th(0.0, w); // thickness of slur
QPointF p3o = p6o + t.map(ups(Grip::BEZIER1).off);
QPointF p4o = p6o + t.map(ups(Grip::BEZIER2).off);
2012-05-26 14:26:10 +02:00
2013-06-10 11:03:34 +02:00
if (!p6o.isNull()) {
QPointF p6i = t.inverted().map(p6o);
ups(Grip::BEZIER1).off += p6i ;
ups(Grip::BEZIER2).off += p6i;
}
2012-05-26 14:26:10 +02:00
//-----------------------------------calculate p6
QPointF pp3 = p3 + p3o;
QPointF pp4 = p4 + p4o;
QPointF ppp4 = pp4 - pp3;
qreal r2 = atan(ppp4.y() / ppp4.x());
t.reset();
t.rotateRadians(-r2);
QPointF p6 = QPointF(t.map(ppp4).x() * .5, 0.0);
t.rotateRadians(2 * r2);
p6 = t.map(p6) + pp3 - p6o;
2012-05-26 14:26:10 +02:00
//-----------------------------------
path = QPainterPath();
path.moveTo(QPointF());
path.cubicTo(p3 + p3o - th, p4 + p4o - th, p2);
if (slur()->lineType() == 0)
path.cubicTo(p4 +p4o + th, p3 + p3o + th, QPointF());
2012-05-26 14:26:10 +02:00
th = QPointF(0.0, 3.0 * w);
shapePath = QPainterPath();
shapePath.moveTo(QPointF());
shapePath.cubicTo(p3 + p3o - th, p4 + p4o - th, p2);
shapePath.cubicTo(p4 +p4o + th, p3 + p3o + th, QPointF());
2012-05-26 14:26:10 +02:00
// translate back
t.reset();
t.translate(pp1.x(), pp1.y());
t.rotateRadians(sinb);
path = t.map(path);
shapePath = t.map(shapePath);
ups(Grip::BEZIER1).p = t.map(p3);
ups(Grip::BEZIER2).p = t.map(p4);
ups(Grip::END).p = t.map(p2) - ups(Grip::END).off;
ups(Grip::DRAG).p = t.map(p5);
ups(Grip::SHOULDER).p = t.map(p6);
2014-11-06 10:44:57 +01:00
2016-11-05 19:46:39 +01:00
QPainterPath p;
p.moveTo(QPointF());
p.cubicTo(p3 + p3o - th, p4 + p4o - th, p2);
_shape.clear();
2016-11-05 19:46:39 +01:00
QPointF start;
start = t.map(start);
int nbShapes = 15;
2016-11-05 19:46:39 +01:00
for (int i = 1; i <= nbShapes; i++) {
QPointF point = t.map(p.pointAtPercent(i/float(nbShapes)));
QRectF re(start, point);
_shape.add(re);
2016-11-05 19:46:39 +01:00
start = point;
}
}
//---------------------------------------------------------
// slurDistance
//---------------------------------------------------------
2016-11-05 19:46:39 +01:00
inline static qreal slurDistance(const Shape& shape, const QPointF& pt, qreal sdist, bool up)
{
qreal ddy;
if (up) {
ddy = -shape.bottomDistance(pt) + sdist;
if (ddy <= 0.0) // assume no more collisions
2017-06-24 14:32:02 +02:00
ddy = 0.0;
}
else {
ddy = shape.topDistance(pt) - sdist;
if (ddy >= 0.0)
ddy = 0.0;
}
return ddy;
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
2017-05-23 17:23:28 +02:00
// layoutSegment
2012-05-26 14:26:10 +02:00
// p1, p2 are in System coordinates
//---------------------------------------------------------
void SlurSegment::layoutSegment(const QPointF& p1, const QPointF& p2)
2012-05-26 14:26:10 +02:00
{
2016-06-09 09:26:13 +02:00
if (autoplace()) {
// TODO: must be saved when switching to autoplace
for (UP& up : _ups)
up.off = QPointF();
rUserYoffset() = 0;
2016-06-09 09:26:13 +02:00
}
ups(Grip::START).p = p1;
ups(Grip::END).p = p2;
computeBezier();
2016-11-16 08:54:10 +01:00
if (autoplace() && system()) {
bool up = slur()->up();
//
// lookup segments for collision detection
//
struct Collision {
qreal dist;
QPointF p;
Collision(qreal a, const QPointF b) : dist(a), p(b) {}
};
QList<Collision> pl; // skyline
2017-06-24 15:52:00 +02:00
qreal sdist = score()->styleP(StyleIdx::SlurMinDistance);
QPointF pp1 = ups(Grip::START).p;
QPointF pp2 = ups(Grip::END).p;
Segment* ls = system()->lastMeasure()->last();
Segment* fs = system()->firstMeasure()->first();
for (Segment* s = fs; s && s != ls; s = s->next1()) {
if (!s->enabled())
continue;
qreal x1 = s->x() + s->measure()->x();
qreal x2 = x1 + s->width();
if (pp1.x() > x2)
continue;
if (pp2.x() >= x1 && pp2.x() < x2)
break;
if (up) {
2017-06-24 14:32:02 +02:00
QPointF pt = QPointF(s->x() + s->measure()->x(), s->staffShape(staffIdx()).top() + s->y() + s->measure()->y());
qreal dist = _shape.bottomDistance(pt) - sdist;
if (dist < 0.0)
pl.append(Collision(-dist, pt));
}
else {
2017-06-24 14:32:02 +02:00
QPointF pt = QPointF(s->x() + s->measure()->x(), s->staffShape(staffIdx()).bottom() + s->y() + s->measure()->y());
qreal dist = _shape.topDistance(pt) - sdist;
if (dist < 0.0)
pl.append(Collision(dist, pt));
}
2014-08-08 17:31:33 +02:00
}
//
// compute shape
// pl contains a list of colliding points
//
2017-06-24 14:32:02 +02:00
#if 0 // does not work
for (int i = 0; i < pl.size() - 1; ++i) {
Collision& c1 = pl[i];
Collision& c2 = pl[i+1];
if (qAbs(c1.p.y() - c2.p.y()) < 0.1) {
// combine the collisions
2017-06-24 14:32:02 +02:00
printf(" combine\n");
c1.p.rx() += (c2.p.x() - c1.p.x()) / 2.0;
2016-11-16 08:54:10 +01:00
pl.erase(pl.begin() + i + 1);
}
}
2017-06-24 14:32:02 +02:00
#endif
if (!pl.empty()) {
// printf("%p collisions %d\n", this, pl.size());
qSort(pl.begin(), pl.end(), [](const Collision& a, const Collision& b) { return a.dist < b.dist; });
for (int i = 0; i < pl.size(); ++i) {
qreal ddy = pl[i].dist;
qreal x = pl[i].p.x();
qreal x1 = ups(Grip::BEZIER1).p.x(); // p1.x();
qreal x2 = ups(Grip::BEZIER2).p.x(); // p2.x();
qreal ratio = (x - x1) / (x2 - x1);
2017-06-24 14:32:02 +02:00
for (int k = 0; k < 30; ++k) {
const qreal magic = 1.1;
_ups[int(Grip::BEZIER1)].off.ry() -= ddy * magic * (1.0 - ratio);
_ups[int(Grip::BEZIER2)].off.ry() -= ddy * magic * ratio;
computeBezier();
2017-06-24 14:32:02 +02:00
qreal nddy = slurDistance(_shape, pl[i].p, sdist, up);
// printf("%d %d ddy %f nddy %f magic %f ratio %f\n", i, k, ddy, nddy, magic, ratio);
if (qAbs(nddy) <= spatium() * 0.1) {
// printf(" collision %d -- tries %d\n", i, k);
break;
}
2017-06-24 14:32:02 +02:00
ddy = nddy;
}
2014-03-30 18:14:55 +02:00
2017-06-24 14:32:02 +02:00
// ++i;
// if (i == pl.size())
// break;
// ddy = slurDistance(_shape, pl[i].p, sdist, up);
// printf("%p %f %f < %f\n", this, ddy, qAbs(ddy), minDistance);
// if (ddy < minDistance)
// break;
}
}
#if 0
else {
// fallback: move whole slur
qreal ddy = up ? -1000000 : +1000000;
for (const Collision& c : pl)
ddy = up ? qMax(ddy, c.dist) : qMin(ddy, c.dist);
rUserYoffset() = -ddy;
}
#endif
2014-03-30 18:14:55 +02:00
}
else {
if ((staffIdx() > 0) && score()->mscVersion() < 206 && !readPos().isNull()) {
QPointF staffOffset;
if (system() && track() >= 0)
staffOffset = QPointF(0.0, system()->staff(staffIdx())->y());
setReadPos(readPos() + staffOffset);
}
adjustReadPos();
}
setbbox(path.boundingRect());
2014-03-30 18:14:55 +02:00
}
2014-04-01 03:39:01 +02:00
//---------------------------------------------------------
// isEdited
//---------------------------------------------------------
bool SlurSegment::isEdited() const
{
for (int i = 0; i < int(Grip::GRIPS); ++i) {
if (!_ups[i].off.isNull())
2014-04-01 03:39:01 +02:00
return true;
}
return false;
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// fixArticulations
//---------------------------------------------------------
static qreal fixArticulations(qreal yo, Chord* c, qreal _up)
{
//
// handle special case of tenuto and staccato;
//
2016-02-06 22:03:43 +01:00
const QVector<Articulation*>& al = c->articulations();
2012-11-20 20:51:18 +01:00
if (al.size() >= 2) {
Articulation* a = al.at(1);
if (a->up() == c->up())
return yo;
2016-10-06 12:21:28 +02:00
else if (a->isTenuto() || a->isStaccato())
2012-05-26 14:26:10 +02:00
return a->y() + (a->height() + c->score()->spatium() * .3) * _up;
}
2012-11-20 20:51:18 +01:00
else if (al.size() >= 1) {
Articulation* a = al.at(0);
if (a->up() == c->up())
return yo;
2016-10-06 12:21:28 +02:00
else if (a->isTenuto() || a->isStaccato())
2012-05-26 14:26:10 +02:00
return a->y() + (a->height() + c->score()->spatium() * .3) * _up;
}
return yo;
}
2013-06-16 23:33:37 +02:00
//---------------------------------------------------------
// slurPos
// Calculate position of start- and endpoint of slur
// relative to System() position.
//---------------------------------------------------------
void Slur::slurPosChord(SlurPos* sp)
{
2014-04-23 18:07:38 +02:00
Chord* stChord;
Chord* enChord ;
2014-07-02 09:55:50 +02:00
if (startChord()->isGraceAfter()){ // grace notes after, coming in reverse order
2014-04-23 18:07:38 +02:00
stChord = endChord();
enChord = startChord();
_up = false;
}
2017-05-23 17:23:28 +02:00
else {
2014-04-23 18:07:38 +02:00
stChord = startChord();
enChord = endChord();
}
Note* _startNote = stChord->downNote();
Note* _endNote = enChord->downNote();
2013-06-16 23:33:37 +02:00
qreal hw = _startNote->headWidth();
qreal __up = _up ? -1.0 : 1.0;
qreal _spatium = spatium();
2014-07-03 15:02:36 +02:00
Measure* measure = endChord()->measure();
sp->system1 = measure->system();
2017-05-23 17:23:28 +02:00
if (!sp->system1) { // DEBUG
qDebug("no system1");
2014-07-03 15:02:36 +02:00
return;
2017-05-23 17:23:28 +02:00
}
2014-07-03 15:02:36 +02:00
Q_ASSERT(sp->system1);
2013-06-16 23:33:37 +02:00
sp->system2 = sp->system1;
QPointF pp(sp->system1->pagePos());
qreal xo;
qreal yo;
//------p1
if (_up) {
xo = _startNote->x() + hw * 1.12;
yo = _startNote->pos().y() + hw * .3 * __up;
}
else {
xo = _startNote->x() + hw * 0.4;
yo = _startNote->pos().y() + _spatium * .75 * __up;
}
2014-04-23 18:07:38 +02:00
sp->p1 = stChord->pagePos() - pp + QPointF(xo, yo);
2013-06-16 23:33:37 +02:00
//------p2
2014-04-23 18:07:38 +02:00
if ((enChord->notes().size() > 1) || (enChord->stem() && !enChord->up() && !_up)) {
2013-06-16 23:33:37 +02:00
xo = _endNote->x() - hw * 0.12;
yo = _endNote->pos().y() + hw * .3 * __up;
}
else {
xo = _endNote->x() + hw * 0.15;
yo = _endNote->pos().y() + _spatium * .75 * __up;
}
2014-04-23 18:07:38 +02:00
sp->p2 = enChord->pagePos() - pp + QPointF(xo, yo);
2013-06-16 23:33:37 +02:00
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// slurPos
// calculate position of start- and endpoint of slur
// relative to System() position
//---------------------------------------------------------
void Slur::slurPos(SlurPos* sp)
{
qreal _spatium = spatium();
2013-06-16 23:33:37 +02:00
if (endCR() == 0) {
sp->p1 = startCR()->pagePos();
sp->p1.rx() += startCR()->width();
2012-05-26 14:26:10 +02:00
sp->p2 = sp->p1;
sp->p2.rx() += 5 * _spatium;
2013-06-16 23:33:37 +02:00
sp->system1 = startCR()->measure()->system();
2012-05-26 14:26:10 +02:00
sp->system2 = sp->system1;
return;
}
2016-12-13 13:16:17 +01:00
bool useTablature = staff() && staff()->isTabStaff(endCR()->tick());
2016-12-09 15:56:48 +01:00
bool staffHasStems = true; // assume staff uses stems
StaffType* stt = 0;
if (useTablature) {
2016-12-13 13:16:17 +01:00
stt = staff()->staffType(tick());
2016-12-09 15:56:48 +01:00
staffHasStems = stt->stemThrough(); // if tab with stems beside, stems do not count for slur pos
}
// start and end cr, chord, and note
2014-07-08 11:16:21 +02:00
ChordRest* scr = startCR();
ChordRest* ecr = endCR();
Chord* sc = 0;
Note* note1 = 0;
2016-07-04 13:21:33 +02:00
if (scr->isChord()) {
sc = toChord(scr);
note1 = _up ? sc->upNote() : sc->downNote();
}
Chord* ec = 0;
Note* note2 = 0;
2016-07-04 13:21:33 +02:00
if (ecr->isChord()) {
ec = toChord(ecr);
note2 = _up ? ec->upNote() : ec->downNote();
2012-05-26 14:26:10 +02:00
}
sp->system1 = scr->measure()->system();
sp->system2 = ecr->measure()->system();
2016-12-09 15:56:48 +01:00
2017-05-23 17:23:28 +02:00
if (sp->system1 == 0) {
qDebug("no system1");
2013-10-14 15:34:46 +02:00
return;
2017-05-23 17:23:28 +02:00
}
2013-10-14 15:34:46 +02:00
sp->p1 = scr->pos() + scr->segment()->pos() + scr->measure()->pos();
sp->p2 = ecr->pos() + ecr->segment()->pos() + ecr->measure()->pos();
2017-05-23 17:23:28 +02:00
2014-07-22 17:39:25 +02:00
// account for centering or other adjustments (other than mirroring)
if (note1 && !note1->mirror())
2014-07-22 17:39:25 +02:00
sp->p1.rx() += note1->x();
if (note2 && !note2->mirror())
2014-07-22 17:39:25 +02:00
sp->p2.rx() += note2->x();
2012-05-26 14:26:10 +02:00
qreal xo, yo;
Stem* stem1 = sc && staffHasStems ? sc->stem() : 0;
Stem* stem2 = ec && staffHasStems ? ec->stem() : 0;
2012-05-26 14:26:10 +02:00
enum class SlurAnchor : char {
NONE, STEM
};
SlurAnchor sa1 = SlurAnchor::NONE;
SlurAnchor sa2 = SlurAnchor::NONE;
// if slur is 'embedded' between either stem or both (as it might happen with voices)
// link corresponding slur end to stem position
if ((scr->up() == ecr->up()) && !scr->beam() && !ecr->beam() && (_up == scr->up())) {
// both chords are facing same direction and slur is also in same direction
// and no beams
if (stem1)
sa1 = SlurAnchor::STEM;
if (stem2)
sa2 = SlurAnchor::STEM;
}
qreal __up = _up ? -1.0 : 1.0;
qreal hw1 = note1 ? note1->tabHeadWidth(stt) : scr->width(); // if stt == 0, tabHeadWidth()
qreal hw2 = note2 ? note2->tabHeadWidth(stt) : ecr->width(); // defaults to headWidth()
QPointF pt;
switch (sa1) {
case SlurAnchor::STEM: //sc can't be null
// place slur starting point at stem base point
pt = sc->stemPos() - sc->pagePos() + sc->stem()->p2();
if (useTablature) // in tabs, stems are centred on note:
pt.rx() = hw1 * 0.5; // skip half notehead to touch stem
sp->p1 += pt;
sp->p1 += QPointF(0.35 * _spatium, 0.25 * _spatium); // clear the stem (x) and the notehead (y)
break;
case SlurAnchor::NONE:
break;
}
switch (sa2) {
case SlurAnchor::STEM: //ec can't be null
pt = ec->stemPos() - ec->pagePos() + ec->stem()->p2();
if (useTablature)
pt.rx() = hw2 * 0.5;
sp->p2 += pt;
sp->p2 += QPointF(-0.35 * _spatium, 0.25 * _spatium);
break;
case SlurAnchor::NONE:
break;
}
2012-05-26 14:26:10 +02:00
//
// default position:
// horizontal: middle of notehead
// vertical: _spatium * .4 above/below notehead
2012-05-26 14:26:10 +02:00
//
//------p1
// Compute x0, y0 and stemPos
if (sa1 == SlurAnchor::NONE || sa2 == SlurAnchor::NONE) { // need stemPos if sa2 == SlurAnchor::NONE
bool stemPos = false; // p1 starts at chord stem side
// default positions
xo = hw1 * .5;
if (note1)
yo = note1->pos().y();
else if (_up)
yo = scr->bbox().top();
else
yo = scr->bbox().top() + scr->height();
yo += _spatium * .9 * __up;
// adjustments for stem and/or beam
if (stem1) { //sc not null
Beam* beam1 = sc->beam();
if (beam1 && (beam1->elements().back() != sc) && (sc->up() == _up)) {
// start chord is beamed but not the last chord of beam group
// and slur direction is same as start chord (stem side)
// in these cases, layout start of slur to stem
qreal sh = stem1->height() + _spatium;
if (_up)
yo = sc->downNote()->pos().y() - sh;
else
yo = sc->upNote()->pos().y() + sh;
xo = stem1->pos().x();
// force end of slur to layout to stem as well,
// if start and end chords have same stem direction
stemPos = true;
}
else {
// start chord is not beamed or is last chord of beam group
// or slur direction is opposite that of start chord
// at this point slur is in default position relative to note on slur side
// but we may need to make further adjustments
// if stem and slur are both up
// we need to clear stem horizontally
if (sc->up() && _up)
xo = hw1 + _spatium * .3;
//
// handle case: stem up - stem down
// stem down - stem up
//
if ((sc->up() != ecr->up()) && (sc->up() == _up)) {
// start and end chord have opposite direction
// and slur direction is same as start chord
// (so slur starts on stem side)
// float the start point along the stem to follow direction of movement
// see for example Gould p. 111
// get position of note on slur side for start & end chords
Note* n1 = sc->up() ? sc->upNote() : sc->downNote();
Note* n2 = 0;
if (ec)
n2 = ec->up() ? ec->upNote() : ec->downNote();
// differential in note positions
qreal yd = (n2 ? n2->pos().y() : ecr->pos().y()) - n1->pos().y();
yd *= .5;
// float along stem according to differential
qreal sh = stem1->height();
if (_up && yd < 0.0)
yo = qMax(yo + yd, sc->downNote()->pos().y() - sh - _spatium);
else if (!_up && yd > 0.0)
yo = qMin(yo + yd, sc->upNote()->pos().y() + sh + _spatium);
// we may wish to force end to align to stem as well,
// if it is in same direction
// (but it won't be, so this assignment should have no effect)
stemPos = true;
2012-05-26 14:26:10 +02:00
}
else if (sc->up() != _up) {
// slur opposite direction from chord
// avoid articulations
yo = fixArticulations(yo, sc, __up);
}
2012-05-26 14:26:10 +02:00
}
}
else if (sc && sc->up() != _up) {
// slur opposite direction from chord
// avoid articulations
yo = fixArticulations(yo, sc, __up);
}
if (sa1 == SlurAnchor::NONE)
sp->p1 += QPointF(xo, yo);
//------p2
if (sa2 == SlurAnchor::NONE) {
// default positions
xo = hw2 * .5;
if (note2)
yo = note2->pos().y();
else if (_up)
yo = endCR()->bbox().top();
2012-05-26 14:26:10 +02:00
else
yo = endCR()->bbox().top() + endCR()->height();
yo += _spatium * .9 * __up;
// adjustments for stem and/or beam
if (stem2) { //ec can't be null
Beam* beam2 = ec->beam();
if ((stemPos && (scr->up() == ec->up()))
|| (beam2
2016-02-06 22:03:43 +01:00
&& (!beam2->elements().empty())
&& (beam2->elements().front() != ec)
&& (ec->up() == _up)
&& sc && (sc->noteType() == NoteType::NORMAL)
)
) {
// slur start was laid out to stem and start and end have same direction
// OR
// end chord is beamed but not the first chord of beam group
// and slur direction is same as end chord (stem side)
// and start chordrest is not a grace chord
// in these cases, layout end of slur to stem
qreal sh = stem2->height() + _spatium;
if (_up)
yo = ec->downNote()->pos().y() - sh;
else
yo = ec->upNote()->pos().y() + sh;
xo = stem2->pos().x();
}
else
{
// slur was not aligned to stem or start and end have different direction
// AND
// end chord is not beamed or is first chord of beam group
// or slur direction is opposite that of end chord
// if stem and slur are both down,
// we need to clear stem horizontally
if (!ec->up() && !_up)
xo = -_spatium * .3 + note2->x();
//
// handle case: stem up - stem down
// stem down - stem up
//
if ((scr->up() != ec->up()) && (ec->up() == _up)) {
// start and end chord have opposite direction
// and slur direction is same as end chord
// (so slur end on stem side)
// float the end point along the stem to follow direction of movement
// see for example Gould p. 111
Note* n1 = 0;
if (sc)
n1 = sc->up() ? sc->upNote() : sc->downNote();
Note* n2 = ec->up() ? ec->upNote() : ec->downNote();
qreal yd = n2->pos().y() - (n1 ? n1->pos().y() : startCR()->pos().y());
yd *= .5;
qreal mh = stem2->height();
if (_up && yd > 0.0)
yo = qMax(yo - yd, ec->downNote()->pos().y() - mh - _spatium);
else if (!_up && yd < 0.0)
yo = qMin(yo - yd, ec->upNote()->pos().y() + mh + _spatium);
}
else if (ec->up() != _up) {
// slur opposite direction from chord
// avoid articulations
yo = fixArticulations(yo, ec, __up);
}
}
2012-05-26 14:26:10 +02:00
}
else if (ec && ec->up() != _up) {
// slur opposite direction from chord
// avoid articulations
yo = fixArticulations(yo, ec, __up);
}
2012-05-26 14:26:10 +02:00
sp->p2 += QPointF(xo, yo);
2012-05-26 14:26:10 +02:00
}
}
}
//---------------------------------------------------------
// Slur
//---------------------------------------------------------
Slur::Slur(Score* s)
: SlurTie(s)
{
setAnchor(Anchor::CHORD);
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// Slur
//---------------------------------------------------------
Slur::~Slur()
{
}
//---------------------------------------------------------
// write
//---------------------------------------------------------
2016-11-19 11:51:21 +01:00
void Slur::write(XmlWriter& xml) const
2012-05-26 14:26:10 +02:00
{
2016-10-26 11:33:51 +02:00
if (broken()) {
2016-07-18 12:34:27 +02:00
qDebug("broken slur not written");
return;
}
2014-08-15 17:20:20 +02:00
if (!xml.canWrite(this))
return;
2014-07-21 13:24:21 +02:00
xml.stag(QString("Slur id=\"%1\"").arg(xml.spannerId(this)));
2012-05-26 14:26:10 +02:00
SlurTie::writeProperties(xml);
xml.etag();
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
2013-01-11 18:10:18 +01:00
void Slur::read(XmlReader& e)
2012-05-26 14:26:10 +02:00
{
2013-10-01 09:31:04 +02:00
setTrack(e.track()); // set staff
2014-07-21 13:24:21 +02:00
e.addSpanner(e.intAttribute("id"), this);
2013-01-11 18:10:18 +01:00
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
2013-09-27 11:18:30 +02:00
if (tag == "track2")
setTrack2(e.readInt());
2012-05-26 14:26:10 +02:00
else if (tag == "startTrack") // obsolete
2013-01-11 18:10:18 +01:00
setTrack(e.readInt());
2012-05-26 14:26:10 +02:00
else if (tag == "endTrack") // obsolete
2013-06-10 11:03:34 +02:00
e.readInt();
2012-05-26 14:26:10 +02:00
else if (!SlurTie::readProperties(e))
2013-01-11 18:10:18 +01:00
e.unknown();
2012-05-26 14:26:10 +02:00
}
if (track2() == -1)
2013-10-01 09:31:04 +02:00
setTrack2(track());
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// chordsHaveTie
//---------------------------------------------------------
static bool chordsHaveTie(Chord* c1, Chord* c2)
{
2013-01-03 08:42:40 +01:00
int n1 = c1->notes().size();
for (int i1 = 0; i1 < n1; ++i1) {
Note* n1 = c1->notes().at(i1);
int n2 = c2->notes().size();
for (int i2 = 0; i2 < n2; ++i2) {
Note* n2 = c2->notes().at(i2);
2012-05-26 14:26:10 +02:00
if (n1->tieFor() && n1->tieFor() == n2->tieBack())
return true;
}
}
return false;
}
//---------------------------------------------------------
// directionMixture
//---------------------------------------------------------
static bool isDirectionMixture(Chord* c1, Chord* c2)
{
bool up = c1->up();
2017-03-08 13:12:26 +01:00
for (Segment* seg = c1->segment(); seg; seg = seg->next(SegmentType::ChordRest)) {
2016-07-05 14:23:10 +02:00
Element* e = seg->element(c1->track());
if (!e || !e->isChord())
2012-05-26 14:26:10 +02:00
continue;
2016-07-05 14:23:10 +02:00
Chord* c = toChord(e);
2012-05-26 14:26:10 +02:00
if (c->up() != up)
return true;
if (seg == c2->segment())
break;
}
return false;
}
2016-07-01 12:42:15 +02:00
//---------------------------------------------------------
// layoutSystem
2016-12-09 15:56:48 +01:00
// layout slurSegment for system
2016-07-01 12:42:15 +02:00
//---------------------------------------------------------
2016-07-10 12:00:57 +02:00
SpannerSegment* Slur::layoutSystem(System* system)
2016-07-01 12:42:15 +02:00
{
int stick = system->firstMeasure()->tick();
int etick = system->lastMeasure()->endTick();
SlurSegment* slurSegment = 0;
for (SpannerSegment* ss : segments) {
if (!ss->system()) {
2016-07-04 13:21:33 +02:00
slurSegment = toSlurSegment(ss);
2016-07-01 12:42:15 +02:00
break;
}
}
if (!slurSegment) {
slurSegment = new SlurSegment(score());
add(slurSegment);
}
slurSegment->setSystem(system);
slurSegment->setSpanner(this);
SpannerSegmentType sst;
if (tick() >= stick) {
//
// this is the first call to layoutSystem,
// processing the first line segment
//
if (track2() == -1)
setTrack2(track());
if (startCR() == 0 || startCR()->measure() == 0) {
qDebug("Slur::layout(): track %d-%d %p - %p tick %d-%d null start anchor",
track(), track2(), startCR(), endCR(), tick(), tick2());
2016-07-10 12:00:57 +02:00
return slurSegment;
2016-07-01 12:42:15 +02:00
}
if (endCR() == 0) { // sanity check
setEndElement(startCR());
setTick2(tick());
}
switch (_slurDirection) {
case Direction::UP:
_up = true;
break;
case Direction::DOWN:
_up = false;
break;
case Direction::AUTO:
{
//
// assumption:
// slurs have only chords or rests as start/end elements
//
if (startCR() == 0 || endCR() == 0) {
_up = true;
break;
}
Chord* c1 = startCR()->isChord() ? toChord(startCR()) : 0;
Chord* c2 = endCR()->isChord() ? toChord(endCR()) : 0;
2016-07-01 12:42:15 +02:00
_up = !(startCR()->up());
Measure* m1 = startCR()->measure();
if ((endCR()->tick() - startCR()->tick()) > m1->ticks()) // long slurs are always above
_up = true;
else
_up = !startCR()->up();
if (c1 && c2 && isDirectionMixture(c1, c2) && !c1->isGrace()) {
// slurs go above if start and end note have different stem directions,
// but grace notes are exceptions
_up = true;
}
2016-12-12 12:02:18 +01:00
else if (m1->hasVoices(startCR()->staffIdx()) && c1 && !c1->isGrace()) {
2016-07-01 12:42:15 +02:00
// in polyphonic passage, slurs go on the stem side
_up = startCR()->up();
}
else if (c1 && c2 && chordsHaveTie(c1, c2)) {
// could confuse slur with tie, put slur on stem side
_up = startCR()->up();
}
}
break;
}
2016-12-09 15:56:48 +01:00
sst = tick2() < etick ? SpannerSegmentType::SINGLE : SpannerSegmentType::BEGIN;
2016-07-01 12:42:15 +02:00
}
else if (tick() < stick && tick2() > etick)
sst = SpannerSegmentType::MIDDLE;
else
sst = SpannerSegmentType::END;
slurSegment->setSpannerSegmentType(sst);
SlurPos sPos;
slurPos(&sPos);
switch (sst) {
case SpannerSegmentType::SINGLE:
slurSegment->layoutSegment(sPos.p1, sPos.p2);
break;
case SpannerSegmentType::BEGIN:
slurSegment->layoutSegment(sPos.p1, QPointF(system->bbox().width(), sPos.p1.y()));
break;
case SpannerSegmentType::MIDDLE: {
qreal x1 = firstNoteRestSegmentX(system);
qreal x2 = system->bbox().width();
qreal y = staffIdx() > system->staves()->size() ? system->y() : system->staff(staffIdx())->y();
slurSegment->layoutSegment(QPointF(x1, y), QPointF(x2, y));
}
break;
case SpannerSegmentType::END:
slurSegment->layoutSegment(QPointF(firstNoteRestSegmentX(system), sPos.p2.y()), sPos.p2);
break;
}
QList<SpannerSegment*> 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);
2016-07-10 12:00:57 +02:00
return slurSegment;
2016-07-01 12:42:15 +02:00
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// layout
//---------------------------------------------------------
void Slur::layout()
{
2013-09-27 20:37:40 +02:00
if (track2() == -1)
2013-10-03 08:05:32 +02:00
setTrack2(track());
2013-09-27 20:37:40 +02:00
2012-05-26 14:26:10 +02:00
qreal _spatium = spatium();
2013-06-10 11:03:34 +02:00
if (score() == gscore || tick() == -1) {
2012-05-26 14:26:10 +02:00
//
// when used in a palette, slur has no parent and
// tick and tick2 has no meaning so no layout is
// possible and needed
//
SlurSegment* s;
2016-02-06 22:03:43 +01:00
if (spannerSegments().empty()) {
2012-05-26 14:26:10 +02:00
s = new SlurSegment(score());
s->setTrack(track());
add(s);
}
else {
s = frontSegment();
}
s->setSpannerSegmentType(SpannerSegmentType::SINGLE);
s->layoutSegment(QPointF(0, 0), QPointF(_spatium * 6, 0));
2012-05-26 14:26:10 +02:00
setbbox(frontSegment()->bbox());
return;
}
2013-06-20 18:48:28 +02:00
if (startCR() == 0 || startCR()->measure() == 0) {
2014-07-21 13:24:21 +02:00
qDebug("Slur::layout(): track %d-%d %p - %p tick %d-%d null start anchor",
track(), track2(), startCR(), endCR(), tick(), tick2());
2013-06-10 21:13:04 +02:00
return;
}
2014-08-12 20:10:46 +02:00
if (endCR() == 0) { // sanity check
setEndElement(startCR());
setTick2(tick());
}
2012-05-26 14:26:10 +02:00
switch (_slurDirection) {
2016-03-02 13:20:19 +01:00
case Direction::UP:
2012-05-26 14:26:10 +02:00
_up = true;
break;
2016-03-02 13:20:19 +01:00
case Direction::DOWN:
2012-05-26 14:26:10 +02:00
_up = false;
break;
2016-03-02 13:20:19 +01:00
case Direction::AUTO:
2012-05-26 14:26:10 +02:00
{
//
// assumption:
// slurs have only chords or rests as start/end elements
//
2013-06-16 23:33:37 +02:00
if (startCR() == 0 || endCR() == 0) {
2012-05-26 14:26:10 +02:00
_up = true;
break;
}
Measure* m1 = startCR()->measure();
2012-05-26 14:26:10 +02:00
2016-07-04 13:21:33 +02:00
Chord* c1 = startCR()->isChord() ? toChord(startCR()) : 0;
Chord* c2 = endCR()->isChord() ? toChord(endCR()) : 0;
2012-05-26 14:26:10 +02:00
2013-06-16 23:33:37 +02:00
_up = !(startCR()->up());
2012-05-26 14:26:10 +02:00
2013-06-16 23:33:37 +02:00
if ((endCR()->tick() - startCR()->tick()) > m1->ticks()) {
2012-05-26 14:26:10 +02:00
// long slurs are always above
_up = true;
}
else
2013-06-16 23:33:37 +02:00
_up = !(startCR()->up());
2012-05-26 14:26:10 +02:00
2014-05-27 10:35:28 +02:00
if (c1 && c2 && isDirectionMixture(c1, c2) && (c1->noteType() == NoteType::NORMAL)) {
2012-05-26 14:26:10 +02:00
// slurs go above if start and end note have different stem directions,
// but grace notes are exceptions
_up = true;
}
2016-12-12 12:02:18 +01:00
else if (m1->hasVoices(startCR()->staffIdx()) && c1 && c1->noteType() == NoteType::NORMAL) {
2012-05-26 14:26:10 +02:00
// in polyphonic passage, slurs go on the stem side
2013-06-16 23:33:37 +02:00
_up = startCR()->up();
2012-05-26 14:26:10 +02:00
}
else if (c1 && c2 && chordsHaveTie(c1, c2)) {
// could confuse slur with tie, put slur on stem side
2013-06-16 23:33:37 +02:00
_up = startCR()->up();
2012-05-26 14:26:10 +02:00
}
}
break;
}
SlurPos sPos;
slurPos(&sPos);
2016-02-04 11:27:47 +01:00
const QList<System*>& sl = score()->systems();
ciSystem is = sl.begin();
while (is != sl.end()) {
2012-05-26 14:26:10 +02:00
if (*is == sPos.system1)
break;
++is;
}
2016-02-04 11:27:47 +01:00
if (is == sl.end())
2012-05-26 14:26:10 +02:00
qDebug("Slur::layout first system not found");
setPos(0, 0);
//---------------------------------------------------------
// count number of segments, if no change, all
// user offsets (drags) are retained
//---------------------------------------------------------
unsigned nsegs = 1;
2016-02-04 11:27:47 +01:00
for (ciSystem iis = is; iis != sl.end(); ++iis) {
2016-03-02 13:20:19 +01:00
if ((*iis)->vbox())
2012-05-26 14:26:10 +02:00
continue;
if (*iis == sPos.system2)
break;
++nsegs;
}
fixupSegments(nsegs);
2016-02-04 11:27:47 +01:00
for (int i = 0; is != sl.end(); ++i, ++is) {
2012-05-26 14:26:10 +02:00
System* system = *is;
2016-03-02 13:20:19 +01:00
if (system->vbox()) {
2012-05-26 14:26:10 +02:00
--i;
continue;
}
SlurSegment* segment = segmentAt(i);
segment->setSystem(system);
// case 1: one segment
if (sPos.system1 == sPos.system2) {
segment->setSpannerSegmentType(SpannerSegmentType::SINGLE);
segment->layoutSegment(sPos.p1, sPos.p2);
2012-05-26 14:26:10 +02:00
}
// case 2: start segment
else if (i == 0) {
segment->setSpannerSegmentType(SpannerSegmentType::BEGIN);
2012-05-26 14:26:10 +02:00
qreal x = system->bbox().width();
segment->layoutSegment(sPos.p1, QPointF(x, sPos.p1.y()));
2012-05-26 14:26:10 +02:00
}
// case 3: middle segment
else if (i != 0 && system != sPos.system2) {
segment->setSpannerSegmentType(SpannerSegmentType::MIDDLE);
qreal x1 = firstNoteRestSegmentX(system);
2012-05-26 14:26:10 +02:00
qreal x2 = system->bbox().width();
2016-06-20 15:33:45 +02:00
qreal y = staffIdx() > system->staves()->size() ? system->y() : system->staff(staffIdx())->y();
segment->layoutSegment(QPointF(x1, y), QPointF(x2, y));
2012-05-26 14:26:10 +02:00
}
// case 4: end segment
else {
segment->setSpannerSegmentType(SpannerSegmentType::END);
qreal x = firstNoteRestSegmentX(system);
segment->layoutSegment(QPointF(x, sPos.p2.y()), sPos.p2);
2012-05-26 14:26:10 +02:00
}
if (system == sPos.system2)
break;
}
2016-02-06 22:03:43 +01:00
setbbox(spannerSegments().empty() ? QRectF() : frontSegment()->bbox());
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// setTrack
//---------------------------------------------------------
void Slur::setTrack(int n)
{
Element::setTrack(n);
2016-07-04 13:21:33 +02:00
for (SpannerSegment* ss : spannerSegments())
2012-05-26 14:26:10 +02:00
ss->setTrack(n);
}
//---------------------------------------------------------
// readProperties
2014-07-31 16:30:04 +02:00
//---------------------------------------------------------
bool Slur::readProperties(XmlReader& e)
2014-07-31 16:30:04 +02:00
{
const QStringRef& tag(e.name());
2014-07-31 16:30:04 +02:00
if (tag == "SlurSegment") {
int idx = e.intAttribute("no", 0);
int n = spannerSegments().size();
for (int i = n; i < idx; ++i)
add(new SlurSegment(score()));
SlurSegment* segment = new SlurSegment(score());
segment->setAutoplace(false);
segment->read(e);
add(segment);
2014-07-31 16:30:04 +02:00
}
else if (!SlurTie::readProperties(e))
return false;
return true;
2014-07-31 16:30:04 +02:00
}
2013-05-13 18:49:17 +02:00
}