2012-05-26 14:26:10 +02:00
|
|
|
//=============================================================================
|
|
|
|
// MuseScore
|
|
|
|
// Music Composition & Notation
|
|
|
|
//
|
|
|
|
// Copyright (C) 2002-2011 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 "tuplet.h"
|
|
|
|
#include "score.h"
|
|
|
|
#include "chord.h"
|
|
|
|
#include "note.h"
|
|
|
|
#include "xml.h"
|
2013-04-24 11:29:19 +02:00
|
|
|
#include "staff.h"
|
2012-05-26 14:26:10 +02:00
|
|
|
#include "style.h"
|
|
|
|
#include "text.h"
|
|
|
|
#include "element.h"
|
|
|
|
#include "undo.h"
|
|
|
|
#include "stem.h"
|
2013-09-02 19:07:39 +02:00
|
|
|
#include "beam.h"
|
|
|
|
#include "measure.h"
|
2019-03-22 21:27:38 +01:00
|
|
|
#include "system.h"
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2013-05-13 18:49:17 +02:00
|
|
|
namespace Ms {
|
|
|
|
|
2018-08-01 11:46:07 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// tupletStyle
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
static const ElementStyle tupletStyle {
|
|
|
|
{ Sid::tupletDirection, Pid::DIRECTION },
|
|
|
|
{ Sid::tupletNumberType, Pid::NUMBER_TYPE },
|
|
|
|
{ Sid::tupletBracketType, Pid::BRACKET_TYPE },
|
|
|
|
{ Sid::tupletBracketWidth, Pid::LINE_WIDTH },
|
|
|
|
{ Sid::tupletFontFace, Pid::FONT_FACE },
|
|
|
|
{ Sid::tupletFontSize, Pid::FONT_SIZE },
|
2018-11-26 21:09:20 +01:00
|
|
|
{ Sid::tupletFontStyle, Pid::FONT_STYLE },
|
2018-08-01 11:46:07 +02:00
|
|
|
{ Sid::tupletAlign, Pid::ALIGN },
|
2019-06-05 11:35:38 +02:00
|
|
|
{ Sid::tupletMinDistance, Pid::MIN_DISTANCE },
|
2019-05-19 20:51:43 +02:00
|
|
|
{ Sid::tupletFontSpatiumDependent, Pid::SIZE_SPATIUM_DEPENDENT },
|
2018-08-01 11:46:07 +02:00
|
|
|
};
|
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// Tuplet
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Tuplet::Tuplet(Score* s)
|
2018-07-04 12:41:03 +02:00
|
|
|
: DurationElement(s)
|
2012-05-26 14:26:10 +02:00
|
|
|
{
|
2014-12-19 00:14:30 +01:00
|
|
|
_ratio = Fraction(1, 1);
|
2012-05-26 14:26:10 +02:00
|
|
|
_number = 0;
|
|
|
|
_hasBracket = false;
|
|
|
|
_isUp = true;
|
2018-08-01 11:46:07 +02:00
|
|
|
initElementStyle(&tupletStyle);
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Tuplet::Tuplet(const Tuplet& t)
|
|
|
|
: DurationElement(t)
|
|
|
|
{
|
|
|
|
_tick = t._tick;
|
|
|
|
_hasBracket = t._hasBracket;
|
|
|
|
_ratio = t._ratio;
|
|
|
|
_baseLen = t._baseLen;
|
|
|
|
_direction = t._direction;
|
2016-07-08 16:48:04 +02:00
|
|
|
_numberType = t._numberType;
|
|
|
|
_bracketType = t._bracketType;
|
2018-03-16 12:08:57 +01:00
|
|
|
_bracketWidth = t._bracketWidth;
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2016-07-08 16:48:04 +02:00
|
|
|
_isUp = t._isUp;
|
|
|
|
|
|
|
|
p1 = t.p1;
|
|
|
|
p2 = t.p2;
|
|
|
|
_p1 = t._p1;
|
|
|
|
_p2 = t._p2;
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2018-04-09 11:51:35 +02:00
|
|
|
// recreated on layout
|
|
|
|
_number = 0;
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// ~Tuplet
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Tuplet::~Tuplet()
|
|
|
|
{
|
2019-03-19 13:48:38 +01:00
|
|
|
for (DurationElement* de : _elements)
|
|
|
|
de->setTuplet(nullptr);
|
2012-05-26 14:26:10 +02:00
|
|
|
delete _number;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setSelected
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tuplet::setSelected(bool f)
|
|
|
|
{
|
|
|
|
Element::setSelected(f);
|
|
|
|
if (_number)
|
|
|
|
_number->setSelected(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setVisible
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tuplet::setVisible(bool f)
|
|
|
|
{
|
|
|
|
Element::setVisible(f);
|
|
|
|
if (_number)
|
|
|
|
_number->setVisible(f);
|
|
|
|
}
|
|
|
|
|
2019-01-30 15:13:54 +01:00
|
|
|
#if 0
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// tick
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Fraction Tuplet::tick() const
|
|
|
|
{
|
|
|
|
std::vector<DurationElement*> _elements;
|
|
|
|
|
|
|
|
const DurationElement* de = this;
|
|
|
|
while (de->isTuplet()) {
|
|
|
|
const Tuplet* t = toTuplet(de);
|
|
|
|
if (t->_elements.empty())
|
|
|
|
return Fraction(0, 1);
|
|
|
|
de = t->_elements.front();
|
|
|
|
}
|
|
|
|
return toChordRest(de)->tick();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// rtick
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Fraction Tuplet::rtick() const
|
|
|
|
{
|
|
|
|
return tick() - measure()->tick();
|
|
|
|
}
|
|
|
|
|
2018-08-29 15:39:15 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// resetNumberProperty
|
|
|
|
// reset number properties to default values
|
|
|
|
// Set FONT_ITALIC to true, because for tuplets number should be italic
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tuplet::resetNumberProperty()
|
|
|
|
{
|
2019-05-19 20:51:43 +02:00
|
|
|
for (auto p : { Pid::FONT_FACE, Pid::FONT_STYLE, Pid::FONT_SIZE, Pid::ALIGN, Pid::SIZE_SPATIUM_DEPENDENT })
|
2018-08-29 15:39:15 +02:00
|
|
|
_number->resetProperty(p);
|
|
|
|
}
|
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// layout
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tuplet::layout()
|
|
|
|
{
|
|
|
|
if (_elements.empty()) {
|
|
|
|
qDebug("Tuplet::layout(): tuplet is empty");
|
|
|
|
return;
|
|
|
|
}
|
2013-04-24 11:29:19 +02:00
|
|
|
// is in a TAB without stems, skip any format: tuplets are not shown
|
2019-08-19 15:07:30 +02:00
|
|
|
if (staff() && staff()->isTabStaff(tick()) && staff()->staffType(tick())->stemless())
|
2013-04-24 11:29:19 +02:00
|
|
|
return;
|
|
|
|
|
2018-03-27 14:40:34 +02:00
|
|
|
//
|
|
|
|
// create tuplet number if necessary
|
|
|
|
//
|
2012-05-26 14:26:10 +02:00
|
|
|
qreal _spatium = spatium();
|
2018-03-16 10:54:18 +01:00
|
|
|
if (_numberType != TupletNumberType::NO_TEXT) {
|
2012-05-26 14:26:10 +02:00
|
|
|
if (_number == 0) {
|
2018-09-14 11:15:17 +02:00
|
|
|
_number = new Text(score(), Tid::TUPLET);
|
2018-04-09 11:51:35 +02:00
|
|
|
_number->setComposition(true);
|
2013-05-29 12:55:56 +02:00
|
|
|
_number->setTrack(track());
|
2012-05-26 14:26:10 +02:00
|
|
|
_number->setParent(this);
|
|
|
|
_number->setVisible(visible());
|
2018-08-29 15:39:15 +02:00
|
|
|
resetNumberProperty();
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2018-03-16 10:54:18 +01:00
|
|
|
if (_numberType == TupletNumberType::SHOW_NUMBER)
|
2015-04-27 12:59:30 +02:00
|
|
|
_number->setXmlText(QString("%1").arg(_ratio.numerator()));
|
2012-05-26 14:26:10 +02:00
|
|
|
else
|
2015-04-27 12:59:30 +02:00
|
|
|
_number->setXmlText(QString("%1:%2").arg(_ratio.numerator()).arg(_ratio.denominator()));
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (_number) {
|
|
|
|
if (_number->selected())
|
|
|
|
score()->deselect(_number);
|
|
|
|
delete _number;
|
|
|
|
_number = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
|
|
// find out main direction
|
|
|
|
//
|
2016-03-02 13:20:19 +01:00
|
|
|
if (_direction == Direction::AUTO) {
|
2012-05-26 14:26:10 +02:00
|
|
|
int up = 1;
|
2017-07-10 19:23:56 +02:00
|
|
|
for (const DurationElement* e : _elements) {
|
2016-07-08 16:48:04 +02:00
|
|
|
if (e->isChord()) {
|
|
|
|
const Chord* c = toChord(e);
|
2016-03-02 13:20:19 +01:00
|
|
|
if (c->stemDirection() != Direction::AUTO)
|
|
|
|
up += c->stemDirection() == Direction::UP ? 1000 : -1000;
|
2018-09-18 11:34:11 +02:00
|
|
|
else {
|
2012-05-26 14:26:10 +02:00
|
|
|
up += c->up() ? 1 : -1;
|
2018-09-18 11:34:11 +02:00
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2016-07-08 16:48:04 +02:00
|
|
|
else if (e->isTuplet()) {
|
2012-05-26 14:26:10 +02:00
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_isUp = up > 0;
|
|
|
|
}
|
|
|
|
else
|
2016-03-02 13:20:19 +01:00
|
|
|
_isUp = _direction == Direction::UP;
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2018-03-27 14:40:34 +02:00
|
|
|
//
|
|
|
|
// find first and last chord of tuplet
|
|
|
|
// (tuplets can be nested)
|
|
|
|
//
|
2019-03-22 21:27:38 +01:00
|
|
|
bool nested = false;
|
2012-05-26 14:26:10 +02:00
|
|
|
const DurationElement* cr1 = _elements.front();
|
2016-07-08 16:48:04 +02:00
|
|
|
while (cr1->isTuplet()) {
|
|
|
|
const Tuplet* t = toTuplet(cr1);
|
2012-05-26 14:26:10 +02:00
|
|
|
if (t->elements().empty())
|
|
|
|
break;
|
2019-03-22 21:27:38 +01:00
|
|
|
nested = true;
|
2012-05-26 14:26:10 +02:00
|
|
|
cr1 = t->elements().front();
|
|
|
|
}
|
|
|
|
const DurationElement* cr2 = _elements.back();
|
2016-07-08 16:48:04 +02:00
|
|
|
while (cr2->isTuplet()) {
|
|
|
|
const Tuplet* t = toTuplet(cr2);
|
2012-05-26 14:26:10 +02:00
|
|
|
if (t->elements().empty())
|
|
|
|
break;
|
2019-03-22 21:27:38 +01:00
|
|
|
nested = true;
|
2012-05-26 14:26:10 +02:00
|
|
|
cr2 = t->elements().back();
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// shall we draw a bracket?
|
|
|
|
//
|
2018-03-16 10:54:18 +01:00
|
|
|
if (_bracketType == TupletBracketType::AUTO_BRACKET) {
|
2013-05-24 11:44:21 +02:00
|
|
|
_hasBracket = false;
|
2018-03-27 14:40:34 +02:00
|
|
|
for (DurationElement* e : _elements) {
|
2016-07-08 16:48:04 +02:00
|
|
|
if (e->isTuplet() || e->isRest()) {
|
2013-05-24 11:44:21 +02:00
|
|
|
_hasBracket = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (e->isChordRest()) {
|
2016-07-08 16:48:04 +02:00
|
|
|
ChordRest* cr = toChordRest(e);
|
2013-05-24 11:44:21 +02:00
|
|
|
//
|
|
|
|
// maybe we should check for more than one beam
|
|
|
|
//
|
|
|
|
if (cr->beam() == 0) {
|
2012-05-26 14:26:10 +02:00
|
|
|
_hasBracket = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
2018-03-16 10:54:18 +01:00
|
|
|
_hasBracket = _bracketType != TupletBracketType::SHOW_NO_BRACKET;
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// calculate bracket start and end point p1 p2
|
|
|
|
//
|
2018-03-27 15:36:00 +02:00
|
|
|
qreal maxSlope = score()->styleD(Sid::tupletMaxSlope);
|
|
|
|
bool outOfStaff = score()->styleB(Sid::tupletOufOfStaff);
|
|
|
|
qreal vHeadDistance = score()->styleP(Sid::tupletVHeadDistance);
|
|
|
|
qreal vStemDistance = score()->styleP(Sid::tupletVStemDistance);
|
|
|
|
qreal stemLeft = score()->styleP(Sid::tupletStemLeftDistance);
|
|
|
|
qreal stemRight = score()->styleP(Sid::tupletStemRightDistance);
|
|
|
|
qreal noteLeft = score()->styleP(Sid::tupletNoteLeftDistance);
|
|
|
|
qreal noteRight = score()->styleP(Sid::tupletNoteRightDistance);
|
2013-09-02 19:07:39 +02:00
|
|
|
|
2015-04-18 00:13:24 +02:00
|
|
|
int move = 0;
|
2019-03-22 21:27:38 +01:00
|
|
|
setTrack(cr1->staffIdx() * VOICES + voice());
|
2015-04-18 00:13:24 +02:00
|
|
|
if (outOfStaff && cr1->isChordRest() && cr2->isChordRest()) {
|
|
|
|
// account for staff move when adjusting bracket to avoid staff
|
|
|
|
// but don't attempt adjustment unless both endpoints are in same staff
|
2019-03-22 21:27:38 +01:00
|
|
|
// and not a nested tuplet
|
|
|
|
if (toChordRest(cr1)->staffMove() == toChordRest(cr2)->staffMove() && !tuplet() && !nested) {
|
2016-07-08 16:48:04 +02:00
|
|
|
move = toChordRest(cr1)->staffMove();
|
2019-03-22 21:27:38 +01:00
|
|
|
if (move == 1)
|
|
|
|
setTrack(cr1->vStaffIdx() * VOICES + voice());
|
2018-09-18 11:34:11 +02:00
|
|
|
}
|
2015-04-18 00:13:24 +02:00
|
|
|
else
|
|
|
|
outOfStaff = false;
|
|
|
|
}
|
|
|
|
|
2018-07-20 11:11:59 +02:00
|
|
|
qreal l1 = score()->styleP(Sid::tupletBracketHookHeight);
|
2015-02-02 00:01:03 +01:00
|
|
|
qreal l2l = vHeadDistance; // left bracket vertical distance
|
|
|
|
qreal l2r = vHeadDistance; // right bracket vertical distance right
|
2013-05-24 11:44:21 +02:00
|
|
|
|
|
|
|
if (_isUp)
|
2013-09-02 19:07:39 +02:00
|
|
|
vHeadDistance = -vHeadDistance;
|
2013-05-24 11:44:21 +02:00
|
|
|
|
|
|
|
p1 = cr1->pagePos();
|
|
|
|
p2 = cr2->pagePos();
|
2018-09-18 11:34:11 +02:00
|
|
|
|
2013-09-02 19:07:39 +02:00
|
|
|
p1.rx() -= noteLeft;
|
|
|
|
p2.rx() += score()->noteHeadWidth() + noteRight;
|
2018-07-20 10:03:21 +02:00
|
|
|
p1.ry() += vHeadDistance; // TODO: Direction ?
|
2013-09-02 19:07:39 +02:00
|
|
|
p2.ry() += vHeadDistance;
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2013-09-02 19:07:39 +02:00
|
|
|
qreal xx1 = p1.x(); // use to center the number on the beam
|
2013-05-29 16:45:39 +02:00
|
|
|
|
2015-02-02 00:01:03 +01:00
|
|
|
// follow beam angle if one beam extends over entire tuplet
|
|
|
|
bool followBeam = false;
|
|
|
|
qreal beamAdjust = 0.0;
|
|
|
|
if (cr1->beam() && cr1->beam() == cr2->beam()) {
|
|
|
|
followBeam = true;
|
2019-03-24 15:14:52 +01:00
|
|
|
beamAdjust = point(score()->styleS(Sid::beamWidth)) * 0.5 * mag();
|
2015-02-02 00:01:03 +01:00
|
|
|
}
|
|
|
|
|
2013-05-24 11:44:21 +02:00
|
|
|
if (_isUp) {
|
2016-07-08 16:48:04 +02:00
|
|
|
if (cr1->isChord()) {
|
|
|
|
const Chord* chord1 = toChord(cr1);
|
2012-05-26 14:26:10 +02:00
|
|
|
Stem* stem = chord1->stem();
|
2013-09-02 19:07:39 +02:00
|
|
|
if (stem)
|
2013-05-29 16:45:39 +02:00
|
|
|
xx1 = stem->abbox().x();
|
2013-09-02 19:07:39 +02:00
|
|
|
if (chord1->up()) {
|
|
|
|
if (stem) {
|
2015-02-02 00:01:03 +01:00
|
|
|
if (followBeam)
|
|
|
|
p1.ry() = stem->abbox().y() - beamAdjust;
|
|
|
|
else if (chord1->beam())
|
2013-09-02 19:07:39 +02:00
|
|
|
p1.ry() = chord1->beam()->abbox().y();
|
|
|
|
else
|
|
|
|
p1.ry() = stem->abbox().y();
|
|
|
|
l2l = vStemDistance;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
p1.ry() = chord1->upNote()->abbox().top(); // whole note
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
}
|
2013-09-02 19:07:39 +02:00
|
|
|
else if (!chord1->up()) {
|
|
|
|
p1.ry() = chord1->upNote()->abbox().top();
|
2015-02-02 00:01:03 +01:00
|
|
|
if (stem)
|
2013-09-02 19:07:39 +02:00
|
|
|
p1.rx() = cr1->pagePos().x() - stemLeft;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
|
2016-07-08 16:48:04 +02:00
|
|
|
if (cr2->isChord()) {
|
|
|
|
const Chord* chord2 = toChord(cr2);
|
2012-05-26 14:26:10 +02:00
|
|
|
Stem* stem = chord2->stem();
|
2013-09-02 19:07:39 +02:00
|
|
|
if (stem && chord2->up()) {
|
2015-02-02 00:01:03 +01:00
|
|
|
if (followBeam)
|
|
|
|
p2.ry() = stem->abbox().top() - beamAdjust;
|
2019-03-22 21:27:38 +01:00
|
|
|
else if (chord2->beam() && !chord2->staffMove() && !chord2->beam()->cross())
|
2013-09-02 19:07:39 +02:00
|
|
|
p2.ry() = chord2->beam()->abbox().top();
|
|
|
|
else
|
|
|
|
p2.ry() = stem->abbox().top();
|
|
|
|
l2r = vStemDistance;
|
|
|
|
p2.rx() = chord2->pagePos().x() + chord2->maxHeadWidth() + stemRight;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
p2.ry() = chord2->upNote()->abbox().top();
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
|
|
// special case: one of the bracket endpoints is
|
|
|
|
// a rest
|
|
|
|
//
|
2016-07-08 16:48:04 +02:00
|
|
|
if (cr1->isChord() && cr2->isChord()) {
|
2012-05-26 14:26:10 +02:00
|
|
|
if (p2.y() < p1.y())
|
|
|
|
p1.setY(p2.y());
|
|
|
|
else
|
|
|
|
p2.setY(p1.y());
|
|
|
|
}
|
2016-07-08 16:48:04 +02:00
|
|
|
else if (cr1->isChord() && !cr2->isChord()) {
|
2012-05-26 14:26:10 +02:00
|
|
|
if (p1.y() < p2.y())
|
|
|
|
p2.setY(p1.y());
|
|
|
|
else
|
|
|
|
p1.setY(p2.y());
|
|
|
|
}
|
|
|
|
|
2013-09-02 19:07:39 +02:00
|
|
|
// outOfStaff
|
2015-02-02 00:01:03 +01:00
|
|
|
if (outOfStaff) {
|
2015-04-18 00:13:24 +02:00
|
|
|
qreal min = cr1->measure()->staffabbox(cr1->staffIdx() + move).y();
|
2013-09-02 19:07:39 +02:00
|
|
|
if (min < p1.y()) {
|
|
|
|
p1.ry() = min;
|
|
|
|
l2l = vStemDistance;
|
|
|
|
}
|
2015-04-18 00:13:24 +02:00
|
|
|
min = cr2->measure()->staffabbox(cr2->staffIdx() + move).y();
|
2013-09-02 19:07:39 +02:00
|
|
|
if (min < p2.y()) {
|
|
|
|
p2.ry() = min;
|
|
|
|
l2r = vStemDistance;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-02 00:01:03 +01:00
|
|
|
// check that slope is no more than max
|
2013-09-02 19:07:39 +02:00
|
|
|
qreal d = (p2.y() - p1.y())/(p2.x() - p1.x());
|
|
|
|
if (d < -maxSlope) {
|
|
|
|
// move p1 y up
|
|
|
|
p1.ry() = p2.y() + maxSlope * (p2.x() - p1.x());
|
|
|
|
}
|
|
|
|
else if (d > maxSlope) {
|
|
|
|
// move p2 y up
|
|
|
|
p2.ry() = p1.ry() + maxSlope * (p2.x() - p1.x());
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2013-09-02 19:07:39 +02:00
|
|
|
// check for collisions
|
2018-09-11 16:56:50 +02:00
|
|
|
size_t n = _elements.size();
|
2012-05-26 14:26:10 +02:00
|
|
|
if (n >= 3) {
|
2013-09-02 19:07:39 +02:00
|
|
|
d = (p2.y() - p1.y())/(p2.x() - p1.x());
|
2018-09-11 16:56:50 +02:00
|
|
|
for (size_t i = 1; i < (n-1); ++i) {
|
2012-05-26 14:26:10 +02:00
|
|
|
Element* e = _elements[i];
|
2016-07-08 16:48:04 +02:00
|
|
|
if (e->isChord()) {
|
|
|
|
const Chord* chord = toChord(e);
|
2012-05-26 14:26:10 +02:00
|
|
|
const Stem* stem = chord->stem();
|
|
|
|
if (stem) {
|
2015-02-02 00:01:03 +01:00
|
|
|
QRectF r(chord->up() ? stem->abbox() : chord->upNote()->abbox());
|
2012-05-26 14:26:10 +02:00
|
|
|
qreal y3 = r.top();
|
|
|
|
qreal x3 = r.x() + r.width() * .5;
|
|
|
|
qreal y0 = p1.y() + (x3 - p1.x()) * d;
|
|
|
|
qreal c = y0 - y3;
|
|
|
|
if (c > 0) {
|
|
|
|
p1.ry() -= c;
|
|
|
|
p2.ry() -= c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2016-07-08 16:48:04 +02:00
|
|
|
if (cr1->isChord()) {
|
|
|
|
const Chord* chord1 = toChord(cr1);
|
2012-05-26 14:26:10 +02:00
|
|
|
Stem* stem = chord1->stem();
|
2013-09-02 19:07:39 +02:00
|
|
|
if (stem)
|
2013-05-29 16:45:39 +02:00
|
|
|
xx1 = stem->abbox().x();
|
2013-09-02 19:07:39 +02:00
|
|
|
if (!chord1->up()) {
|
2015-02-02 00:01:03 +01:00
|
|
|
if (stem) {
|
|
|
|
if (followBeam)
|
|
|
|
p1.ry() = stem->abbox().bottom() + beamAdjust;
|
|
|
|
else if (chord1->beam())
|
2013-09-02 19:07:39 +02:00
|
|
|
p1.ry() = chord1->beam()->abbox().bottom();
|
|
|
|
else
|
|
|
|
p1.ry() = stem->abbox().bottom();
|
|
|
|
l2l = vStemDistance;
|
|
|
|
p1.rx() = cr1->pagePos().x() - stemLeft;
|
2013-05-29 16:45:39 +02:00
|
|
|
}
|
2015-02-02 00:01:03 +01:00
|
|
|
else {
|
2013-09-02 19:07:39 +02:00
|
|
|
p1.ry() = chord1->downNote()->abbox().bottom(); // whole note
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
}
|
2013-09-02 19:07:39 +02:00
|
|
|
else if (chord1->up()) {
|
|
|
|
p1.ry() = chord1->downNote()->abbox().bottom();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
|
2016-07-08 16:48:04 +02:00
|
|
|
if (cr2->isChord()) {
|
|
|
|
const Chord* chord2 = toChord(cr2);
|
2012-05-26 14:26:10 +02:00
|
|
|
Stem* stem = chord2->stem();
|
|
|
|
if (stem && !chord2->up()) {
|
2013-05-24 11:44:21 +02:00
|
|
|
// if (chord2->beam())
|
|
|
|
// p2.setX(stem->abbox().x());
|
2018-09-17 14:05:56 +02:00
|
|
|
if (followBeam) //??
|
|
|
|
p2.ry() = stem->abbox().bottom() + beamAdjust; //??
|
2019-03-22 21:27:38 +01:00
|
|
|
if (chord2->beam() && !chord2->staffMove() && !chord2->beam()->cross())
|
2013-09-02 19:07:39 +02:00
|
|
|
p2.ry() = chord2->beam()->abbox().bottom();
|
|
|
|
else
|
|
|
|
p2.ry() = stem->abbox().bottom();
|
|
|
|
l2r = vStemDistance;
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2013-09-02 19:07:39 +02:00
|
|
|
else {
|
|
|
|
p2.ry() = chord2->downNote()->abbox().bottom();
|
|
|
|
if (stem)
|
|
|
|
p2.rx() = chord2->pagePos().x() + chord2->maxHeadWidth() + stemRight;
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
}
|
2013-09-02 19:07:39 +02:00
|
|
|
//
|
|
|
|
// special case: one of the bracket endpoints is
|
|
|
|
// a rest
|
|
|
|
//
|
2016-07-08 16:48:04 +02:00
|
|
|
if (!cr1->isChord() && cr2->isChord()) {
|
2012-05-26 14:26:10 +02:00
|
|
|
if (p2.y() > p1.y())
|
|
|
|
p1.setY(p2.y());
|
|
|
|
else
|
|
|
|
p2.setY(p1.y());
|
|
|
|
}
|
2016-07-08 16:48:04 +02:00
|
|
|
else if (cr1->isChord() && !cr2->isChord()) {
|
2012-05-26 14:26:10 +02:00
|
|
|
if (p1.y() > p2.y())
|
|
|
|
p2.setY(p1.y());
|
|
|
|
else
|
|
|
|
p1.setY(p2.y());
|
|
|
|
}
|
2013-09-02 19:07:39 +02:00
|
|
|
// outOfStaff
|
2015-02-02 00:01:03 +01:00
|
|
|
if (outOfStaff) {
|
2015-04-18 00:13:24 +02:00
|
|
|
qreal max = cr1->measure()->staffabbox(cr1->staffIdx() + move).bottom();
|
2013-09-02 19:07:39 +02:00
|
|
|
if (max > p1.y()) {
|
|
|
|
p1.ry() = max;
|
|
|
|
l2l = vStemDistance;
|
|
|
|
}
|
2015-04-18 00:13:24 +02:00
|
|
|
max = cr2->measure()->staffabbox(cr2->staffIdx() + move).bottom();
|
2013-09-02 19:07:39 +02:00
|
|
|
if (max > p2.y()) {
|
|
|
|
p2.ry() = max;
|
|
|
|
l2r = vStemDistance;
|
|
|
|
}
|
|
|
|
}
|
2015-02-02 00:01:03 +01:00
|
|
|
// check that slope is no more than max
|
2013-09-02 19:07:39 +02:00
|
|
|
qreal d = (p2.y() - p1.y())/(p2.x() - p1.x());
|
|
|
|
if (d < -maxSlope) {
|
|
|
|
// move p1 y up
|
|
|
|
p2.ry() = p1.y() - maxSlope * (p2.x() - p1.x());
|
|
|
|
}
|
|
|
|
else if (d > maxSlope) {
|
|
|
|
// move p2 y up
|
|
|
|
p1.ry() = p2.ry() - maxSlope * (p2.x() - p1.x());
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
// check for collisions
|
2018-09-11 16:56:50 +02:00
|
|
|
size_t n = _elements.size();
|
2012-05-26 14:26:10 +02:00
|
|
|
if (n >= 3) {
|
2018-09-25 15:55:08 +02:00
|
|
|
d = (p2.y() - p1.y())/(p2.x() - p1.x());
|
2018-09-11 16:56:50 +02:00
|
|
|
for (size_t i = 1; i < (n-1); ++i) {
|
2012-05-26 14:26:10 +02:00
|
|
|
Element* e = _elements[i];
|
2016-07-08 16:48:04 +02:00
|
|
|
if (e->isChord()) {
|
|
|
|
const Chord* chord = toChord(e);
|
2012-05-26 14:26:10 +02:00
|
|
|
const Stem* stem = chord->stem();
|
|
|
|
if (stem) {
|
2015-02-02 00:01:03 +01:00
|
|
|
QRectF r(chord->up() ? chord->downNote()->abbox() : stem->abbox());
|
2012-05-26 14:26:10 +02:00
|
|
|
qreal y3 = r.bottom();
|
|
|
|
qreal x3 = r.x() + r.width() * .5;
|
|
|
|
qreal y0 = p1.y() + (x3 - p1.x()) * d;
|
|
|
|
qreal c = y0 - y3;
|
|
|
|
if (c < 0) {
|
|
|
|
p1.ry() -= c;
|
|
|
|
p2.ry() -= c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setPos(0.0, 0.0);
|
|
|
|
QPointF mp(parent()->pagePos());
|
2019-03-22 21:27:38 +01:00
|
|
|
if (parent()->isMeasure()) {
|
|
|
|
System* s = toMeasure(parent())->system();
|
|
|
|
if (s)
|
|
|
|
mp.ry() += s->staff(staffIdx())->y();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
p1 -= mp;
|
|
|
|
p2 -= mp;
|
|
|
|
|
|
|
|
p1 += _p1;
|
|
|
|
p2 += _p2;
|
2013-05-29 16:45:39 +02:00
|
|
|
xx1 -= mp.x();
|
2013-09-02 19:07:39 +02:00
|
|
|
|
|
|
|
p1.ry() -= l2l * (_isUp ? 1.0 : -1.0);
|
|
|
|
p2.ry() -= l2r * (_isUp ? 1.0 : -1.0);
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2018-03-27 14:40:34 +02:00
|
|
|
// l2l l2r, mp, _p1, _p2 const
|
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
// center number
|
|
|
|
qreal x3 = 0.0;
|
|
|
|
qreal numberWidth = 0.0;
|
|
|
|
if (_number) {
|
|
|
|
_number->layout();
|
2013-09-02 19:07:39 +02:00
|
|
|
numberWidth = _number->bbox().width();
|
2018-06-27 10:38:00 +02:00
|
|
|
|
2018-09-14 11:15:17 +02:00
|
|
|
qreal y3 = p1.y() + (p2.y() - p1.y()) * .5 - l1 * (_isUp ? 1.0 : -1.0);
|
2013-05-29 16:45:39 +02:00
|
|
|
//
|
|
|
|
// for beamed tuplets, center number on beam
|
|
|
|
//
|
2013-09-02 19:07:39 +02:00
|
|
|
if (cr1->beam() && cr2->beam() && cr1->beam() == cr2->beam()) {
|
2016-07-08 16:48:04 +02:00
|
|
|
const ChordRest* crr = toChordRest(cr1);
|
2018-07-20 10:03:21 +02:00
|
|
|
if (_isUp == crr->up()) {
|
2013-09-02 19:07:39 +02:00
|
|
|
qreal deltax = cr2->pagePos().x() - cr1->pagePos().x();
|
|
|
|
x3 = xx1 + deltax * .5;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
qreal deltax = p2.x() - p1.x();
|
|
|
|
x3 = p1.x() + deltax * .5;
|
|
|
|
}
|
2013-06-06 10:35:57 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
qreal deltax = p2.x() - p1.x();
|
|
|
|
x3 = p1.x() + deltax * .5;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
_number->setPos(QPointF(x3, y3) - ipos());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_hasBracket) {
|
|
|
|
qreal slope = (p2.y() - p1.y()) / (p2.x() - p1.x());
|
|
|
|
|
|
|
|
if (_isUp) {
|
|
|
|
if (_number) {
|
2013-09-02 19:07:39 +02:00
|
|
|
bracketL[0] = QPointF(p1.x(), p1.y());
|
|
|
|
bracketL[1] = QPointF(p1.x(), p1.y() - l1);
|
2018-08-29 15:39:15 +02:00
|
|
|
//set width of bracket hole
|
2018-09-14 11:15:17 +02:00
|
|
|
qreal x = x3 - numberWidth * .5 - _spatium * .5;
|
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
qreal y = p1.y() + (x - p1.x()) * slope;
|
2013-09-02 19:07:39 +02:00
|
|
|
bracketL[2] = QPointF(x, y - l1);
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2018-09-14 11:15:17 +02:00
|
|
|
//set width of bracket hole
|
|
|
|
x = x3 + numberWidth * .5 + _spatium * .5;
|
2012-05-26 14:26:10 +02:00
|
|
|
y = p1.y() + (x - p1.x()) * slope;
|
2013-09-02 19:07:39 +02:00
|
|
|
bracketR[0] = QPointF(x, y - l1);
|
|
|
|
bracketR[1] = QPointF(p2.x(), p2.y() - l1);
|
|
|
|
bracketR[2] = QPointF(p2.x(), p2.y());
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
else {
|
2013-09-02 19:07:39 +02:00
|
|
|
bracketL[0] = QPointF(p1.x(), p1.y());
|
|
|
|
bracketL[1] = QPointF(p1.x(), p1.y() - l1);
|
|
|
|
bracketL[2] = QPointF(p2.x(), p2.y() - l1);
|
|
|
|
bracketL[3] = QPointF(p2.x(), p2.y());
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (_number) {
|
2013-09-02 19:07:39 +02:00
|
|
|
bracketL[0] = QPointF(p1.x(), p1.y());
|
|
|
|
bracketL[1] = QPointF(p1.x(), p1.y() + l1);
|
2018-08-29 15:39:15 +02:00
|
|
|
//set width of bracket hole
|
2018-09-14 11:15:17 +02:00
|
|
|
qreal x = x3 - numberWidth * .5 - _spatium * .5;
|
2012-05-26 14:26:10 +02:00
|
|
|
qreal y = p1.y() + (x - p1.x()) * slope;
|
2013-09-02 19:07:39 +02:00
|
|
|
bracketL[2] = QPointF(x, y + l1);
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2018-09-14 11:15:17 +02:00
|
|
|
//set width of bracket hole
|
|
|
|
x = x3 + numberWidth * .5 + _spatium * .5;
|
2012-05-26 14:26:10 +02:00
|
|
|
y = p1.y() + (x - p1.x()) * slope;
|
2013-09-02 19:07:39 +02:00
|
|
|
bracketR[0] = QPointF(x, y + l1);
|
|
|
|
bracketR[1] = QPointF(p2.x(), p2.y() + l1);
|
|
|
|
bracketR[2] = QPointF(p2.x(), p2.y());
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
else {
|
2013-09-02 19:07:39 +02:00
|
|
|
bracketL[0] = QPointF(p1.x(), p1.y());
|
|
|
|
bracketL[1] = QPointF(p1.x(), p1.y() + l1);
|
|
|
|
bracketL[2] = QPointF(p2.x(), p2.y() + l1);
|
|
|
|
bracketL[3] = QPointF(p2.x(), p2.y());
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-03-27 14:40:34 +02:00
|
|
|
|
|
|
|
// collect bounding box
|
2012-05-26 14:26:10 +02:00
|
|
|
QRectF r;
|
|
|
|
if (_number) {
|
|
|
|
r |= _number->bbox().translated(_number->pos());
|
|
|
|
if (_hasBracket) {
|
|
|
|
QRectF b;
|
|
|
|
b.setCoords(bracketL[1].x(), bracketL[1].y(), bracketR[2].x(), bracketR[2].y());
|
|
|
|
r |= b;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (_hasBracket) {
|
|
|
|
QRectF b;
|
|
|
|
b.setCoords(bracketL[1].x(), bracketL[1].y(), bracketL[3].x(), bracketL[3].y());
|
|
|
|
r |= b;
|
|
|
|
}
|
|
|
|
setbbox(r);
|
2019-06-05 11:35:38 +02:00
|
|
|
|
2019-06-30 16:19:34 +02:00
|
|
|
if (outOfStaff && !cross())
|
2019-06-05 11:35:38 +02:00
|
|
|
autoplaceMeasureElement(_isUp, /* add to skyline */ true);
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// draw
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tuplet::draw(QPainter* painter) const
|
|
|
|
{
|
2013-04-24 11:29:19 +02:00
|
|
|
// if in a TAB without stems, tuplets are not shown
|
2019-08-19 15:07:30 +02:00
|
|
|
if (staff() && staff()->isTabStaff(tick()) && staff()->staffType(tick())->stemless())
|
2013-04-24 11:29:19 +02:00
|
|
|
return;
|
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
QColor color(curColor());
|
|
|
|
if (_number) {
|
|
|
|
painter->setPen(color);
|
|
|
|
QPointF pos(_number->pos());
|
|
|
|
painter->translate(pos);
|
|
|
|
_number->draw(painter);
|
|
|
|
painter->translate(-pos);
|
|
|
|
}
|
|
|
|
if (_hasBracket) {
|
2018-05-09 16:30:37 +02:00
|
|
|
painter->setPen(QPen(color, _bracketWidth.val()));
|
2012-05-26 14:26:10 +02:00
|
|
|
if (!_number)
|
|
|
|
painter->drawPolyline(bracketL, 4);
|
|
|
|
else {
|
|
|
|
painter->drawPolyline(bracketL, 3);
|
|
|
|
painter->drawPolyline(bracketR, 3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-16 10:54:18 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// Rect
|
|
|
|
// helper class
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
class Rect : public QRectF {
|
|
|
|
public:
|
|
|
|
Rect(const QPointF& p1, const QPointF& p2, qreal w);
|
|
|
|
};
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// Rect
|
|
|
|
// construct a rectangle out of a line with width w
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Rect::Rect(const QPointF& p1, const QPointF& p2, qreal w)
|
|
|
|
{
|
|
|
|
qreal w2 = w * .5;
|
|
|
|
setCoords(qMin(p1.x(), p2.x()) - w2, qMin(p1.y(), p2.y()) - w2, qMax(p1.x(), p2.x()) + w2, qMax(p1.y(), p2.y()) + w2);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// shape
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Shape Tuplet::shape() const
|
|
|
|
{
|
|
|
|
Shape s;
|
|
|
|
if (_hasBracket) {
|
2018-05-09 16:30:37 +02:00
|
|
|
qreal w = _bracketWidth.val();
|
2018-07-23 11:17:52 +02:00
|
|
|
s.add(Rect(bracketL[0], bracketL[1], w));
|
|
|
|
s.add(Rect(bracketL[1], bracketL[2], w));
|
2018-03-16 10:54:18 +01:00
|
|
|
if (_number) {
|
|
|
|
s.add(Rect(bracketR[0], bracketR[1], w));
|
|
|
|
s.add(Rect(bracketR[1], bracketR[2], w));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
s.add(Rect(bracketL[2], bracketL[3], w));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (_number)
|
|
|
|
s.add(_number->bbox().translated(_number->pos()));
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2015-06-05 11:55:37 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// scanElements
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tuplet::scanElements(void* data, void (*func)(void*, Element*), bool all)
|
|
|
|
{
|
|
|
|
if (_number && all)
|
|
|
|
func(data, _number);
|
2018-03-27 14:40:34 +02:00
|
|
|
func(data, this);
|
2015-06-05 11:55:37 +02:00
|
|
|
}
|
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// write
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-11-19 11:51:21 +01:00
|
|
|
void Tuplet::write(XmlWriter& xml) const
|
2012-05-26 14:26:10 +02:00
|
|
|
{
|
2018-09-26 12:20:00 +02:00
|
|
|
xml.stag(this);
|
2012-05-26 14:26:10 +02:00
|
|
|
Element::writeProperties(xml);
|
2018-03-16 10:54:18 +01:00
|
|
|
|
2018-03-27 15:36:00 +02:00
|
|
|
writeProperty(xml, Pid::NORMAL_NOTES);
|
|
|
|
writeProperty(xml, Pid::ACTUAL_NOTES);
|
|
|
|
writeProperty(xml, Pid::P1);
|
|
|
|
writeProperty(xml, Pid::P2);
|
2012-08-10 17:01:35 +02:00
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
xml.tag("baseNote", _baseLen.name());
|
2018-11-30 17:27:29 +01:00
|
|
|
if (int dots = _baseLen.dots())
|
|
|
|
xml.tag("baseDots", dots);
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
if (_number) {
|
2018-09-26 12:20:00 +02:00
|
|
|
xml.stag("Number", _number);
|
2012-05-26 14:26:10 +02:00
|
|
|
_number->writeProperties(xml);
|
|
|
|
xml.etag();
|
|
|
|
}
|
2019-09-20 08:45:47 +02:00
|
|
|
|
|
|
|
writeStyledProperties(xml);
|
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
xml.etag();
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// read
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2013-01-11 18:10:18 +01:00
|
|
|
void Tuplet::read(XmlReader& e)
|
2012-05-26 14:26:10 +02:00
|
|
|
{
|
2018-06-27 10:38:00 +02:00
|
|
|
_id = e.intAttribute("id", 0);
|
2013-01-11 18:10:18 +01:00
|
|
|
while (e.readNextStartElement()) {
|
2016-11-09 17:12:52 +01:00
|
|
|
if (readProperties(e))
|
|
|
|
;
|
|
|
|
else
|
2013-01-11 18:10:18 +01:00
|
|
|
e.unknown();
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2018-11-30 17:27:29 +01:00
|
|
|
Fraction f = _baseLen.fraction() * _ratio.denominator();
|
2019-01-30 15:13:54 +01:00
|
|
|
setTicks(f.reduced());
|
2016-11-09 17:12:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// readProperties
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool Tuplet::readProperties(XmlReader& e)
|
|
|
|
{
|
|
|
|
const QStringRef& tag(e.name());
|
2018-04-09 11:51:35 +02:00
|
|
|
|
2018-03-16 10:54:18 +01:00
|
|
|
if (readStyledProperty(e, tag))
|
2017-01-16 20:51:12 +01:00
|
|
|
;
|
2019-09-20 08:45:47 +02:00
|
|
|
else if (tag == "bold") { //important that these properties are read after number is created
|
|
|
|
bool val = e.readInt();
|
|
|
|
_number->setBold(val);
|
|
|
|
if (isStyled(Pid::FONT_STYLE))
|
|
|
|
setPropertyFlags(Pid::FONT_STYLE, PropertyFlags::UNSTYLED);
|
|
|
|
}
|
|
|
|
else if (tag == "italic") {
|
|
|
|
bool val = e.readInt();
|
|
|
|
_number->setItalic(val);
|
|
|
|
if (isStyled(Pid::FONT_STYLE))
|
|
|
|
setPropertyFlags(Pid::FONT_STYLE, PropertyFlags::UNSTYLED);
|
|
|
|
}
|
|
|
|
else if (tag == "underline") {
|
|
|
|
bool val = e.readInt();
|
|
|
|
_number->setUnderline(val);
|
|
|
|
if (isStyled(Pid::FONT_STYLE))
|
|
|
|
setPropertyFlags(Pid::FONT_STYLE, PropertyFlags::UNSTYLED);
|
|
|
|
}
|
2016-11-09 17:12:52 +01:00
|
|
|
else if (tag == "normalNotes")
|
|
|
|
_ratio.setDenominator(e.readInt());
|
|
|
|
else if (tag == "actualNotes")
|
|
|
|
_ratio.setNumerator(e.readInt());
|
|
|
|
else if (tag == "p1")
|
2018-06-11 17:30:29 +02:00
|
|
|
_p1 = e.readPoint() * score()->spatium();
|
2016-11-09 17:12:52 +01:00
|
|
|
else if (tag == "p2")
|
2018-06-11 17:30:29 +02:00
|
|
|
_p2 = e.readPoint() * score()->spatium();
|
2016-11-09 17:12:52 +01:00
|
|
|
else if (tag == "baseNote")
|
|
|
|
_baseLen = TDuration(e.readElementText());
|
2018-11-30 17:27:29 +01:00
|
|
|
else if (tag == "baseDots")
|
|
|
|
_baseLen.setDots(e.readInt());
|
2016-11-09 17:12:52 +01:00
|
|
|
else if (tag == "Number") {
|
2018-09-14 11:15:17 +02:00
|
|
|
_number = new Text(score(), Tid::TUPLET);
|
2018-04-09 11:51:35 +02:00
|
|
|
_number->setComposition(true);
|
2016-11-09 17:12:52 +01:00
|
|
|
_number->setParent(this);
|
2018-09-14 11:15:17 +02:00
|
|
|
resetNumberProperty();
|
2016-11-09 17:12:52 +01:00
|
|
|
_number->read(e);
|
|
|
|
_number->setVisible(visible()); //?? override saved property
|
|
|
|
_number->setTrack(track());
|
2018-06-27 10:38:00 +02:00
|
|
|
// move property flags from _number back to tuplet
|
2018-11-26 21:09:20 +01:00
|
|
|
for (auto p : { Pid::FONT_FACE, Pid::FONT_SIZE, Pid::FONT_STYLE, Pid::ALIGN })
|
2018-03-16 10:54:18 +01:00
|
|
|
setPropertyFlags(p, _number->propertyFlags(p));
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2016-11-09 17:12:52 +01:00
|
|
|
else if (!DurationElement::readProperties(e))
|
|
|
|
return false;
|
|
|
|
return true;
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// add
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tuplet::add(Element* e)
|
|
|
|
{
|
|
|
|
#ifndef NDEBUG
|
2014-09-28 11:10:18 +02:00
|
|
|
for(DurationElement* el : _elements) {
|
2012-05-26 14:26:10 +02:00
|
|
|
if (el == e) {
|
2016-11-28 17:25:26 +01:00
|
|
|
qDebug("%p: %p %s already there", this, e, e->name());
|
2014-09-28 11:10:18 +02:00
|
|
|
return;
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2016-07-08 16:48:04 +02:00
|
|
|
switch (e->type()) {
|
2017-01-18 14:16:33 +01:00
|
|
|
case ElementType::CHORD:
|
|
|
|
case ElementType::REST:
|
|
|
|
case ElementType::TUPLET: {
|
2013-01-24 09:31:41 +01:00
|
|
|
bool found = false;
|
2016-11-28 17:25:26 +01:00
|
|
|
DurationElement* de = toDurationElement(e);
|
2019-01-30 15:13:54 +01:00
|
|
|
Fraction tick = de->rtick();
|
|
|
|
if (tick != Fraction(-1,1)) {
|
2016-07-09 17:07:12 +02:00
|
|
|
for (unsigned int i = 0; i < _elements.size(); ++i) {
|
2019-01-30 15:13:54 +01:00
|
|
|
if (_elements[i]->rtick() > tick) {
|
2016-07-09 17:07:12 +02:00
|
|
|
_elements.insert(_elements.begin() + i, de);
|
2013-01-24 09:31:41 +01:00
|
|
|
found = true;
|
|
|
|
break;
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-01-24 09:31:41 +01:00
|
|
|
if (!found)
|
2016-07-09 17:07:12 +02:00
|
|
|
_elements.push_back(de);
|
2012-05-26 14:26:10 +02:00
|
|
|
de->setTuplet(this);
|
2013-01-24 09:31:41 +01:00
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
qDebug("Tuplet::add() unknown element");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// remove
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tuplet::remove(Element* e)
|
|
|
|
{
|
2016-07-08 16:48:04 +02:00
|
|
|
switch (e->type()) {
|
2018-03-27 14:40:34 +02:00
|
|
|
// case ElementType::TEXT:
|
|
|
|
// if (e == _number)
|
|
|
|
// _number = 0;
|
|
|
|
// break;
|
2017-01-18 14:16:33 +01:00
|
|
|
case ElementType::CHORD:
|
|
|
|
case ElementType::REST:
|
|
|
|
case ElementType::TUPLET: {
|
2017-12-20 16:49:30 +01:00
|
|
|
auto i = std::find(_elements.begin(), _elements.end(), toDurationElement(e));
|
2016-07-09 17:07:12 +02:00
|
|
|
if (i == _elements.end()) {
|
2014-04-15 17:01:41 +02:00
|
|
|
qDebug("Tuplet::remove: cannot find element <%s>", e->name());
|
2016-07-10 12:00:57 +02:00
|
|
|
qDebug(" elements %zu", _elements.size());
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2016-07-09 17:07:12 +02:00
|
|
|
else
|
|
|
|
_elements.erase(i);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
qDebug("Tuplet::remove: unknown element");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// isEditable
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool Tuplet::isEditable() const
|
|
|
|
{
|
|
|
|
return _hasBracket;
|
|
|
|
}
|
|
|
|
|
2020-01-10 13:55:21 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// startEditDrag
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tuplet::startEditDrag(EditData& ed)
|
|
|
|
{
|
|
|
|
DurationElement::startEditDrag(ed);
|
|
|
|
ElementEditData* eed = ed.getData(this);
|
|
|
|
|
|
|
|
eed->pushProperty(Pid::P1);
|
|
|
|
eed->pushProperty(Pid::P2);
|
|
|
|
}
|
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// editDrag
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2017-03-31 13:03:15 +02:00
|
|
|
void Tuplet::editDrag(EditData& ed)
|
2012-05-26 14:26:10 +02:00
|
|
|
{
|
2015-01-19 12:37:17 +01:00
|
|
|
if (ed.curGrip == Grip::START)
|
2012-05-26 14:26:10 +02:00
|
|
|
_p1 += ed.delta;
|
|
|
|
else
|
|
|
|
_p2 += ed.delta;
|
|
|
|
setGenerated(false);
|
2019-06-30 16:19:34 +02:00
|
|
|
//layout();
|
|
|
|
//score()->setUpdateAll();
|
|
|
|
triggerLayout();
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2019-12-13 14:04:22 +01:00
|
|
|
// gripsPositions
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-12-13 14:04:22 +01:00
|
|
|
std::vector<QPointF> Tuplet::gripsPositions(const EditData&) const
|
2012-05-26 14:26:10 +02:00
|
|
|
{
|
2019-12-13 14:04:22 +01:00
|
|
|
const QPointF pp(pagePos());
|
|
|
|
return { pp + p1, pp + p2 };
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2012-11-19 10:08:15 +01:00
|
|
|
// reset
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2012-11-19 10:08:15 +01:00
|
|
|
void Tuplet::reset()
|
2012-05-26 14:26:10 +02:00
|
|
|
{
|
2018-03-27 15:36:00 +02:00
|
|
|
undoChangeProperty(Pid::P1, QPointF());
|
|
|
|
undoChangeProperty(Pid::P2, QPointF());
|
2012-11-19 10:08:15 +01:00
|
|
|
Element::reset();
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// dump
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tuplet::dump() const
|
|
|
|
{
|
|
|
|
Element::dump();
|
|
|
|
qDebug("ratio %s", qPrintable(_ratio.print()));
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setTrack
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tuplet::setTrack(int val)
|
|
|
|
{
|
2018-12-27 23:55:22 +01:00
|
|
|
if (tuplet())
|
|
|
|
tuplet()->setTrack(val);
|
2013-05-29 12:55:56 +02:00
|
|
|
if (_number)
|
|
|
|
_number->setTrack(val);
|
2012-05-26 14:26:10 +02:00
|
|
|
Element::setTrack(val);
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// tickGreater
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
static bool tickGreater(const DurationElement* a, const DurationElement* b)
|
|
|
|
{
|
|
|
|
return a->tick() < b->tick();
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// sortElements
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tuplet::sortElements()
|
|
|
|
{
|
|
|
|
qSort(_elements.begin(), _elements.end(), tickGreater);
|
|
|
|
}
|
|
|
|
|
2019-03-22 21:27:38 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// cross
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool Tuplet::cross() const
|
|
|
|
{
|
|
|
|
for (DurationElement* de : _elements) {
|
|
|
|
if (!de) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (de->isChordRest()) {
|
|
|
|
if (toChordRest(de)->staffMove())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (de->isTuplet()) {
|
|
|
|
if (toTuplet(de)->cross())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-08-01 08:16:41 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// elementsDuration
|
|
|
|
/// Get the sum of the element fraction in the tuplet,
|
|
|
|
/// even if the tuplet is not complete yet
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Fraction Tuplet::elementsDuration()
|
|
|
|
{
|
|
|
|
Fraction f;
|
2016-07-09 17:07:12 +02:00
|
|
|
for (DurationElement* el : _elements)
|
2019-01-30 15:13:54 +01:00
|
|
|
f += el->ticks();
|
2013-08-01 08:16:41 +02:00
|
|
|
return f;
|
|
|
|
}
|
|
|
|
|
2012-08-10 17:01:35 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// getProperty
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2018-03-27 15:36:00 +02:00
|
|
|
QVariant Tuplet::getProperty(Pid propertyId) const
|
2012-05-26 14:26:10 +02:00
|
|
|
{
|
2016-03-08 17:58:04 +01:00
|
|
|
switch (propertyId) {
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::DIRECTION:
|
2017-06-22 12:42:14 +02:00
|
|
|
return QVariant::fromValue<Direction>(_direction);
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::NUMBER_TYPE:
|
2014-05-26 18:19:38 +02:00
|
|
|
return int(_numberType);
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::BRACKET_TYPE:
|
2014-05-26 18:19:38 +02:00
|
|
|
return int(_bracketType);
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::LINE_WIDTH:
|
2018-03-16 12:08:57 +01:00
|
|
|
return _bracketWidth;
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::NORMAL_NOTES:
|
2012-08-10 17:01:35 +02:00
|
|
|
return _ratio.denominator();
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::ACTUAL_NOTES:
|
2012-08-10 17:01:35 +02:00
|
|
|
return _ratio.numerator();
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::P1:
|
2012-08-10 17:01:35 +02:00
|
|
|
return _p1;
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::P2:
|
2012-08-10 17:01:35 +02:00
|
|
|
return _p2;
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::FONT_SIZE:
|
|
|
|
case Pid::FONT_FACE:
|
2018-11-26 21:09:20 +01:00
|
|
|
case Pid::FONT_STYLE:
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::ALIGN:
|
2019-05-19 20:51:43 +02:00
|
|
|
case Pid::SIZE_SPATIUM_DEPENDENT:
|
2017-07-10 19:23:56 +02:00
|
|
|
return _number ? _number->getProperty(propertyId) : QVariant();
|
2012-08-10 17:01:35 +02:00
|
|
|
default:
|
2012-05-26 14:26:10 +02:00
|
|
|
break;
|
|
|
|
}
|
2012-09-25 13:33:50 +02:00
|
|
|
return DurationElement::getProperty(propertyId);
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2012-08-10 17:01:35 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setProperty
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2018-03-27 15:36:00 +02:00
|
|
|
bool Tuplet::setProperty(Pid propertyId, const QVariant& v)
|
2012-05-26 14:26:10 +02:00
|
|
|
{
|
2016-03-08 17:58:04 +01:00
|
|
|
switch (propertyId) {
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::DIRECTION:
|
2016-03-02 13:20:19 +01:00
|
|
|
setDirection(v.value<Direction>());
|
2012-08-10 17:01:35 +02:00
|
|
|
break;
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::NUMBER_TYPE:
|
2018-03-16 10:54:18 +01:00
|
|
|
setNumberType(TupletNumberType(v.toInt()));
|
2012-08-10 17:01:35 +02:00
|
|
|
break;
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::BRACKET_TYPE:
|
2018-03-16 10:54:18 +01:00
|
|
|
setBracketType(TupletBracketType(v.toInt()));
|
2012-08-10 17:01:35 +02:00
|
|
|
break;
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::LINE_WIDTH:
|
2018-03-16 12:08:57 +01:00
|
|
|
setBracketWidth(v.value<Spatium>());
|
|
|
|
break;
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::NORMAL_NOTES:
|
2012-08-10 17:01:35 +02:00
|
|
|
_ratio.setDenominator(v.toInt());
|
|
|
|
break;
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::ACTUAL_NOTES:
|
2012-08-10 17:01:35 +02:00
|
|
|
_ratio.setNumerator(v.toInt());
|
|
|
|
break;
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::P1:
|
2012-08-10 17:01:35 +02:00
|
|
|
_p1 = v.toPointF();
|
|
|
|
break;
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::P2:
|
2012-08-10 17:01:35 +02:00
|
|
|
_p2 = v.toPointF();
|
|
|
|
break;
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::FONT_SIZE:
|
|
|
|
case Pid::FONT_FACE:
|
2018-11-26 21:09:20 +01:00
|
|
|
case Pid::FONT_STYLE:
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::ALIGN:
|
2019-05-19 20:51:43 +02:00
|
|
|
case Pid::SIZE_SPATIUM_DEPENDENT:
|
2017-07-10 19:23:56 +02:00
|
|
|
if (_number)
|
|
|
|
_number->setProperty(propertyId, v);
|
|
|
|
break;
|
2012-08-10 17:01:35 +02:00
|
|
|
default:
|
|
|
|
return DurationElement::setProperty(propertyId, v);
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2016-06-14 10:32:34 +02:00
|
|
|
if (!_elements.empty()) {
|
|
|
|
_elements.front()->triggerLayout();
|
|
|
|
_elements.back()->triggerLayout();
|
|
|
|
}
|
2012-08-10 17:01:35 +02:00
|
|
|
return true;
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2012-08-10 17:01:35 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// propertyDefault
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2018-03-27 15:36:00 +02:00
|
|
|
QVariant Tuplet::propertyDefault(Pid id) const
|
2012-05-26 14:26:10 +02:00
|
|
|
{
|
2012-08-10 17:01:35 +02:00
|
|
|
switch(id) {
|
2018-08-01 11:46:07 +02:00
|
|
|
case Pid::SUB_STYLE:
|
|
|
|
return int(Tid::TUPLET);
|
|
|
|
case Pid::SYSTEM_FLAG:
|
|
|
|
return false;
|
|
|
|
case Pid::TEXT:
|
|
|
|
return QString("");
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::NORMAL_NOTES:
|
|
|
|
case Pid::ACTUAL_NOTES:
|
2014-12-19 00:14:30 +01:00
|
|
|
return 0;
|
2018-03-27 15:36:00 +02:00
|
|
|
case Pid::P1:
|
|
|
|
case Pid::P2:
|
2012-08-10 17:01:35 +02:00
|
|
|
return QPointF();
|
2018-09-14 11:15:17 +02:00
|
|
|
case Pid::ALIGN:
|
|
|
|
return score()->styleV(Sid::tupletAlign);
|
|
|
|
case Pid::FONT_FACE:
|
|
|
|
return score()->styleV(Sid::tupletFontFace);
|
|
|
|
case Pid::FONT_SIZE:
|
|
|
|
return score()->styleV(Sid::tupletFontSize);
|
2018-11-26 21:09:20 +01:00
|
|
|
case Pid::FONT_STYLE:
|
|
|
|
return score()->styleV(Sid::tupletFontStyle);
|
2019-05-19 20:51:43 +02:00
|
|
|
case Pid::SIZE_SPATIUM_DEPENDENT:
|
|
|
|
return score()->styleV(Sid::tupletFontSpatiumDependent);
|
2012-08-10 17:01:35 +02:00
|
|
|
default:
|
2018-08-09 15:36:37 +02:00
|
|
|
{
|
|
|
|
QVariant v = ScoreElement::propertyDefault(id, Tid::DEFAULT);
|
|
|
|
if (v.isValid())
|
|
|
|
return v;
|
|
|
|
}
|
2012-08-10 17:01:35 +02:00
|
|
|
return DurationElement::propertyDefault(id);
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-17 14:28:05 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// sanitizeTuplet
|
|
|
|
/// Check validity of tuplets and coherence between duration
|
|
|
|
/// and baselength. Needed for importing old files due to a bug
|
|
|
|
/// in the released version for corner-case tuplets.
|
|
|
|
/// See issue #136406 and Pull request #2881
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tuplet::sanitizeTuplet()
|
|
|
|
{
|
2018-03-08 20:29:47 +01:00
|
|
|
if (ratio().numerator() == ratio().reduced().numerator()) // return if the ratio is an irreducible fraction
|
2016-12-16 15:29:52 +01:00
|
|
|
return;
|
2018-03-08 20:29:47 +01:00
|
|
|
Fraction baseLenDuration = (Fraction(ratio().denominator(),1) * baseLen().fraction()).reduced();
|
2019-01-30 15:13:54 +01:00
|
|
|
|
2018-03-16 10:54:18 +01:00
|
|
|
// Due to a bug present in 2.1 (and before), a tuplet with non-reduced ratio could be
|
2018-03-08 20:29:47 +01:00
|
|
|
// in a corrupted state (mismatch between duration and base length).
|
2016-12-16 15:29:52 +01:00
|
|
|
// A tentative will now be made to retrieve the correct duration by summing up all the
|
|
|
|
// durations of the elements constituting the tuplet. This does not work for
|
|
|
|
// not-completely filled tuplets, such as tuplets in voices > 0 with
|
|
|
|
// gaps (for example, a tuplet in second voice with a deleted chordrest element)
|
2019-01-30 15:13:54 +01:00
|
|
|
|
2016-11-17 14:28:05 +01:00
|
|
|
Fraction testDuration(0,1);
|
|
|
|
for (DurationElement* de : elements()) {
|
|
|
|
if (de == 0)
|
|
|
|
continue;
|
|
|
|
Fraction elementDuration(0,1);
|
|
|
|
if (de->isTuplet()){
|
|
|
|
Tuplet* t = toTuplet(de);
|
|
|
|
t->sanitizeTuplet();
|
2019-01-30 15:13:54 +01:00
|
|
|
elementDuration = t->ticks();
|
2016-11-17 14:28:05 +01:00
|
|
|
}
|
|
|
|
else {
|
2019-01-30 15:13:54 +01:00
|
|
|
elementDuration = de->ticks();
|
2016-11-17 14:28:05 +01:00
|
|
|
}
|
|
|
|
testDuration += elementDuration;
|
|
|
|
}
|
|
|
|
testDuration = testDuration / ratio();
|
|
|
|
testDuration.reduce();
|
2019-01-30 15:13:54 +01:00
|
|
|
if (elements().back()->tick() + elements().back()->actualTicks() - elements().front()->tick() > testDuration)
|
2018-03-23 17:11:03 +01:00
|
|
|
return; // this tuplet has missing elements; do not sanitize
|
2019-01-30 15:13:54 +01:00
|
|
|
if (!(testDuration == baseLenDuration && baseLenDuration == ticks())) {
|
2016-11-17 14:28:05 +01:00
|
|
|
Fraction f = testDuration * Fraction(1, ratio().denominator());
|
|
|
|
f.reduce();
|
|
|
|
Fraction fbl(1, f.denominator());
|
|
|
|
if (TDuration::isValid(fbl)) {
|
2019-01-30 15:13:54 +01:00
|
|
|
setTicks(testDuration);
|
2016-11-17 14:28:05 +01:00
|
|
|
setBaseLen(fbl);
|
2018-12-09 11:02:11 +01:00
|
|
|
qDebug("Tuplet %p sanitized duration %d/%d baseLen %d/%d",this,
|
|
|
|
testDuration.numerator(), testDuration.denominator(),
|
|
|
|
1, fbl.denominator());
|
2016-11-17 14:28:05 +01:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
qDebug("Impossible to sanitize the tuplet");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-03-20 04:52:08 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// addMissingElement
|
|
|
|
// Add a rest with the given start and end ticks.
|
|
|
|
// Should only be called from Tuplet::addMissingElements().
|
|
|
|
// Needed for importing files that saved incomplete tuplets.
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-01-30 15:13:54 +01:00
|
|
|
Fraction Tuplet::addMissingElement(const Fraction& startTick, const Fraction& endTick)
|
2018-03-20 04:52:08 +01:00
|
|
|
{
|
2019-01-30 15:13:54 +01:00
|
|
|
Fraction f = (endTick - startTick) * ratio();
|
2018-03-20 04:52:08 +01:00
|
|
|
TDuration d = TDuration(f, true);
|
|
|
|
if (!d.isValid()) {
|
|
|
|
qDebug("Tuplet::addMissingElement(): invalid duration: %d/%d", f.numerator(), f.denominator());
|
|
|
|
return Fraction::fromTicks(0);
|
|
|
|
}
|
|
|
|
f = d.fraction();
|
|
|
|
Rest* rest = new Rest(score());
|
|
|
|
rest->setDurationType(d);
|
2019-01-30 15:13:54 +01:00
|
|
|
rest->setTicks(f);
|
2018-03-20 04:52:08 +01:00
|
|
|
rest->setTrack(track());
|
|
|
|
rest->setVisible(false);
|
|
|
|
Segment* segment = measure()->getSegment(SegmentType::ChordRest, startTick);
|
|
|
|
segment->add(rest);
|
|
|
|
add(rest);
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// addMissingElements
|
|
|
|
// Make this tuplet complete by filling in holes where
|
|
|
|
// there ought to be rests. Needed for importing files
|
|
|
|
// that saved incomplete tuplets.
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Tuplet::addMissingElements()
|
|
|
|
{
|
|
|
|
if (tuplet())
|
|
|
|
return; // do not correct nested tuplets
|
|
|
|
if (voice() == 0)
|
|
|
|
return; // nothing to do for tuplets in voice 1
|
2019-01-30 15:13:54 +01:00
|
|
|
Fraction missingElementsDuration = ticks() * ratio() - elementsDuration();
|
2018-03-20 04:52:08 +01:00
|
|
|
if (missingElementsDuration.isZero())
|
|
|
|
return;
|
|
|
|
// first, fill in any holes in the middle of the tuplet
|
2019-01-30 15:13:54 +01:00
|
|
|
Fraction expectedTick = elements().front()->tick();
|
2018-03-20 04:52:08 +01:00
|
|
|
for (DurationElement* de : elements()) {
|
|
|
|
if (de->tick() != expectedTick) {
|
|
|
|
missingElementsDuration -= addMissingElement(expectedTick, de->tick());
|
|
|
|
if (missingElementsDuration.isZero())
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
expectedTick += de->actualTicks();
|
|
|
|
}
|
|
|
|
// calculate the tick where we would expect a tuplet of this duration to start
|
2019-01-30 15:13:54 +01:00
|
|
|
// TODO: check:
|
|
|
|
expectedTick = elements().front()->tick() - Fraction::fromTicks(elements().front()->tick().ticks() % ticks().ticks());
|
2018-03-20 04:52:08 +01:00
|
|
|
if (expectedTick != elements().front()->tick()) {
|
|
|
|
// try to fill a hole at the beginning of the tuplet
|
2019-01-30 15:13:54 +01:00
|
|
|
Fraction firstAvailableTick = measure()->tick();
|
2018-03-20 04:52:08 +01:00
|
|
|
Segment* segment = measure()->findSegment(SegmentType::ChordRest, elements().front()->tick());
|
|
|
|
ChordRest* prevChordRest = segment && segment->prev() ? segment->prev()->nextChordRest(track(), true) : nullptr;
|
|
|
|
if (prevChordRest && prevChordRest->measure() == measure())
|
|
|
|
firstAvailableTick = prevChordRest->tick() + prevChordRest->actualTicks();
|
|
|
|
if (firstAvailableTick != elements().front()->tick()) {
|
|
|
|
Fraction f = missingElementsDuration / ratio();
|
2019-01-30 15:13:54 +01:00
|
|
|
Fraction ticksRequired = f;
|
|
|
|
Fraction endTick = elements().front()->tick();
|
|
|
|
Fraction startTick = max(firstAvailableTick, endTick - ticksRequired);
|
2018-03-20 04:52:08 +01:00
|
|
|
if (expectedTick > startTick)
|
|
|
|
startTick = expectedTick;
|
|
|
|
missingElementsDuration -= addMissingElement(startTick, endTick);
|
|
|
|
if (missingElementsDuration.isZero())
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// now fill a hole at the end of the tuplet
|
2019-01-30 15:13:54 +01:00
|
|
|
Fraction startTick = elements().back()->tick() + elements().back()->actualTicks();
|
|
|
|
Fraction endTick = elements().front()->tick() + ticks();
|
2018-03-20 04:52:08 +01:00
|
|
|
// just to be safe, find the next ChordRest in the track, and adjust endTick if necessary
|
|
|
|
Segment* segment = measure()->findSegment(SegmentType::ChordRest, elements().back()->tick());
|
|
|
|
ChordRest* nextChordRest = segment && segment->next() ? segment->next()->nextChordRest(track(), false) : nullptr;
|
|
|
|
if (nextChordRest && nextChordRest->tick() < endTick)
|
|
|
|
endTick = nextChordRest->tick();
|
|
|
|
missingElementsDuration -= addMissingElement(startTick, endTick);
|
|
|
|
if (!missingElementsDuration.isZero())
|
|
|
|
qDebug("Tuplet::addMissingElements(): still missing duration of %d/%d", missingElementsDuration.numerator(), missingElementsDuration.denominator());
|
|
|
|
}
|
2016-07-08 16:48:04 +02:00
|
|
|
} // namespace Ms
|
2013-05-13 18:49:17 +02:00
|
|
|
|