MuseScore/libmscore/slurtie.cpp

592 lines
20 KiB
C++

//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2016 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"
#include "slurtie.h"
#include "tie.h"
#include "chord.h"
namespace Ms {
//---------------------------------------------------------
// SlurTieSegment
//---------------------------------------------------------
SlurTieSegment::SlurTieSegment(Score* score)
: SpannerSegment(score)
{
setFlag(ElementFlag::ON_STAFF, true);
}
SlurTieSegment::SlurTieSegment(const SlurTieSegment& b)
: SpannerSegment(b)
{
for (int i = 0; i < int(Grip::GRIPS); ++i) {
_ups[i] = b._ups[i];
_ups[i].p = QPointF();
}
path = b.path;
}
//---------------------------------------------------------
// gripAnchor
//---------------------------------------------------------
QPointF SlurTieSegment::gripAnchor(Grip grip) const
{
if (!system() || (grip != Grip::START && grip != Grip::END))
return QPointF();
QPointF sp(system()->pagePos());
QPointF pp(pagePos());
QPointF p1(ups(Grip::START).p + pp);
QPointF p2(ups(Grip::END).p + pp);
switch (spannerSegmentType()) {
case SpannerSegmentType::SINGLE:
return grip == Grip::START ? p1 : p2;
case SpannerSegmentType::BEGIN:
return grip == Grip::START ? p1 : system()->abbox().topRight();
case SpannerSegmentType::MIDDLE:
return grip == Grip::START ? sp : system()->abbox().topRight();
case SpannerSegmentType::END:
return grip == Grip::START ? sp : p2;
}
return QPointF();
}
//---------------------------------------------------------
// move
//---------------------------------------------------------
void SlurTieSegment::move(const QPointF& s)
{
Element::move(s);
for (int k = 0; k < int(Grip::GRIPS); ++k)
_ups[k].p += s;
}
//---------------------------------------------------------
// spatiumChanged
//---------------------------------------------------------
void SlurTieSegment::spatiumChanged(qreal oldValue, qreal newValue)
{
qreal diff = newValue / oldValue;
for (UP& u : _ups)
u.off *= diff;
}
//---------------------------------------------------------
// startEdit
//---------------------------------------------------------
void SlurTieSegment::startEdit(EditData& ed)
{
Element::startEdit(ed);
ed.grips = int(Grip::GRIPS);
ed.curGrip = Grip::END;
}
//---------------------------------------------------------
// endEdit
//---------------------------------------------------------
void SlurTieSegment::endEdit(EditData&)
{
}
//---------------------------------------------------------
// startEditDrag
//---------------------------------------------------------
void SlurTieSegment::startEditDrag(EditData& ed)
{
ElementEditData* eed = ed.getData(this);
for (auto i : { Pid::SLUR_UOFF1, Pid::SLUR_UOFF2, Pid::SLUR_UOFF3, Pid::SLUR_UOFF4, Pid::OFFSET })
eed->pushProperty(i);
}
//---------------------------------------------------------
// endEditDrag
//---------------------------------------------------------
void SlurTieSegment::endEditDrag(EditData& ed)
{
Element::endEditDrag(ed);
triggerLayout();
}
//---------------------------------------------------------
// editDrag
//---------------------------------------------------------
void SlurTieSegment::editDrag(EditData& ed)
{
Grip g = ed.curGrip;
ups(g).off += ed.delta;
QPointF delta;
switch (g) {
case Grip::START:
case Grip::END:
//
// move anchor for slurs/ties
//
if ((g == Grip::START && isSingleBeginType()) || (g == Grip::END && isSingleEndType())) {
Spanner* spanner = slurTie();
Qt::KeyboardModifiers km = qApp->keyboardModifiers();
Element* e = ed.view->elementNear(ed.pos);
if (e && e->isNote()) {
Note* note = toNote(e);
int tick = note->chord()->tick();
if ((g == Grip::END && tick > slurTie()->tick()) || (g == Grip::START && tick < slurTie()->tick2())) {
if (km != (Qt::ShiftModifier | Qt::ControlModifier)) {
Chord* c = note->chord();
ed.view->setDropTarget(note);
if (c->part() == spanner->part() && c != spanner->endCR())
changeAnchor(ed, c);
}
}
}
else
ed.view->setDropTarget(0);
}
break;
case Grip::BEZIER1:
break;
case Grip::BEZIER2:
break;
case Grip::SHOULDER:
ups(g).off = QPointF();
delta = ed.delta;
break;
case Grip::DRAG:
ups(g).off = QPointF();
setOffset(offset() + ed.delta);
break;
case Grip::NO_GRIP:
case Grip::GRIPS:
break;
}
computeBezier(delta);
}
//---------------------------------------------------------
// getProperty
//---------------------------------------------------------
QVariant SlurTieSegment::getProperty(Pid propertyId) const
{
switch (propertyId) {
case Pid::LINE_TYPE:
case Pid::SLUR_DIRECTION:
return slurTie()->getProperty(propertyId);
case Pid::SLUR_UOFF1:
return ups(Grip::START).off;
case Pid::SLUR_UOFF2:
return ups(Grip::BEZIER1).off;
case Pid::SLUR_UOFF3:
return ups(Grip::BEZIER2).off;
case Pid::SLUR_UOFF4:
return ups(Grip::END).off;
default:
return SpannerSegment::getProperty(propertyId);
}
}
//---------------------------------------------------------
// setProperty
//---------------------------------------------------------
bool SlurTieSegment::setProperty(Pid propertyId, const QVariant& v)
{
switch(propertyId) {
case Pid::LINE_TYPE:
case Pid::SLUR_DIRECTION:
return slurTie()->setProperty(propertyId, v);
case Pid::SLUR_UOFF1:
ups(Grip::START).off = v.toPointF();
break;
case Pid::SLUR_UOFF2:
ups(Grip::BEZIER1).off = v.toPointF();
break;
case Pid::SLUR_UOFF3:
ups(Grip::BEZIER2).off = v.toPointF();
break;
case Pid::SLUR_UOFF4:
ups(Grip::END).off = v.toPointF();
break;
default:
return SpannerSegment::setProperty(propertyId, v);
}
score()->setLayoutAll();
return true;
}
//---------------------------------------------------------
// propertyDefault
//---------------------------------------------------------
QVariant SlurTieSegment::propertyDefault(Pid id) const
{
switch (id) {
case Pid::LINE_TYPE:
case Pid::SLUR_DIRECTION:
return slurTie()->propertyDefault(id);
case Pid::SLUR_UOFF1:
case Pid::SLUR_UOFF2:
case Pid::SLUR_UOFF3:
case Pid::SLUR_UOFF4:
return QPointF();
default:
return SpannerSegment::propertyDefault(id);
}
}
//---------------------------------------------------------
// reset
//---------------------------------------------------------
void SlurTieSegment::reset()
{
Element::reset();
undoResetProperty(Pid::SLUR_UOFF1);
undoResetProperty(Pid::SLUR_UOFF2);
undoResetProperty(Pid::SLUR_UOFF3);
undoResetProperty(Pid::SLUR_UOFF4);
slurTie()->reset();
}
//---------------------------------------------------------
// undoChangeProperty
//---------------------------------------------------------
void SlurTieSegment::undoChangeProperty(Pid pid, const QVariant& val, PropertyFlags ps)
{
if (pid == Pid::AUTOPLACE && (val.toBool() == true && !autoplace())) {
// Switching autoplacement on. Save user-defined
// placement properties to undo stack.
undoPushProperty(Pid::SLUR_UOFF1);
undoPushProperty(Pid::SLUR_UOFF2);
undoPushProperty(Pid::SLUR_UOFF3);
undoPushProperty(Pid::SLUR_UOFF4);
// other will be saved in base classes.
}
SpannerSegment::undoChangeProperty(pid, val, ps);
}
//---------------------------------------------------------
// writeProperties
//---------------------------------------------------------
void SlurTieSegment::writeSlur(XmlWriter& xml, int no) const
{
if (visible() && autoplace()
&& (color() == Qt::black)
&& offset().isNull()
&& ups(Grip::START).off.isNull()
&& ups(Grip::BEZIER1).off.isNull()
&& ups(Grip::BEZIER2).off.isNull()
&& ups(Grip::END).off.isNull()
)
return;
xml.stag(this, QString("no=\"%1\"").arg(no));
qreal _spatium = spatium();
if (!ups(Grip::START).off.isNull())
xml.tag("o1", ups(Grip::START).off / _spatium);
if (!ups(Grip::BEZIER1).off.isNull())
xml.tag("o2", ups(Grip::BEZIER1).off / _spatium);
if (!ups(Grip::BEZIER2).off.isNull())
xml.tag("o3", ups(Grip::BEZIER2).off / _spatium);
if (!ups(Grip::END).off.isNull())
xml.tag("o4", ups(Grip::END).off / _spatium);
Element::writeProperties(xml);
xml.etag();
}
//---------------------------------------------------------
// readSegment
//---------------------------------------------------------
void SlurTieSegment::read(XmlReader& e)
{
qreal _spatium = spatium();
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "o1")
ups(Grip::START).off = e.readPoint() * _spatium;
else if (tag == "o2")
ups(Grip::BEZIER1).off = e.readPoint() * _spatium;
else if (tag == "o3")
ups(Grip::BEZIER2).off = e.readPoint() * _spatium;
else if (tag == "o4")
ups(Grip::END).off = e.readPoint() * _spatium;
else if (!Element::readProperties(e))
e.unknown();
}
}
//---------------------------------------------------------
// drawEditMode
//---------------------------------------------------------
void SlurTieSegment::drawEditMode(QPainter* p, EditData& ed)
{
QPolygonF polygon(7);
polygon[0] = QPointF(ed.grip[int(Grip::START)].center());
polygon[1] = QPointF(ed.grip[int(Grip::BEZIER1)].center());
polygon[2] = QPointF(ed.grip[int(Grip::SHOULDER)].center());
polygon[3] = QPointF(ed.grip[int(Grip::BEZIER2)].center());
polygon[4] = QPointF(ed.grip[int(Grip::END)].center());
polygon[5] = QPointF(ed.grip[int(Grip::DRAG)].center());
polygon[6] = QPointF(ed.grip[int(Grip::START)].center());
p->setPen(QPen(MScore::frameMarginColor, 0.0));
p->drawPolyline(polygon);
p->setPen(QPen(MScore::defaultColor, 0.0));
for (int i = 0; i < ed.grips; ++i) {
// This must be done with an if-else statement rather than a ternary operator.
// This is because there are two setBrush methods that take different types
// of argument, either a Qt::BrushStyle or a QBrush. Since a QBrush can be
// constructed from a QColour, passing Mscore::frameMarginColor works.
// Qt::NoBrush is a Qt::BrushStyle, however, so if it is passed in a ternary
// operator with a QColor, a new QColor will be created from it, and from that
// a QBrush. Instead, what we really want to do is pass Qt::NoBrush as a
// Qt::BrushStyle, therefore this requires two seperate function calls:
if (Grip(i) == ed.curGrip)
p->setBrush(MScore::frameMarginColor);
else
p->setBrush(Qt::NoBrush);
p->drawRect(ed.grip[i]);
}
}
//---------------------------------------------------------
// SlurTie
//---------------------------------------------------------
SlurTie::SlurTie(Score* s)
: Spanner(s)
{
_slurDirection = Direction::AUTO;
_up = true;
_lineType = 0; // default is solid
}
SlurTie::SlurTie(const SlurTie& t)
: Spanner(t)
{
_up = t._up;
_slurDirection = t._slurDirection;
_lineType = t._lineType;
}
//---------------------------------------------------------
// SlurTie
//---------------------------------------------------------
SlurTie::~SlurTie()
{
}
//---------------------------------------------------------
// writeProperties
//---------------------------------------------------------
void SlurTie::writeProperties(XmlWriter& xml) const
{
Element::writeProperties(xml);
int idx = 0;
for (const SpannerSegment* ss : spannerSegments())
((SlurTieSegment*)ss)->writeSlur(xml, idx++);
writeProperty(xml, Pid::SLUR_DIRECTION);
writeProperty(xml, Pid::LINE_TYPE);
}
//---------------------------------------------------------
// readProperties
//---------------------------------------------------------
bool SlurTie::readProperties(XmlReader& e)
{
const QStringRef& tag(e.name());
if (readProperty(tag, e, Pid::SLUR_DIRECTION))
;
else if (tag == "lineType")
_lineType = e.readInt();
else if (tag == "SlurSegment" || tag == "TieSegment") {
const int idx = e.intAttribute("no", 0);
const int n = int(spannerSegments().size());
for (int i = n; i < idx; ++i)
add(newSlurTieSegment());
SlurTieSegment* s = newSlurTieSegment();
s->read(e);
add(s);
}
else if (!Element::readProperties(e))
return false;
return true;
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
void SlurTie::read(XmlReader& e)
{
while (e.readNextStartElement()) {
if (!SlurTie::readProperties(e))
e.unknown();
}
}
//---------------------------------------------------------
// undoSetLineType
//---------------------------------------------------------
void SlurTie::undoSetLineType(int t)
{
undoChangeProperty(Pid::LINE_TYPE, t);
}
//---------------------------------------------------------
// undoSetSlurDirection
//---------------------------------------------------------
void SlurTie::undoSetSlurDirection(Direction d)
{
undoChangeProperty(Pid::SLUR_DIRECTION, QVariant::fromValue<Direction>(d));
}
//---------------------------------------------------------
// getProperty
//---------------------------------------------------------
QVariant SlurTie::getProperty(Pid propertyId) const
{
switch (propertyId) {
case Pid::LINE_TYPE:
return lineType();
case Pid::SLUR_DIRECTION:
return QVariant::fromValue<Direction>(slurDirection());
default:
return Spanner::getProperty(propertyId);
}
}
//---------------------------------------------------------
// setProperty
//---------------------------------------------------------
bool SlurTie::setProperty(Pid propertyId, const QVariant& v)
{
switch (propertyId) {
case Pid::LINE_TYPE:
setLineType(v.toInt());
break;
case Pid::SLUR_DIRECTION:
setSlurDirection(v.value<Direction>());
break;
default:
return Spanner::setProperty(propertyId, v);
}
score()->setLayoutAll();
return true;
}
//---------------------------------------------------------
// propertyDefault
//---------------------------------------------------------
QVariant SlurTie::propertyDefault(Pid id) const
{
switch (id) {
case Pid::LINE_TYPE:
return 0;
case Pid::SLUR_DIRECTION:
return QVariant::fromValue<Direction>(Direction::AUTO);
default:
return Spanner::propertyDefault(id);
}
}
//---------------------------------------------------------
// firstNoteRestSegmentX
// in System() coordinates
// returns the position just after the last non-chordrest segment
//---------------------------------------------------------
qreal SlurTie::firstNoteRestSegmentX(System* system)
{
for (const MeasureBase* mb : system->measures()) {
if (mb->isMeasure()) {
const Measure* measure = static_cast<const Measure*>(mb);
for (const Segment* seg = measure->first(); seg; seg = seg->next()) {
if (seg->isChordRestType()) {
// first CR found; back up to previous segment
seg = seg->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());
}
return seg->measure()->pos().x() + seg->pos().x() + width;
}
else
return 0.0;
}
}
}
}
qDebug("firstNoteRestSegmentX: did not find segment");
return 0.0;
}
//---------------------------------------------------------
// fixupSegments
//---------------------------------------------------------
void SlurTie::fixupSegments(unsigned nsegs)
{
Spanner::fixupSegments(nsegs, [this]() { return newSlurTieSegment(); });
}
//---------------------------------------------------------
// reset
//---------------------------------------------------------
void SlurTie::reset()
{
Element::reset();
undoResetProperty(Pid::SLUR_DIRECTION);
undoResetProperty(Pid::LINE_TYPE);
}
}