MuseScore/libmscore/tie.cpp
2014-05-26 15:31:36 +02:00

450 lines
14 KiB
C++

//=============================================================================
// 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 "note.h"
#include "chord.h"
#include "xml.h"
#include "slur.h"
#include "measure.h"
#include "utils.h"
#include "score.h"
#include "system.h"
#include "segment.h"
#include "staff.h"
#include "navigate.h"
#include "articulation.h"
#include "undo.h"
#include "stem.h"
#include "beam.h"
#include "mscore.h"
#include "page.h"
#include "tie.h"
namespace Ms {
Note* Tie::editStartNote;
Note* Tie::editEndNote;
//---------------------------------------------------------
// computeBezier
// compute help points of slur bezier segment
//---------------------------------------------------------
void Tie::computeBezier(SlurSegment* ss, QPointF p6o)
{
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
//
QPointF pp1 = ss->ups[int(GripSlurSegment::START)].p + ss->ups[int(GripSlurSegment::START)].off * _spatium;
QPointF pp2 = ss->ups[int(GripSlurSegment::END)].p + ss->ups[int(GripSlurSegment::END)].off * _spatium;
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;
shoulderH = qBound(0.4, shoulderH, 1.3);
shoulderH *= _spatium;
shoulderW = .6;
shoulderH -= p6o.y();
if (!up())
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);
qreal w = (score()->styleS(StyleIdx::SlurMidWidth).val() - score()->styleS(StyleIdx::SlurEndWidth).val()) * _spatium;
QPointF th(0.0, w); // thickness of slur
QPointF p3o = p6o + t.map(ss->ups[int(GripSlurSegment::BEZIER1)].off * _spatium);
QPointF p4o = p6o + t.map(ss->ups[int(GripSlurSegment::BEZIER2)].off * _spatium);
if(!p6o.isNull()) {
QPointF p6i = t.inverted().map(p6o) / _spatium;
ss->ups[int(GripSlurSegment::BEZIER1)].off += p6i ;
ss->ups[int(GripSlurSegment::BEZIER2)].off += p6i;
}
//-----------------------------------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;
//-----------------------------------
ss->path = QPainterPath();
ss->path.moveTo(QPointF());
ss->path.cubicTo(p3 + p3o - th, p4 + p4o - th, p2);
if (lineType() == 0)
ss->path.cubicTo(p4 +p4o + th, p3 + p3o + th, QPointF());
th = QPointF(0.0, 3.0 * w);
ss->shapePath = QPainterPath();
ss->shapePath.moveTo(QPointF());
ss->shapePath.cubicTo(p3 + p3o - th, p4 + p4o - th, p2);
ss->shapePath.cubicTo(p4 +p4o + th, p3 + p3o + th, QPointF());
// translate back
t.reset();
t.translate(pp1.x(), pp1.y());
t.rotateRadians(sinb);
ss->path = t.map(ss->path);
ss->shapePath = t.map(ss->shapePath);
ss->ups[int(GripSlurSegment::BEZIER1)].p = t.map(p3);
ss->ups[int(GripSlurSegment::BEZIER2)].p = t.map(p4);
ss->ups[int(GripSlurSegment::END)].p = t.map(p2) - ss->ups[int(GripSlurSegment::END)].off * _spatium;
ss->ups[int(GripSlurSegment::DRAG)].p = t.map(p5);
ss->ups[int(GripSlurSegment::SHOULDER)].p = t.map(p6);
}
//---------------------------------------------------------
// slurPos
// Calculate position of start- and endpoint of slur
// relative to System() position.
//---------------------------------------------------------
void Tie::slurPos(SlurPos* sp)
{
qreal hw = startNote()->headWidth();
qreal __up = _up ? -1.0 : 1.0;
qreal _spatium = spatium();
Chord* sc = startNote()->chord();
Q_ASSERT(sc);
sp->system1 = sc->measure()->system();
if (!sp->system1) {
Measure* m = sc->measure();
qDebug("No system: measure is %d has %d count %d", m->isMMRest(), m->hasMMRest(), m->mmRestCount());
}
Q_ASSERT(sp->system1);
qreal xo;
qreal yo;
bool shortStart = false;
// determine attachment points
// similar code is used in Chord::layoutPitched()
// to allocate extra space to enforce minTieLength
// so keep these in sync
//------p1
if ((sc->notes().size() > 1) || (sc->stem() && (sc->up() == _up))) {
xo = startNote()->x() + hw * 1.12;
yo = startNote()->pos().y() + hw * .3 * __up;
shortStart = true;
}
else {
xo = startNote()->x() + hw * 0.65;
yo = startNote()->pos().y() + _spatium * .75 * __up;
}
sp->p1 = sc->pagePos() - sp->system1->pagePos() + QPointF(xo, yo);
//------p2
if (endNote() == 0) {
sp->p2 = sp->p1 + QPointF(_spatium * 3, 0.0);
sp->system2 = sp->system1;
return;
}
Chord* ec = endNote()->chord();
sp->system2 = ec->measure()->system();
if ((ec->notes().size() > 1) || (ec->stem() && !ec->up() && !_up))
xo = endNote()->x() - hw * 0.12;
else if (shortStart)
xo = endNote()->x() + hw * 0.15;
else
xo = endNote()->x() + hw * 0.35;
sp->p2 = ec->pagePos() - sp->system2->pagePos() + QPointF(xo, yo);
}
//---------------------------------------------------------
// Tie
//---------------------------------------------------------
Tie::Tie(Score* s)
: SlurTie(s)
{
setAnchor(ANCHOR_NOTE);
}
//---------------------------------------------------------
// setStartNote
//---------------------------------------------------------
void Tie::setStartNote(Note* note)
{
setStartElement(note);
setParent(note);
}
//---------------------------------------------------------
// write
//---------------------------------------------------------
void Tie::write(Xml& xml) const
{
xml.stag(QString("Tie id=\"%1\"").arg(id()));
SlurTie::writeProperties(xml);
xml.etag();
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
void Tie::read(XmlReader& e)
{
setId(e.intAttribute("id"));
while (e.readNextStartElement()) {
if (SlurTie::readProperties(e) || Element::readProperties(e))
;
else
e.unknown();
}
if (score()->mscVersion() <= 114 && spannerSegments().size() == 1) {
// ignore manual adjustments to single-segment ties in older scores
SlurSegment* ss = frontSegment();
QPointF zeroP;
ss->ups[int(GripSlurSegment::START)].off = zeroP;
ss->ups[int(GripSlurSegment::BEZIER1)].off = zeroP;
ss->ups[int(GripSlurSegment::BEZIER2)].off = zeroP;
ss->ups[int(GripSlurSegment::END)].off = zeroP;
ss->setUserOff(zeroP);
ss->setUserOff2(zeroP);
}
}
//---------------------------------------------------------
// calculateDirection
//---------------------------------------------------------
void Tie::calculateDirection()
{
Chord* c1 = startNote()->chord();
Chord* c2 = endNote()->chord();
Measure* m1 = c1->measure();
Measure* m2 = c2->measure();
if (_slurDirection == Direction::AUTO) {
QList<Note*> notes = c1->notes();
int n = notes.size();
if (m1->mstaff(c1->staffIdx())->hasVoices || m2->mstaff(c2->staffIdx())->hasVoices) {
// 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;
for (int i = 0; i < n; ++i) {
if (notes[i]->tieFor()) {
ties.append(notes[i]->line());
if (notes[i] == startNote())
idx = ties.size() - 1;
}
}
if (idx == 0)
_up = false;
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
_up = _slurDirection == Direction::UP ? true : false;
}
//---------------------------------------------------------
// layout
//---------------------------------------------------------
void Tie::layout()
{
qreal _spatium = spatium();
//
// show short bow
//
if (startNote() == 0 || endNote() == 0) {
if (startNote() == 0) {
qDebug("Tie::layout(): no start note");
return;
}
Chord* c1 = startNote()->chord();
if (_slurDirection == Direction::AUTO) {
if (c1->measure()->mstaff(c1->staffIdx())->hasVoices) {
// in polyphonic passage, ties go on the stem side
_up = c1->up();
}
else
_up = !c1->up();
}
else
_up = _slurDirection == Direction::UP ? true : false;
fixupSegments(1);
SlurSegment* segment = segmentAt(0);
segment->setSpannerSegmentType(SEGMENT_SINGLE);
segment->setSystem(startNote()->chord()->segment()->measure()->system());
SlurPos sPos;
slurPos(&sPos);
segment->layout(sPos.p1, sPos.p2);
return;
}
calculateDirection();
qreal w = startNote()->headWidth();
qreal xo1 = w * 1.12;
qreal h = w * 0.3;
qreal yo = _up ? -h : h;
QPointF off1(xo1, yo);
QPointF off2(0.0, yo);
QPointF ppos(pagePos());
// TODO: cleanup
SlurPos sPos;
slurPos(&sPos);
// p1, p2, s1, s2
QList<System*>* systems = score()->systems();
setPos(0, 0);
//---------------------------------------------------------
// count number of segments, if no change, all
// user offsets (drags) are retained
//---------------------------------------------------------
int sysIdx1 = systems->indexOf(sPos.system1);
if (sysIdx1 == -1) {
qDebug("system not found");
foreach(System* s, *systems)
qDebug(" search %p in %p", sPos.system1, s);
return;
}
int sysIdx2 = systems->indexOf(sPos.system2);
if (sysIdx2 < 0)
sysIdx2 = sysIdx1;
unsigned nsegs = sysIdx2 - sysIdx1 + 1;
fixupSegments(nsegs);
int i = 0;
for (uint ii = 0; ii < nsegs; ++ii) {
System* system = (*systems)[sysIdx1++];
if (system->isVbox())
continue;
SlurSegment* segment = segmentAt(i);
segment->setSystem(system);
// case 1: one segment
if (sPos.system1 == sPos.system2) {
segment->layout(sPos.p1, sPos.p2);
segment->setSpannerSegmentType(SEGMENT_SINGLE);
}
// case 2: start segment
else if (i == 0) {
qreal x = system->bbox().width();
segment->layout(sPos.p1, QPointF(x, sPos.p1.y()));
segment->setSpannerSegmentType(SEGMENT_BEGIN);
}
// case 4: end segment
else {
qreal x = firstNoteRestSegmentX(system) - 2 * _spatium;
segment->layout(QPointF(x, sPos.p2.y()), sPos.p2);
segment->setSpannerSegmentType(SEGMENT_END);
}
++i;
}
}
//---------------------------------------------------------
// startEdit
//---------------------------------------------------------
void Tie::startEdit(MuseScoreView* v, const QPointF& p)
{
editStartNote = startNote();
editEndNote = endNote();
SlurTie::startEdit(v, p);
}
//---------------------------------------------------------
// endEdit
//---------------------------------------------------------
void Tie::endEdit()
{
if (editStartNote != startNote() || editEndNote != endNote()) {
score()->undo()->push1(new ChangeSpannerElements(this, editStartNote, editEndNote));
}
SlurTie::endEdit();
score()->setLayoutAll(true);
}
}