2013-08-22 12:18:14 +02:00
|
|
|
//=============================================================================
|
|
|
|
// MuseScore
|
|
|
|
// Music Composition & Notation
|
|
|
|
//
|
|
|
|
// Copyright (C) 2002-2013 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 "measure.h"
|
|
|
|
#include "score.h"
|
|
|
|
#include "system.h"
|
|
|
|
#include "undo.h"
|
2016-11-15 16:33:27 +01:00
|
|
|
#include "chord.h"
|
2013-08-22 12:18:14 +02:00
|
|
|
#include "tie.h"
|
|
|
|
|
|
|
|
namespace Ms {
|
|
|
|
|
|
|
|
Note* Tie::editStartNote;
|
|
|
|
Note* Tie::editEndNote;
|
|
|
|
|
2016-11-15 16:33:27 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// updateGrips
|
|
|
|
// return grip rectangles in page coordinates
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2017-03-31 13:03:15 +02:00
|
|
|
void TieSegment::updateGrips(EditData& ed) const
|
2016-11-15 16:33:27 +01:00
|
|
|
{
|
|
|
|
QPointF p(pagePos());
|
|
|
|
p -= QPointF(0.0, system()->staff(staffIdx())->y()); // ??
|
|
|
|
for (int i = 0; i < int(Grip::GRIPS); ++i)
|
2017-03-31 13:03:15 +02:00
|
|
|
ed.grip[i].translate(_ups[i].p + _ups[i].off + p);
|
|
|
|
}
|
|
|
|
|
2016-11-15 16:33:27 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// draw
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void TieSegment::draw(QPainter* painter) const
|
|
|
|
{
|
|
|
|
// hide tie toward the second chord of a cross-measure value
|
|
|
|
if (tie()->endNote() && tie()->endNote()->chord()->crossMeasure() == CrossMeasure::SECOND)
|
|
|
|
return;
|
|
|
|
|
|
|
|
QPen pen(curColor());
|
|
|
|
switch (slurTie()->lineType()) {
|
|
|
|
case 0:
|
|
|
|
painter->setBrush(QBrush(pen.color()));
|
|
|
|
pen.setCapStyle(Qt::RoundCap);
|
|
|
|
pen.setJoinStyle(Qt::RoundJoin);
|
|
|
|
pen.setWidthF(score()->styleP(StyleIdx::SlurEndWidth));
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
painter->setBrush(Qt::NoBrush);
|
|
|
|
pen.setWidthF(score()->styleP(StyleIdx::SlurDottedWidth));
|
|
|
|
pen.setStyle(Qt::DotLine);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
painter->setBrush(Qt::NoBrush);
|
|
|
|
pen.setWidthF(score()->styleP(StyleIdx::SlurDottedWidth));
|
|
|
|
pen.setStyle(Qt::DashLine);
|
|
|
|
break;
|
2017-04-13 13:54:12 +02:00
|
|
|
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;
|
2016-11-15 16:33:27 +01:00
|
|
|
}
|
|
|
|
painter->setPen(pen);
|
|
|
|
painter->drawPath(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// edit
|
|
|
|
// return true if event is accepted
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2017-03-31 13:03:15 +02:00
|
|
|
bool TieSegment::edit(EditData& ed)
|
2016-11-15 16:33:27 +01:00
|
|
|
{
|
|
|
|
SlurTie* sl = tie();
|
|
|
|
|
2017-03-31 13:03:15 +02:00
|
|
|
if (ed.key == Qt::Key_X) {
|
2016-11-15 16:33:27 +01:00
|
|
|
sl->setSlurDirection(sl->up() ? Direction::DOWN : Direction::UP);
|
|
|
|
sl->layout();
|
|
|
|
return true;
|
|
|
|
}
|
2017-03-31 13:03:15 +02:00
|
|
|
if (ed.key == Qt::Key_Home) {
|
|
|
|
ups(ed.curGrip).off = QPointF();
|
2016-11-15 16:33:27 +01:00
|
|
|
sl->layout();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// changeAnchor
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2017-07-26 09:59:24 +02:00
|
|
|
void TieSegment::changeAnchor(EditData& ed, Element* element)
|
2016-11-15 16:33:27 +01:00
|
|
|
{
|
2017-07-26 09:59:24 +02:00
|
|
|
if (ed.curGrip == Grip::START) {
|
2016-11-15 16:33:27 +01:00
|
|
|
spanner()->setStartElement(element);
|
2017-07-26 09:59:24 +02:00
|
|
|
Note* note = toNote(element);
|
|
|
|
if (note->chord()->tick() <= tie()->endNote()->chord()->tick()) {
|
|
|
|
tie()->startNote()->setTieFor(0);
|
|
|
|
tie()->setStartNote(note);
|
|
|
|
note->setTieFor(tie());
|
2016-11-15 16:33:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
spanner()->setEndElement(element);
|
2017-07-26 09:59:24 +02:00
|
|
|
Note* note = toNote(element);
|
|
|
|
// do not allow backward ties
|
|
|
|
if (note->chord()->tick() >= tie()->startNote()->chord()->tick()) {
|
|
|
|
tie()->endNote()->setTieBack(0);
|
|
|
|
tie()->setEndNote(note);
|
|
|
|
note->setTieBack(tie());
|
2016-11-15 16:33:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int segments = spanner()->spannerSegments().size();
|
2017-07-26 09:59:24 +02:00
|
|
|
ups(ed.curGrip).off = QPointF();
|
2016-11-15 16:33:27 +01:00
|
|
|
spanner()->layout();
|
|
|
|
if (spanner()->spannerSegments().size() != segments) {
|
|
|
|
QList<SpannerSegment*>& ss = spanner()->spannerSegments();
|
|
|
|
|
2017-07-26 09:59:24 +02:00
|
|
|
TieSegment* newSegment = toTieSegment(ed.curGrip == Grip::END ? ss.back() : ss.front());
|
2016-11-15 16:33:27 +01:00
|
|
|
score()->endCmd();
|
|
|
|
score()->startCmd();
|
2017-07-26 09:59:24 +02:00
|
|
|
ed.view->startEdit(newSegment, ed.curGrip);
|
2016-11-15 16:33:27 +01:00
|
|
|
score()->setLayoutAll();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// editDrag
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2017-03-31 13:03:15 +02:00
|
|
|
void TieSegment::editDrag(EditData& ed)
|
2016-11-15 16:33:27 +01:00
|
|
|
{
|
|
|
|
Grip g = ed.curGrip;
|
2016-11-16 16:06:56 +01:00
|
|
|
ups(g).off += ed.delta;
|
2016-11-15 16:33:27 +01:00
|
|
|
|
|
|
|
if (g == Grip::START || g == Grip::END) {
|
|
|
|
computeBezier();
|
|
|
|
//
|
|
|
|
// move anchor for slurs/ties
|
|
|
|
//
|
|
|
|
if ((g == Grip::START && isSingleBeginType()) || (g == Grip::END && isSingleEndType())) {
|
|
|
|
Spanner* spanner = tie();
|
2017-12-20 16:49:30 +01:00
|
|
|
Note* note = toNote(ed.view->elementNear(ed.pos));
|
2016-11-15 16:33:27 +01:00
|
|
|
if (note && note->isNote()
|
|
|
|
&& ((g == Grip::END && note->tick() > tie()->tick()) || (g == Grip::START && note->tick() < tie()->tick2()))
|
|
|
|
) {
|
2016-11-17 09:46:58 +01:00
|
|
|
if (g == Grip::END) {
|
2016-11-15 16:33:27 +01:00
|
|
|
Tie* tie = toTie(spanner);
|
|
|
|
if (tie->startNote()->pitch() == note->pitch()
|
|
|
|
&& tie->startNote()->chord()->tick() < note->chord()->tick()) {
|
|
|
|
ed.view->setDropTarget(note);
|
|
|
|
if (note != tie->endNote()) {
|
2017-07-26 09:59:24 +02:00
|
|
|
changeAnchor(ed, note);
|
2016-11-15 16:33:27 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
ed.view->setDropTarget(0);
|
|
|
|
}
|
|
|
|
}
|
2016-11-16 16:06:56 +01:00
|
|
|
else if (g == Grip::BEZIER1 || g == Grip::BEZIER2)
|
2016-11-15 16:33:27 +01:00
|
|
|
computeBezier();
|
2016-11-16 16:06:56 +01:00
|
|
|
else if (g == Grip::SHOULDER) {
|
|
|
|
ups(g).off = QPointF();
|
2016-11-15 16:33:27 +01:00
|
|
|
computeBezier(ed.delta);
|
|
|
|
}
|
2016-11-16 16:06:56 +01:00
|
|
|
else if (g == Grip::DRAG) {
|
2016-11-15 16:33:27 +01:00
|
|
|
ups(Grip::DRAG).off = QPointF();
|
|
|
|
setUserOff(userOff() + ed.delta);
|
|
|
|
}
|
|
|
|
|
|
|
|
// if this SlurSegment was automatically adjusted to avoid collision
|
|
|
|
// lock this edit by resetting SlurSegment to default position
|
|
|
|
// and incorporating previous adjustment into user offset
|
|
|
|
QPointF offset = getAutoAdjust();
|
|
|
|
if (!offset.isNull()) {
|
|
|
|
setAutoAdjust(0.0, 0.0);
|
|
|
|
setUserOff(userOff() + offset);
|
|
|
|
}
|
|
|
|
undoChangeProperty(P_ID::AUTOPLACE, false);
|
|
|
|
}
|
|
|
|
|
2013-08-22 12:18:14 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// computeBezier
|
|
|
|
// compute help points of slur bezier segment
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-11-15 16:33:27 +01:00
|
|
|
void TieSegment::computeBezier(QPointF p6o)
|
2013-08-22 12:18:14 +02:00
|
|
|
{
|
|
|
|
qreal _spatium = spatium();
|
|
|
|
qreal shoulderW; // height as fraction of slur-length
|
|
|
|
qreal shoulderH;
|
|
|
|
|
|
|
|
//
|
|
|
|
// pp1 start of slur
|
|
|
|
// pp2 end of slur
|
|
|
|
// pp3 bezier 1
|
|
|
|
// pp4 bezier 2
|
|
|
|
// pp5 drag
|
|
|
|
// pp6 shoulder
|
|
|
|
//
|
2016-11-16 16:06:56 +01:00
|
|
|
QPointF pp1 = ups(Grip::START).p + ups(Grip::START).off;
|
|
|
|
QPointF pp2 = ups(Grip::END).p + ups(Grip::END).off;
|
2013-08-22 12:18:14 +02:00
|
|
|
|
|
|
|
QPointF p2 = pp2 - pp1; // normalize to zero
|
|
|
|
if (p2.x() == 0.0) {
|
|
|
|
qDebug("zero tie");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
qreal sinb = atan(p2.y() / p2.x());
|
|
|
|
QTransform t;
|
|
|
|
t.rotateRadians(-sinb);
|
|
|
|
p2 = t.map(p2);
|
|
|
|
p6o = t.map(p6o);
|
|
|
|
|
|
|
|
double smallH = 0.38;
|
|
|
|
qreal d = p2.x() / _spatium;
|
|
|
|
shoulderH = d * 0.4 * smallH;
|
2014-03-29 07:42:36 +01:00
|
|
|
shoulderH = qBound(0.4, shoulderH, 1.3);
|
2013-08-22 12:18:14 +02:00
|
|
|
shoulderH *= _spatium;
|
|
|
|
shoulderW = .6;
|
|
|
|
|
|
|
|
shoulderH -= p6o.y();
|
|
|
|
|
2016-11-15 16:33:27 +01:00
|
|
|
if (!tie()->up())
|
2013-08-22 12:18:14 +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-11-16 16:06:56 +01:00
|
|
|
qreal w = score()->styleP(StyleIdx::SlurMidWidth) - score()->styleP(StyleIdx::SlurEndWidth);
|
2013-08-22 12:18:14 +02:00
|
|
|
QPointF th(0.0, w); // thickness of slur
|
|
|
|
|
2016-11-16 16:06:56 +01:00
|
|
|
QPointF p3o = p6o + t.map(ups(Grip::BEZIER1).off);
|
|
|
|
QPointF p4o = p6o + t.map(ups(Grip::BEZIER2).off);
|
2013-08-22 12:18:14 +02:00
|
|
|
|
|
|
|
if(!p6o.isNull()) {
|
2016-11-16 16:06:56 +01:00
|
|
|
QPointF p6i = t.inverted().map(p6o);
|
2016-11-15 16:33:27 +01:00
|
|
|
ups(Grip::BEZIER1).off += p6i ;
|
|
|
|
ups(Grip::BEZIER2).off += p6i;
|
2013-08-22 12:18:14 +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;
|
|
|
|
//-----------------------------------
|
|
|
|
|
2016-11-15 16:33:27 +01:00
|
|
|
path = QPainterPath();
|
|
|
|
path.moveTo(QPointF());
|
|
|
|
path.cubicTo(p3 + p3o - th, p4 + p4o - th, p2);
|
|
|
|
if (tie()->lineType() == 0)
|
|
|
|
path.cubicTo(p4 +p4o + th, p3 + p3o + th, QPointF());
|
2013-08-22 12:18:14 +02:00
|
|
|
|
|
|
|
th = QPointF(0.0, 3.0 * w);
|
2016-11-15 16:33:27 +01:00
|
|
|
shapePath = QPainterPath();
|
|
|
|
shapePath.moveTo(QPointF());
|
|
|
|
shapePath.cubicTo(p3 + p3o - th, p4 + p4o - th, p2);
|
|
|
|
shapePath.cubicTo(p4 +p4o + th, p3 + p3o + th, QPointF());
|
2013-08-22 12:18:14 +02:00
|
|
|
|
|
|
|
// translate back
|
|
|
|
t.reset();
|
|
|
|
t.translate(pp1.x(), pp1.y());
|
|
|
|
t.rotateRadians(sinb);
|
2016-11-15 16:33:27 +01:00
|
|
|
path = t.map(path);
|
|
|
|
shapePath = t.map(shapePath);
|
|
|
|
ups(Grip::BEZIER1).p = t.map(p3);
|
|
|
|
ups(Grip::BEZIER2).p = t.map(p4);
|
2016-11-16 16:06:56 +01:00
|
|
|
ups(Grip::END).p = t.map(p2) - ups(Grip::END).off;
|
2016-11-15 16:33:27 +01:00
|
|
|
ups(Grip::DRAG).p = t.map(p5);
|
|
|
|
ups(Grip::SHOULDER).p = t.map(p6);
|
2014-11-06 19:18:20 +01:00
|
|
|
|
2017-07-10 15:22:51 +02:00
|
|
|
// QPointF staffOffset;
|
|
|
|
// if (system() && track() >= 0)
|
|
|
|
// staffOffset = QPointF(0.0, -system()->staff(staffIdx())->y());
|
2016-11-15 16:33:27 +01:00
|
|
|
|
2017-07-10 15:22:51 +02:00
|
|
|
// path.translate(staffOffset);
|
|
|
|
// shapePath.translate(staffOffset);
|
2016-12-30 10:58:53 +01:00
|
|
|
|
|
|
|
QPainterPath p;
|
|
|
|
p.moveTo(QPointF());
|
2017-07-10 15:22:51 +02:00
|
|
|
// p.cubicTo(p3 + p3o - th, p4 + p4o - th, p2);
|
|
|
|
p.cubicTo(p3 + p3o, p4 + p4o, p2);
|
2016-12-30 10:58:53 +01:00
|
|
|
_shape.clear();
|
|
|
|
QPointF start;
|
|
|
|
start = t.map(start);
|
2017-07-10 15:22:51 +02:00
|
|
|
|
|
|
|
qreal minH = qAbs(3.0 * w);
|
2016-12-30 10:58:53 +01:00
|
|
|
int nbShapes = 15;
|
|
|
|
for (int i = 1; i <= nbShapes; i++) {
|
|
|
|
QPointF point = t.map(p.pointAtPercent(i/float(nbShapes)));
|
2017-07-10 15:22:51 +02:00
|
|
|
QRectF re = QRectF(start, point).normalized();
|
|
|
|
if (re.height() < minH) {
|
|
|
|
qreal d = (minH - re.height()) * .5;
|
|
|
|
re.adjust(0.0, -d, 0.0, d);
|
|
|
|
}
|
|
|
|
// re.translate(staffOffset);
|
2016-12-30 10:58:53 +01:00
|
|
|
_shape.add(re);
|
|
|
|
start = point;
|
|
|
|
}
|
2016-11-15 16:33:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// layout
|
|
|
|
// p1, p2 are in System coordinates
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void TieSegment::layoutSegment(const QPointF& p1, const QPointF& p2)
|
|
|
|
{
|
|
|
|
if (autoplace()) {
|
|
|
|
for (UP& up : _ups)
|
|
|
|
up.off = QPointF();
|
|
|
|
}
|
|
|
|
ups(Grip::START).p = p1;
|
|
|
|
ups(Grip::END).p = p2;
|
|
|
|
computeBezier();
|
|
|
|
|
|
|
|
QRectF bbox = path.boundingRect();
|
|
|
|
|
|
|
|
// adjust position to avoid staff line if necessary
|
2017-07-10 15:22:51 +02:00
|
|
|
Staff* st = staff();
|
2016-11-15 16:33:27 +01:00
|
|
|
bool reverseAdjust = false;
|
2017-07-10 15:22:51 +02:00
|
|
|
|
2016-12-13 13:16:17 +01:00
|
|
|
if (slurTie()->isTie() && st && !st->isTabStaff(slurTie()->tick())) {
|
2016-11-15 16:33:27 +01:00
|
|
|
// multinote chords with ties need special handling
|
|
|
|
// otherwise, adjusted tie might crowd an unadjusted tie unnecessarily
|
2017-07-10 15:22:51 +02:00
|
|
|
Tie* t = toTie(slurTie());
|
|
|
|
Note* sn = t->startNote();
|
2016-11-15 16:33:27 +01:00
|
|
|
Chord* sc = sn ? sn->chord() : 0;
|
2017-07-10 15:22:51 +02:00
|
|
|
|
2016-11-15 16:33:27 +01:00
|
|
|
// normally, the adjustment moves ties according to their direction (eg, up if tie is up)
|
|
|
|
// but we will reverse this for notes within chords when appropriate
|
|
|
|
// for two-note chords, it looks better to have notes on spaces tied outside the lines
|
2017-07-10 15:22:51 +02:00
|
|
|
|
2016-11-15 16:33:27 +01:00
|
|
|
if (sc) {
|
|
|
|
int notes = sc->notes().size();
|
|
|
|
bool onLine = !(sn->line() & 1);
|
|
|
|
if ((onLine && notes > 1) || (!onLine && notes > 2))
|
|
|
|
reverseAdjust = true;
|
|
|
|
}
|
|
|
|
}
|
2017-07-10 15:22:51 +02:00
|
|
|
qreal sp = spatium();
|
2016-11-15 16:33:27 +01:00
|
|
|
qreal minDistance = 0.5;
|
2017-07-10 15:22:51 +02:00
|
|
|
autoAdjustOffset = QPointF();
|
2016-12-13 13:16:17 +01:00
|
|
|
if (bbox.height() < minDistance * 2 * sp && st && !st->isTabStaff(slurTie()->tick())) {
|
2016-11-15 16:33:27 +01:00
|
|
|
// slur/tie is fairly flat
|
2017-07-10 15:22:51 +02:00
|
|
|
bool up = slurTie()->up();
|
|
|
|
qreal ld = st->lineDistance(tick()) * sp;
|
|
|
|
qreal topY = bbox.top() / ld;
|
2016-11-15 16:33:27 +01:00
|
|
|
qreal bottomY = bbox.bottom() / ld;
|
2017-07-10 15:22:51 +02:00
|
|
|
int lineY = up ? qRound(topY) : qRound(bottomY);
|
2016-12-13 13:16:17 +01:00
|
|
|
if (lineY >= 0 && lineY < st->lines(tick()) * st->lineDistance(tick())) {
|
2016-11-15 16:33:27 +01:00
|
|
|
// on staff
|
|
|
|
if (qAbs(topY - lineY) < minDistance && qAbs(bottomY - lineY) < minDistance) {
|
|
|
|
// too close to line
|
|
|
|
if (!isNudged() && !isEdited()) {
|
|
|
|
// user has not nudged or edited
|
|
|
|
qreal offY;
|
|
|
|
if (up != reverseAdjust) // exclusive or
|
|
|
|
offY = (lineY - minDistance) - topY;
|
|
|
|
else
|
|
|
|
offY = (lineY + minDistance) - bottomY;
|
|
|
|
setAutoAdjust(0.0, offY * sp);
|
|
|
|
bbox = path.boundingRect();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-10 15:22:51 +02:00
|
|
|
|
|
|
|
setbbox(bbox);
|
2016-11-15 16:33:27 +01:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setAutoAdjust
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void TieSegment::setAutoAdjust(const QPointF& offset)
|
|
|
|
{
|
|
|
|
QPointF diff = offset - autoAdjustOffset;
|
|
|
|
if (!diff.isNull()) {
|
|
|
|
path.translate(diff);
|
|
|
|
shapePath.translate(diff);
|
2017-07-10 15:22:51 +02:00
|
|
|
_shape.translate(diff);
|
2016-11-15 16:33:27 +01:00
|
|
|
for (int i = 0; i < int(Grip::GRIPS); ++i)
|
|
|
|
_ups[i].p += diff;
|
|
|
|
autoAdjustOffset = offset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// isEdited
|
|
|
|
//---------------------------------------------------------
|
2014-11-06 19:18:20 +01:00
|
|
|
|
2016-11-15 16:33:27 +01:00
|
|
|
bool TieSegment::isEdited() const
|
|
|
|
{
|
|
|
|
for (int i = 0; i < int(Grip::GRIPS); ++i) {
|
|
|
|
if (!_ups[i].off.isNull())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2013-08-22 12:18:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// slurPos
|
|
|
|
// Calculate position of start- and endpoint of slur
|
|
|
|
// relative to System() position.
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tie::slurPos(SlurPos* sp)
|
|
|
|
{
|
2017-07-10 15:22:51 +02:00
|
|
|
bool useTablature = staff() && staff()->isTabStaff(tick());
|
|
|
|
StaffType* stt = useTablature ? staff()->staffType(tick()) : 0;
|
2015-03-05 00:24:00 +01:00
|
|
|
qreal _spatium = spatium();
|
|
|
|
qreal hw = startNote()->tabHeadWidth(stt); // if stt == 0, defaults to headWidth()
|
|
|
|
qreal __up = _up ? -1.0 : 1.0;
|
|
|
|
// y offset for ties inside chord margins (typically multi-note chords): lined up with note top or bottom margin
|
|
|
|
// or outside (typically single-note chord): overlaps note and is above/below it
|
|
|
|
// Outside: Tab: uses font size and may be asymmetric placed above/below line (frets ON or ABOVE line)
|
2016-05-03 01:21:42 +02:00
|
|
|
// Std: assumes notehead is 1 sp high, 1/2 sp above and 1/2 below line; add 1/4 sp to it
|
2015-03-05 00:24:00 +01:00
|
|
|
// Inside: Tab: 1/2 of Outside offset
|
|
|
|
// Std: use a fixed pecentage of note width
|
|
|
|
qreal yOffOutside = useTablature
|
|
|
|
? (_up ? stt->fretBoxY() : stt->fretBoxY() + stt->fretBoxH()) * magS()
|
|
|
|
: 0.75 * _spatium * __up;
|
|
|
|
qreal yOffInside = useTablature ? yOffOutside * 0.5 : hw * .3 * __up;
|
2013-08-22 12:18:14 +02:00
|
|
|
|
|
|
|
Chord* sc = startNote()->chord();
|
|
|
|
sp->system1 = sc->measure()->system();
|
2013-10-02 10:26:09 +02:00
|
|
|
if (!sp->system1) {
|
|
|
|
Measure* m = sc->measure();
|
2014-03-04 16:48:06 +01:00
|
|
|
qDebug("No system: measure is %d has %d count %d", m->isMMRest(), m->hasMMRest(), m->mmRestCount());
|
2013-10-02 10:26:09 +02:00
|
|
|
}
|
2013-08-22 12:18:14 +02:00
|
|
|
|
|
|
|
qreal xo;
|
|
|
|
qreal yo;
|
2014-02-23 17:16:39 +01:00
|
|
|
bool shortStart = false;
|
2013-08-22 12:18:14 +02:00
|
|
|
|
2014-02-24 18:01:55 +01:00
|
|
|
// determine attachment points
|
|
|
|
// similar code is used in Chord::layoutPitched()
|
|
|
|
// to allocate extra space to enforce minTieLength
|
|
|
|
// so keep these in sync
|
|
|
|
|
2017-07-10 15:22:51 +02:00
|
|
|
sp->p1 = sc->pos() + sc->segment()->pos() + sc->measure()->pos();
|
|
|
|
|
2013-08-22 12:18:14 +02:00
|
|
|
//------p1
|
|
|
|
if ((sc->notes().size() > 1) || (sc->stem() && (sc->up() == _up))) {
|
|
|
|
xo = startNote()->x() + hw * 1.12;
|
2015-03-05 00:24:00 +01:00
|
|
|
yo = startNote()->pos().y() + yOffInside;
|
2014-02-23 17:16:39 +01:00
|
|
|
shortStart = true;
|
2013-08-22 12:18:14 +02:00
|
|
|
}
|
|
|
|
else {
|
2014-02-22 21:29:29 +01:00
|
|
|
xo = startNote()->x() + hw * 0.65;
|
2015-03-05 00:24:00 +01:00
|
|
|
yo = startNote()->pos().y() + yOffOutside;
|
2013-08-22 12:18:14 +02:00
|
|
|
}
|
2017-07-10 15:22:51 +02:00
|
|
|
sp->p1 += QPointF(xo, yo);
|
2013-08-22 12:18:14 +02:00
|
|
|
|
|
|
|
//------p2
|
|
|
|
if (endNote() == 0) {
|
|
|
|
sp->p2 = sp->p1 + QPointF(_spatium * 3, 0.0);
|
|
|
|
sp->system2 = sp->system1;
|
|
|
|
return;
|
|
|
|
}
|
2017-09-04 12:14:31 +02:00
|
|
|
Chord* ec = endNote()->chord();
|
|
|
|
sp->p2 = ec->pos() + ec->segment()->pos() + ec->measure()->pos();
|
2013-08-22 12:18:14 +02:00
|
|
|
sp->system2 = ec->measure()->system();
|
2017-07-19 17:53:45 +02:00
|
|
|
|
2015-02-25 18:55:17 +01:00
|
|
|
hw = endNote()->tabHeadWidth(stt);
|
2013-08-22 12:18:14 +02:00
|
|
|
if ((ec->notes().size() > 1) || (ec->stem() && !ec->up() && !_up))
|
|
|
|
xo = endNote()->x() - hw * 0.12;
|
2014-02-23 17:16:39 +01:00
|
|
|
else if (shortStart)
|
|
|
|
xo = endNote()->x() + hw * 0.15;
|
2013-08-22 12:18:14 +02:00
|
|
|
else
|
2014-02-22 21:29:29 +01:00
|
|
|
xo = endNote()->x() + hw * 0.35;
|
2017-07-10 15:22:51 +02:00
|
|
|
sp->p2 += QPointF(xo, yo);
|
2013-08-22 12:18:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// Tie
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Tie::Tie(Score* s)
|
|
|
|
: SlurTie(s)
|
|
|
|
{
|
2014-05-26 20:48:27 +02:00
|
|
|
setAnchor(Anchor::NOTE);
|
2013-08-22 12:18:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// write
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-11-19 11:51:21 +01:00
|
|
|
void Tie::write(XmlWriter& xml) const
|
2013-08-22 12:18:14 +02:00
|
|
|
{
|
2014-07-21 13:24:21 +02:00
|
|
|
xml.stag(QString("Tie id=\"%1\"").arg(xml.spannerId(this)));
|
2013-08-22 12:18:14 +02:00
|
|
|
SlurTie::writeProperties(xml);
|
|
|
|
xml.etag();
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// read
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tie::read(XmlReader& e)
|
|
|
|
{
|
2014-07-21 13:24:21 +02:00
|
|
|
e.addSpanner(e.intAttribute("id"), this);
|
2013-08-22 12:18:14 +02:00
|
|
|
while (e.readNextStartElement()) {
|
2016-11-15 16:33:27 +01:00
|
|
|
if (SlurTie::readProperties(e))
|
2013-08-22 12:18:14 +02:00
|
|
|
;
|
|
|
|
else
|
|
|
|
e.unknown();
|
|
|
|
}
|
2014-05-06 04:28:15 +02:00
|
|
|
if (score()->mscVersion() <= 114 && spannerSegments().size() == 1) {
|
|
|
|
// ignore manual adjustments to single-segment ties in older scores
|
2016-11-15 16:33:27 +01:00
|
|
|
TieSegment* ss = frontSegment();
|
2014-05-06 04:28:15 +02:00
|
|
|
QPointF zeroP;
|
2015-01-19 12:37:17 +01:00
|
|
|
ss->ups(Grip::START).off = zeroP;
|
|
|
|
ss->ups(Grip::BEZIER1).off = zeroP;
|
|
|
|
ss->ups(Grip::BEZIER2).off = zeroP;
|
|
|
|
ss->ups(Grip::END).off = zeroP;
|
2014-05-06 04:28:15 +02:00
|
|
|
ss->setUserOff(zeroP);
|
|
|
|
ss->setUserOff2(zeroP);
|
|
|
|
}
|
2013-08-22 12:18:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2014-02-22 21:29:29 +01:00
|
|
|
// calculateDirection
|
2013-08-22 12:18:14 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-02-22 21:29:29 +01:00
|
|
|
void Tie::calculateDirection()
|
2013-08-22 12:18:14 +02:00
|
|
|
{
|
|
|
|
Chord* c1 = startNote()->chord();
|
|
|
|
Chord* c2 = endNote()->chord();
|
|
|
|
Measure* m1 = c1->measure();
|
|
|
|
Measure* m2 = c2->measure();
|
|
|
|
|
2016-03-02 13:20:19 +01:00
|
|
|
if (_slurDirection == Direction::AUTO) {
|
2016-02-06 22:03:43 +01:00
|
|
|
std::vector<Note*> notes = c1->notes();
|
2013-08-22 12:18:14 +02:00
|
|
|
int n = notes.size();
|
2016-12-12 12:02:18 +01:00
|
|
|
if (m1->hasVoices(c1->staffIdx()) || m2->hasVoices(c2->staffIdx())) {
|
2013-08-22 12:18:14 +02:00
|
|
|
// in polyphonic passage, ties go on the stem side
|
|
|
|
_up = c1->up();
|
|
|
|
}
|
|
|
|
else if (n == 1) {
|
|
|
|
//
|
|
|
|
// single note
|
|
|
|
//
|
|
|
|
if (c1->up() != c2->up()) {
|
|
|
|
// if stem direction is mixed, always up
|
|
|
|
_up = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
_up = !c1->up();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
//
|
|
|
|
// chords
|
|
|
|
//
|
|
|
|
QList<int> ties;
|
|
|
|
int idx = 0;
|
2015-03-05 01:25:52 +01:00
|
|
|
int noteIdx = -1;
|
2013-08-22 12:18:14 +02:00
|
|
|
for (int i = 0; i < n; ++i) {
|
|
|
|
if (notes[i]->tieFor()) {
|
|
|
|
ties.append(notes[i]->line());
|
2015-03-05 01:25:52 +01:00
|
|
|
if (notes[i] == startNote()) {
|
2013-08-22 12:18:14 +02:00
|
|
|
idx = ties.size() - 1;
|
2015-03-05 01:25:52 +01:00
|
|
|
noteIdx = i;
|
|
|
|
}
|
2013-08-22 12:18:14 +02:00
|
|
|
}
|
|
|
|
}
|
2015-03-05 01:25:52 +01:00
|
|
|
if (idx == 0) {
|
|
|
|
if (ties.size() == 1) // if just one tie
|
|
|
|
_up = noteIdx != 0; // it is up if not the bottom note of the chord
|
|
|
|
else // if several ties and this is the bottom one (idx == 0)
|
|
|
|
_up = false; // it is down
|
|
|
|
}
|
2013-08-22 12:18:14 +02:00
|
|
|
else if (idx == ties.size() - 1)
|
|
|
|
_up = true;
|
|
|
|
else {
|
|
|
|
if (ties[idx] <= 4)
|
|
|
|
_up = ((ties[idx-1] - ties[idx]) <= 1) || ((ties[idx] - ties[idx+1]) > 1);
|
|
|
|
else
|
|
|
|
_up = ((ties[idx-1] - ties[idx]) <= 1) && ((ties[idx] - ties[idx+1]) > 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
2016-03-02 13:20:19 +01:00
|
|
|
_up = _slurDirection == Direction::UP ? true : false;
|
2014-02-22 21:29:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2017-07-19 17:53:45 +02:00
|
|
|
// layoutFor
|
|
|
|
// layout the first SpannerSegment of a slur
|
2014-02-22 21:29:29 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2017-07-19 17:53:45 +02:00
|
|
|
void Tie::layoutFor(System* system)
|
2014-02-22 21:29:29 +01:00
|
|
|
{
|
|
|
|
//
|
|
|
|
// show short bow
|
|
|
|
//
|
|
|
|
if (startNote() == 0 || endNote() == 0) {
|
|
|
|
if (startNote() == 0) {
|
|
|
|
qDebug("Tie::layout(): no start note");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Chord* c1 = startNote()->chord();
|
2016-03-02 13:20:19 +01:00
|
|
|
if (_slurDirection == Direction::AUTO) {
|
2016-12-12 12:02:18 +01:00
|
|
|
if (c1->measure()->hasVoices(c1->staffIdx())) {
|
2014-02-22 21:29:29 +01:00
|
|
|
// in polyphonic passage, ties go on the stem side
|
|
|
|
_up = c1->up();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
_up = !c1->up();
|
|
|
|
}
|
|
|
|
else
|
2016-03-02 13:20:19 +01:00
|
|
|
_up = _slurDirection == Direction::UP ? true : false;
|
2014-02-22 21:29:29 +01:00
|
|
|
fixupSegments(1);
|
2016-11-15 16:33:27 +01:00
|
|
|
TieSegment* segment = segmentAt(0);
|
2014-05-26 20:38:22 +02:00
|
|
|
segment->setSpannerSegmentType(SpannerSegmentType::SINGLE);
|
2014-02-22 21:29:29 +01:00
|
|
|
segment->setSystem(startNote()->chord()->segment()->measure()->system());
|
|
|
|
SlurPos sPos;
|
|
|
|
slurPos(&sPos);
|
2015-05-05 18:28:39 +02:00
|
|
|
segment->layoutSegment(sPos.p1, sPos.p2);
|
2014-02-22 21:29:29 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
calculateDirection();
|
2013-08-22 12:18:14 +02:00
|
|
|
|
|
|
|
SlurPos sPos;
|
|
|
|
slurPos(&sPos);
|
|
|
|
|
|
|
|
setPos(0, 0);
|
|
|
|
|
2017-07-19 17:53:45 +02:00
|
|
|
int n;
|
|
|
|
if (sPos.system1 != sPos.system2) {
|
|
|
|
n = 2;
|
|
|
|
sPos.p2 = QPointF(system->width(), sPos.p1.y());
|
2013-08-22 12:18:14 +02:00
|
|
|
}
|
2017-07-19 17:53:45 +02:00
|
|
|
else
|
|
|
|
n = 1;
|
2013-08-22 12:18:14 +02:00
|
|
|
|
2017-07-19 17:53:45 +02:00
|
|
|
fixupSegments(n);
|
|
|
|
TieSegment* segment = segmentAt(0);
|
2017-10-10 01:03:44 +02:00
|
|
|
segment->setSystem(system); // Needed to populate System.spannerSegments
|
2017-07-19 17:53:45 +02:00
|
|
|
segment->layoutSegment(sPos.p1, sPos.p2);
|
|
|
|
segment->setSpannerSegmentType(sPos.system1 != sPos.system2 ? SpannerSegmentType::BEGIN : SpannerSegmentType::SINGLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// layoutBack
|
|
|
|
// layout the second SpannerSegment of a splitted slur
|
|
|
|
//---------------------------------------------------------
|
2013-08-22 12:18:14 +02:00
|
|
|
|
2017-07-19 17:53:45 +02:00
|
|
|
void Tie::layoutBack(System* system)
|
|
|
|
{
|
|
|
|
SlurPos sPos;
|
|
|
|
slurPos(&sPos);
|
|
|
|
|
|
|
|
fixupSegments(2);
|
|
|
|
TieSegment* segment = segmentAt(1);
|
2017-10-10 01:03:44 +02:00
|
|
|
segment->setSystem(system);
|
2017-07-19 17:53:45 +02:00
|
|
|
|
|
|
|
qreal x;
|
|
|
|
Segment* seg = endNote()->chord()->segment()->prev();
|
|
|
|
if (seg) {
|
|
|
|
// find maximum width
|
|
|
|
qreal width = 0.0;
|
|
|
|
int n = score()->nstaves();
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
|
|
if (!system->staff(i)->show())
|
|
|
|
continue;
|
|
|
|
Element* e = seg->element(i * VOICES);
|
|
|
|
if (e)
|
|
|
|
width = qMax(width, e->width());
|
2013-08-22 12:18:14 +02:00
|
|
|
}
|
2017-07-19 17:53:45 +02:00
|
|
|
x = seg->measure()->pos().x() + seg->pos().x() + width;
|
2013-08-22 12:18:14 +02:00
|
|
|
}
|
2017-07-19 17:53:45 +02:00
|
|
|
else
|
|
|
|
x = 0.0;
|
|
|
|
|
|
|
|
segment->layoutSegment(QPointF(x, sPos.p2.y()), sPos.p2);
|
|
|
|
segment->setSpannerSegmentType(SpannerSegmentType::END);
|
2013-08-22 12:18:14 +02:00
|
|
|
}
|
|
|
|
|
2017-07-26 09:59:24 +02:00
|
|
|
#if 0
|
2013-08-22 12:18:14 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// startEdit
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2017-03-31 13:03:15 +02:00
|
|
|
void Tie::startEdit(EditData& ed)
|
2013-08-22 12:18:14 +02:00
|
|
|
{
|
2017-07-26 09:59:24 +02:00
|
|
|
printf("tie start edit %p %p\n", editStartNote, editEndNote);
|
2013-08-22 12:18:14 +02:00
|
|
|
editStartNote = startNote();
|
2017-07-26 09:59:24 +02:00
|
|
|
editEndNote = endNote();
|
2017-03-31 13:03:15 +02:00
|
|
|
SlurTie::startEdit(ed);
|
2013-08-22 12:18:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// endEdit
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2017-03-31 13:03:15 +02:00
|
|
|
void Tie::endEdit(EditData& ed)
|
2013-08-22 12:18:14 +02:00
|
|
|
{
|
2017-07-26 09:59:24 +02:00
|
|
|
printf("tie::endEdit\n");
|
|
|
|
// if (editStartNote != startNote() || editEndNote != endNote()) {
|
|
|
|
// score()->undoStack()->push1(new ChangeSpannerElements(this, editStartNote, editEndNote));
|
|
|
|
// }
|
2017-03-31 13:03:15 +02:00
|
|
|
SlurTie::endEdit(ed);
|
2013-08-22 12:18:14 +02:00
|
|
|
}
|
2017-07-26 09:59:24 +02:00
|
|
|
#endif
|
2013-08-22 12:18:14 +02:00
|
|
|
|
2014-08-07 10:18:50 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setStartNote
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tie::setStartNote(Note* note)
|
|
|
|
{
|
|
|
|
setStartElement(note);
|
|
|
|
setParent(note);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// startNote
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Note* Tie::startNote() const
|
|
|
|
{
|
2017-01-18 14:16:33 +01:00
|
|
|
Q_ASSERT(!startElement() || startElement()->type() == ElementType::NOTE);
|
2017-12-20 16:49:30 +01:00
|
|
|
return toNote(startElement());
|
2014-08-07 10:18:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// endNote
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Note* Tie::endNote() const
|
|
|
|
{
|
2017-12-20 16:49:30 +01:00
|
|
|
return toNote(endElement());
|
2014-08-07 10:18:50 +02:00
|
|
|
}
|
|
|
|
|
2016-11-15 16:33:27 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// readProperties
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool Tie::readProperties(XmlReader& e)
|
|
|
|
{
|
|
|
|
const QStringRef& tag(e.name());
|
|
|
|
|
|
|
|
if (tag == "SlurSegment") {
|
|
|
|
int idx = e.intAttribute("no", 0);
|
|
|
|
int n = spannerSegments().size();
|
|
|
|
for (int i = n; i < idx; ++i)
|
|
|
|
add(new TieSegment(score()));
|
|
|
|
TieSegment* segment = new TieSegment(score());
|
|
|
|
segment->setAutoplace(false);
|
|
|
|
segment->read(e);
|
|
|
|
add(segment);
|
|
|
|
}
|
|
|
|
else if (!SlurTie::readProperties(e))
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-08-22 12:18:14 +02:00
|
|
|
}
|
|
|
|
|