1275 lines
45 KiB
C++
1275 lines
45 KiB
C++
//=============================================================================
|
||
// 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 "chordrest.h"
|
||
#include "chord.h"
|
||
#include "xml.h"
|
||
#include "style.h"
|
||
#include "system.h"
|
||
#include "measure.h"
|
||
#include "staff.h"
|
||
#include "tuplet.h"
|
||
#include "score.h"
|
||
#include "sym.h"
|
||
#include "slur.h"
|
||
#include "beam.h"
|
||
#include "breath.h"
|
||
#include "barline.h"
|
||
#include "articulation.h"
|
||
#include "tempo.h"
|
||
#include "tempotext.h"
|
||
#include "note.h"
|
||
#include "arpeggio.h"
|
||
#include "dynamic.h"
|
||
#include "stafftext.h"
|
||
#include "sig.h"
|
||
#include "clef.h"
|
||
#include "lyrics.h"
|
||
#include "segment.h"
|
||
#include "stafftype.h"
|
||
#include "undo.h"
|
||
#include "stem.h"
|
||
#include "harmony.h"
|
||
#include "figuredbass.h"
|
||
#include "icon.h"
|
||
#include "utils.h"
|
||
#include "keysig.h"
|
||
#include "page.h"
|
||
#include "hook.h"
|
||
#include "rehearsalmark.h"
|
||
|
||
namespace Ms {
|
||
|
||
//---------------------------------------------------------
|
||
// ChordRest
|
||
//---------------------------------------------------------
|
||
|
||
ChordRest::ChordRest(Score* s)
|
||
: DurationElement(s)
|
||
{
|
||
_staffMove = 0;
|
||
_beam = 0;
|
||
_tabDur = 0;
|
||
_up = true;
|
||
_beamMode = Beam::Mode::AUTO;
|
||
_small = false;
|
||
_crossMeasure = CrossMeasure::UNKNOWN;
|
||
}
|
||
|
||
ChordRest::ChordRest(const ChordRest& cr, bool link)
|
||
: DurationElement(cr)
|
||
{
|
||
_durationType = cr._durationType;
|
||
_staffMove = cr._staffMove;
|
||
_beam = 0;
|
||
_tabDur = 0; // tab sur. symb. depends upon context: can't be
|
||
// simply copied from another CR
|
||
|
||
_beamMode = cr._beamMode;
|
||
_up = cr._up;
|
||
_small = cr._small;
|
||
_crossMeasure = cr._crossMeasure;
|
||
|
||
for (Lyrics* l : cr._lyrics) { // make deep copy
|
||
Lyrics* nl = new Lyrics(*l);
|
||
if (link)
|
||
nl->linkTo(l);
|
||
nl->setParent(this);
|
||
nl->setTrack(track());
|
||
_lyrics.push_back(nl);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// undoUnlink
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::undoUnlink()
|
||
{
|
||
DurationElement::undoUnlink();
|
||
for (Lyrics* l : _lyrics)
|
||
l->undoUnlink();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// ChordRest
|
||
//---------------------------------------------------------
|
||
|
||
ChordRest::~ChordRest()
|
||
{
|
||
qDeleteAll(_lyrics);
|
||
qDeleteAll(_el);
|
||
delete _tabDur;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// scanElements
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::scanElements(void* data, void (*func)(void*, Element*), bool all)
|
||
{
|
||
if (_beam && (_beam->elements().front() == this)
|
||
&& !measure()->slashStyle(staffIdx()))
|
||
_beam->scanElements(data, func, all);
|
||
for (Lyrics* l : _lyrics)
|
||
l->scanElements(data, func, all);
|
||
DurationElement* de = this;
|
||
while (de->tuplet() && de->tuplet()->elements().front() == de) {
|
||
de->tuplet()->scanElements(data, func, all);
|
||
de = de->tuplet();
|
||
}
|
||
if (_tabDur)
|
||
func(data, _tabDur);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// writeProperties
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::writeProperties(XmlWriter& xml) const
|
||
{
|
||
DurationElement::writeProperties(xml);
|
||
|
||
//
|
||
// Beam::Mode default:
|
||
// REST - Beam::Mode::NONE
|
||
// CHORD - Beam::Mode::AUTO
|
||
//
|
||
if ((isRest() && _beamMode != Beam::Mode::NONE) || (isChord() && _beamMode != Beam::Mode::AUTO)) {
|
||
QString s;
|
||
switch(_beamMode) {
|
||
case Beam::Mode::AUTO: s = "auto"; break;
|
||
case Beam::Mode::BEGIN: s = "begin"; break;
|
||
case Beam::Mode::MID: s = "mid"; break;
|
||
case Beam::Mode::END: s = "end"; break;
|
||
case Beam::Mode::NONE: s = "no"; break;
|
||
case Beam::Mode::BEGIN32: s = "begin32"; break;
|
||
case Beam::Mode::BEGIN64: s = "begin64"; break;
|
||
case Beam::Mode::INVALID: s = "?"; break;
|
||
}
|
||
xml.tag("BeamMode", s);
|
||
}
|
||
writeProperty(xml, Pid::SMALL);
|
||
if (actualDurationType().dots())
|
||
xml.tag("dots", actualDurationType().dots());
|
||
writeProperty(xml, Pid::STAFF_MOVE);
|
||
|
||
if (actualDurationType().isValid())
|
||
xml.tag("durationType", actualDurationType().name());
|
||
|
||
if (!duration().isZero() && (!actualDurationType().fraction().isValid()
|
||
|| (actualDurationType().fraction() != duration()))) {
|
||
xml.tag("duration", duration());
|
||
//xml.tagE("duration z=\"%d\" n=\"%d\"", duration().numerator(), duration().denominator());
|
||
}
|
||
|
||
for (Lyrics* lyrics : _lyrics)
|
||
lyrics->write(xml);
|
||
if (!isGrace()) {
|
||
Fraction t(globalDuration());
|
||
if (staff())
|
||
t /= staff()->timeStretch(xml.curTick());
|
||
xml.incCurTick(t.ticks());
|
||
}
|
||
for (auto i : score()->spanner()) { // TODO: don’t search whole list
|
||
Spanner* s = i.second;
|
||
if (s->generated() || !s->isSlur() || toSlur(s)->broken() || !xml.canWrite(s))
|
||
continue;
|
||
|
||
if (s->startElement() == this)
|
||
s->writeSpannerStart(xml, this, track());
|
||
else if (s->endElement() == this)
|
||
s->writeSpannerEnd(xml, this, track());
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// readProperties
|
||
//---------------------------------------------------------
|
||
|
||
bool ChordRest::readProperties(XmlReader& e)
|
||
{
|
||
const QStringRef& tag(e.name());
|
||
|
||
if (tag == "durationType") {
|
||
setDurationType(e.readElementText());
|
||
if (actualDurationType().type() != TDuration::DurationType::V_MEASURE) {
|
||
if (score()->mscVersion() < 112 && (type() == ElementType::REST) &&
|
||
// for backward compatibility, convert V_WHOLE rests to V_MEASURE
|
||
// if long enough to fill a measure.
|
||
// OTOH, freshly created (un-initialized) rests have numerator == 0 (< 4/4)
|
||
// (see Fraction() constructor in fraction.h; this happens for instance
|
||
// when pasting selection from clipboard): they should not be converted
|
||
duration().numerator() != 0 &&
|
||
// rest durations are initialized to full measure duration when
|
||
// created upon reading the <Rest> tag (see Measure::read() )
|
||
// so a V_WHOLE rest in a measure of 4/4 or less => V_MEASURE
|
||
(actualDurationType()==TDuration::DurationType::V_WHOLE && duration() <= Fraction(4, 4)) ) {
|
||
// old pre 2.0 scores: convert
|
||
setDurationType(TDuration::DurationType::V_MEASURE);
|
||
}
|
||
else // not from old score: set duration fraction from duration type
|
||
setDuration(actualDurationType().fraction());
|
||
}
|
||
else {
|
||
if (score()->mscVersion() <= 114) {
|
||
SigEvent event = score()->sigmap()->timesig(e.tick());
|
||
setDuration(event.timesig());
|
||
}
|
||
}
|
||
}
|
||
else if (tag == "BeamMode") {
|
||
QString val(e.readElementText());
|
||
Beam::Mode bm = Beam::Mode::AUTO;
|
||
if (val == "auto")
|
||
bm = Beam::Mode::AUTO;
|
||
else if (val == "begin")
|
||
bm = Beam::Mode::BEGIN;
|
||
else if (val == "mid")
|
||
bm = Beam::Mode::MID;
|
||
else if (val == "end")
|
||
bm = Beam::Mode::END;
|
||
else if (val == "no")
|
||
bm = Beam::Mode::NONE;
|
||
else if (val == "begin32")
|
||
bm = Beam::Mode::BEGIN32;
|
||
else if (val == "begin64")
|
||
bm = Beam::Mode::BEGIN64;
|
||
else
|
||
bm = Beam::Mode(val.toInt());
|
||
_beamMode = Beam::Mode(bm);
|
||
}
|
||
else if (tag == "Articulation") {
|
||
Articulation* atr = new Articulation(score());
|
||
atr->setTrack(track());
|
||
atr->read(e);
|
||
add(atr);
|
||
}
|
||
else if (tag == "leadingSpace" || tag == "trailingSpace") {
|
||
qDebug("ChordRest: %s obsolete", tag.toLocal8Bit().data());
|
||
e.skipCurrentElement();
|
||
}
|
||
else if (tag == "small")
|
||
_small = e.readInt();
|
||
else if (tag == "duration")
|
||
setDuration(e.readFraction());
|
||
else if (tag == "ticklen") { // obsolete (version < 1.12)
|
||
int mticks = score()->sigmap()->timesig(e.tick()).timesig().ticks();
|
||
int i = e.readInt();
|
||
if (i == 0)
|
||
i = mticks;
|
||
if ((type() == ElementType::REST) && (mticks == i)) {
|
||
setDurationType(TDuration::DurationType::V_MEASURE);
|
||
setDuration(Fraction::fromTicks(i));
|
||
}
|
||
else {
|
||
Fraction f = Fraction::fromTicks(i);
|
||
setDuration(f);
|
||
setDurationType(TDuration(f));
|
||
}
|
||
}
|
||
else if (tag == "dots")
|
||
setDots(e.readInt());
|
||
else if (tag == "staffMove")
|
||
_staffMove = e.readInt();
|
||
else if (tag == "Spanner")
|
||
Spanner::readSpanner(e, this, track());
|
||
else if (tag == "Lyrics") {
|
||
Element* element = new Lyrics(score());
|
||
element->setTrack(e.track());
|
||
element->read(e);
|
||
add(element);
|
||
}
|
||
else if (tag == "pos") {
|
||
QPointF pt = e.readPoint();
|
||
setOffset(pt * spatium());
|
||
}
|
||
// else if (tag == "offset")
|
||
// DurationElement::readProperties(e);
|
||
else if (!DurationElement::readProperties(e))
|
||
return false;
|
||
return true;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// ChordRest::readAddConnector
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::readAddConnector(ConnectorInfoReader* info, bool pasteMode)
|
||
{
|
||
const ElementType type = info->type();
|
||
switch (type) {
|
||
case ElementType::SLUR:
|
||
{
|
||
Spanner* spanner = toSpanner(info->connector());
|
||
const Location& l = info->location();
|
||
|
||
if (info->isStart()) {
|
||
spanner->setTrack(l.track());
|
||
spanner->setTick(tick());
|
||
spanner->setStartElement(this);
|
||
if (pasteMode) {
|
||
score()->undoAddElement(spanner);
|
||
for (ScoreElement* ee : spanner->linkList()) {
|
||
if (ee == spanner)
|
||
continue;
|
||
Spanner* ls = toSpanner(ee);
|
||
ls->setTick(spanner->tick());
|
||
for (ScoreElement* eee : linkList()) {
|
||
ChordRest* cr = toChordRest(eee);
|
||
if (cr->score() == eee->score() && cr->staffIdx() == ls->staffIdx()) {
|
||
ls->setTrack(cr->track());
|
||
if (ls->isSlur())
|
||
ls->setStartElement(cr);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else
|
||
score()->addSpanner(spanner);
|
||
}
|
||
else if (info->isEnd()) {
|
||
spanner->setTrack2(l.track());
|
||
spanner->setTick2(tick());
|
||
spanner->setEndElement(this);
|
||
if (pasteMode) {
|
||
for (ScoreElement* ee : spanner->linkList()) {
|
||
if (ee == spanner)
|
||
continue;
|
||
Spanner* ls = static_cast<Spanner*>(ee);
|
||
ls->setTick2(spanner->tick2());
|
||
for (ScoreElement* eee : linkList()) {
|
||
ChordRest* cr = toChordRest(eee);
|
||
if (cr->score() == eee->score() && cr->staffIdx() == ls->staffIdx()) {
|
||
ls->setTrack2(cr->track());
|
||
if (ls->type() == ElementType::SLUR)
|
||
ls->setEndElement(cr);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else
|
||
qDebug("ChordRest::readAddConnector(): Slur end is neither start nor end");
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// setSmall
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::setSmall(bool val)
|
||
{
|
||
_small = val;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// undoSetSmall
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::undoSetSmall(bool val)
|
||
{
|
||
undoChangeProperty(Pid::SMALL, val);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// drop
|
||
//---------------------------------------------------------
|
||
|
||
Element* ChordRest::drop(EditData& data)
|
||
{
|
||
Element* e = data.dropElement;
|
||
Measure* m = measure();
|
||
bool fromPalette = (e->track() == -1);
|
||
switch (e->type()) {
|
||
case ElementType::BREATH:
|
||
{
|
||
Breath* b = toBreath(e);
|
||
b->setPos(QPointF());
|
||
int track = staffIdx() * VOICES;
|
||
b->setTrack(track);
|
||
|
||
// find start tick of next note in staff
|
||
#if 0
|
||
int bt = tick() + actualTicks(); // this could make sense if we allowed breath marks in voice > 1
|
||
#else
|
||
Segment* next = segment()->nextCR(track);
|
||
int bt = next ? next->tick() : score()->lastSegment()->tick();
|
||
#endif
|
||
|
||
// TODO: insert automatically in all staves?
|
||
|
||
Segment* seg = m->undoGetSegment(SegmentType::Breath, bt);
|
||
b->setParent(seg);
|
||
score()->undoAddElement(b);
|
||
}
|
||
return e;
|
||
|
||
case ElementType::BAR_LINE:
|
||
if (data.control())
|
||
score()->splitMeasure(segment());
|
||
else {
|
||
BarLine* bl = toBarLine(e);
|
||
bl->setPos(QPointF());
|
||
bl->setTrack(staffIdx() * VOICES);
|
||
bl->setGenerated(false);
|
||
|
||
if (tick() == m->tick())
|
||
return m->drop(data);
|
||
|
||
BarLine* obl = 0;
|
||
for (Staff* st : staff()->staffList()) {
|
||
Score* score = st->score();
|
||
Measure* measure = score->tick2measure(m->tick());
|
||
Segment* seg = measure->undoGetSegmentR(SegmentType::BarLine, rtick());
|
||
BarLine* l;
|
||
if (obl == 0)
|
||
obl = l = bl->clone();
|
||
else
|
||
l = toBarLine(obl->linkedClone());
|
||
l->setTrack(st->idx() * VOICES);
|
||
l->setScore(score);
|
||
l->setParent(seg);
|
||
score->undoAddElement(l);
|
||
l->layout();
|
||
}
|
||
}
|
||
delete e;
|
||
return 0;
|
||
|
||
case ElementType::CLEF:
|
||
score()->cmdInsertClef(toClef(e), this);
|
||
break;
|
||
|
||
case ElementType::TIMESIG:
|
||
if (measure()->system()) {
|
||
EditData ndd = data;
|
||
// adding from palette sets pos, but normal paste does not
|
||
if (!fromPalette)
|
||
ndd.pos = pagePos();
|
||
// convert page-relative pos to score-relative
|
||
ndd.pos += measure()->system()->page()->pos();
|
||
return measure()->drop(ndd);
|
||
}
|
||
else {
|
||
delete e;
|
||
return 0;
|
||
}
|
||
|
||
case ElementType::FERMATA:
|
||
for (Element* el: segment()->annotations())
|
||
if (el->isFermata() && (el->track() == track())) {
|
||
if (el->subtype() == e->subtype()) {
|
||
delete e;
|
||
return el;
|
||
}
|
||
else {
|
||
if (el->placeBelow())
|
||
e->setPlacement(Placement::BELOW);
|
||
e->setTrack(track());
|
||
e->setParent(segment());
|
||
score()->undoChangeElement(el, e);
|
||
return e;
|
||
}
|
||
}
|
||
// fall through
|
||
case ElementType::TEMPO_TEXT:
|
||
case ElementType::DYNAMIC:
|
||
case ElementType::FRET_DIAGRAM:
|
||
case ElementType::TREMOLOBAR:
|
||
case ElementType::SYMBOL:
|
||
e->setTrack(track());
|
||
e->setParent(segment());
|
||
score()->undoAddElement(e);
|
||
return e;
|
||
|
||
case ElementType::NOTE: {
|
||
Note* note = toNote(e);
|
||
NoteVal nval;
|
||
nval.pitch = note->pitch();
|
||
nval.tpc1 = note->tpc1();
|
||
nval.headGroup = note->headGroup();
|
||
nval.fret = note->fret();
|
||
nval.string = note->string();
|
||
score()->setNoteRest(segment(), track(), nval, duration(), Direction::AUTO);
|
||
delete e;
|
||
}
|
||
break;
|
||
|
||
case ElementType::HARMONY:
|
||
{
|
||
// transpose
|
||
Harmony* harmony = toHarmony(e);
|
||
Interval interval = staff()->part()->instrument(tick())->transpose();
|
||
if (!score()->styleB(Sid::concertPitch) && !interval.isZero()) {
|
||
interval.flip();
|
||
int rootTpc = transposeTpc(harmony->rootTpc(), interval, true);
|
||
int baseTpc = transposeTpc(harmony->baseTpc(), interval, true);
|
||
score()->undoTransposeHarmony(harmony, rootTpc, baseTpc);
|
||
}
|
||
// render
|
||
harmony->render();
|
||
}
|
||
// fall through
|
||
case ElementType::TEXT:
|
||
case ElementType::STAFF_TEXT:
|
||
case ElementType::SYSTEM_TEXT:
|
||
case ElementType::STAFF_STATE:
|
||
case ElementType::INSTRUMENT_CHANGE:
|
||
if (e->isInstrumentChange() && part()->instruments()->find(tick()) != part()->instruments()->end()) {
|
||
qDebug()<<"InstrumentChange already exists at tick = "<<tick();
|
||
delete e;
|
||
return 0;
|
||
}
|
||
// fall through
|
||
|
||
case ElementType::REHEARSAL_MARK:
|
||
{
|
||
e->setParent(segment());
|
||
e->setTrack((track() / VOICES) * VOICES);
|
||
if (e->isRehearsalMark()) {
|
||
RehearsalMark* r = toRehearsalMark(e);
|
||
if (fromPalette)
|
||
r->setXmlText(score()->createRehearsalMarkText(r));
|
||
}
|
||
score()->undoAddElement(e);
|
||
return e;
|
||
}
|
||
case ElementType::FIGURED_BASS:
|
||
{
|
||
bool bNew;
|
||
FiguredBass * fb = toFiguredBass(e);
|
||
fb->setParent( segment() );
|
||
fb->setTrack( (track() / VOICES) * VOICES );
|
||
fb->setTicks( duration().ticks() );
|
||
fb->setOnNote(true);
|
||
FiguredBass::addFiguredBassToSegment(segment(),
|
||
fb->track(), fb->ticks(), &bNew);
|
||
if (bNew)
|
||
score()->undoAddElement(e);
|
||
return e;
|
||
}
|
||
|
||
case ElementType::IMAGE:
|
||
e->setParent(segment());
|
||
score()->undoAddElement(e);
|
||
return e;
|
||
|
||
case ElementType::ICON:
|
||
{
|
||
switch (toIcon(e)->iconType()) {
|
||
case IconType::SBEAM:
|
||
undoChangeProperty(Pid::BEAM_MODE, int(Beam::Mode::BEGIN));
|
||
break;
|
||
case IconType::MBEAM:
|
||
undoChangeProperty(Pid::BEAM_MODE, int(Beam::Mode::MID));
|
||
break;
|
||
case IconType::NBEAM:
|
||
undoChangeProperty(Pid::BEAM_MODE, int(Beam::Mode::NONE));
|
||
break;
|
||
case IconType::BEAM32:
|
||
undoChangeProperty(Pid::BEAM_MODE, int(Beam::Mode::BEGIN32));
|
||
break;
|
||
case IconType::BEAM64:
|
||
undoChangeProperty(Pid::BEAM_MODE, int(Beam::Mode::BEGIN64));
|
||
break;
|
||
case IconType::AUTOBEAM:
|
||
undoChangeProperty(Pid::BEAM_MODE, int(Beam::Mode::AUTO));
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
delete e;
|
||
break;
|
||
|
||
case ElementType::KEYSIG:
|
||
{
|
||
KeySig* ks = toKeySig(e);
|
||
KeySigEvent k = ks->keySigEvent();
|
||
delete ks;
|
||
|
||
// apply only to this stave
|
||
score()->undoChangeKeySig(staff(), tick(), k);
|
||
}
|
||
break;
|
||
|
||
default:
|
||
qDebug("cannot drop %s", e->name());
|
||
delete e;
|
||
return 0;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// setBeam
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::setBeam(Beam* b)
|
||
{
|
||
_beam = b;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// setDurationType
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::setDurationType(TDuration::DurationType t)
|
||
{
|
||
_durationType.setType(t);
|
||
_crossMeasure = CrossMeasure::UNKNOWN;
|
||
}
|
||
|
||
void ChordRest::setDurationType(const QString& s)
|
||
{
|
||
_durationType.setType(s);
|
||
_crossMeasure = CrossMeasure::UNKNOWN;
|
||
}
|
||
|
||
void ChordRest::setDurationType(int ticks)
|
||
{
|
||
_durationType.setVal(ticks);
|
||
_crossMeasure = CrossMeasure::UNKNOWN;
|
||
}
|
||
|
||
void ChordRest::setDurationType(TDuration v)
|
||
{
|
||
_durationType = v;
|
||
_crossMeasure = CrossMeasure::UNKNOWN;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// durationUserName
|
||
//---------------------------------------------------------
|
||
|
||
QString ChordRest::durationUserName() const
|
||
{
|
||
QString tupletType = "";
|
||
if (tuplet()) {
|
||
switch (tuplet()->ratio().numerator()) {
|
||
case 2:
|
||
tupletType = QObject::tr("Duplet");
|
||
break;
|
||
case 3:
|
||
tupletType = QObject::tr("Triplet");
|
||
break;
|
||
case 4:
|
||
tupletType = QObject::tr("Quadruplet");
|
||
break;
|
||
case 5:
|
||
tupletType = QObject::tr("Quintuplet");
|
||
break;
|
||
case 6:
|
||
tupletType = QObject::tr("Sextuplet");
|
||
break;
|
||
case 7:
|
||
tupletType = QObject::tr("Septuplet");
|
||
break;
|
||
case 8:
|
||
tupletType = QObject::tr("Octuplet");
|
||
break;
|
||
case 9:
|
||
tupletType = QObject::tr("Nonuplet");
|
||
break;
|
||
default:
|
||
tupletType = QObject::tr("Custom tuplet");
|
||
}
|
||
}
|
||
QString dotString = "";
|
||
if(!tupletType.isEmpty())
|
||
dotString += " ";
|
||
|
||
switch (dots()) {
|
||
case 1:
|
||
dotString += QObject::tr("Dotted %1").arg(durationType().durationTypeUserName()).trimmed();
|
||
break;
|
||
case 2:
|
||
dotString += QObject::tr("Double dotted %1").arg(durationType().durationTypeUserName()).trimmed();
|
||
break;
|
||
case 3:
|
||
dotString += QObject::tr("Triple dotted %1").arg(durationType().durationTypeUserName()).trimmed();
|
||
break;
|
||
case 4:
|
||
dotString += QObject::tr("Quadruple dotted %1").arg(durationType().durationTypeUserName()).trimmed();
|
||
break;
|
||
default:
|
||
dotString += durationType().durationTypeUserName();
|
||
}
|
||
return QString("%1%2").arg(tupletType).arg(dotString);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// add
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::add(Element* e)
|
||
{
|
||
e->setParent(this);
|
||
e->setTrack(track());
|
||
switch (e->type()) {
|
||
case ElementType::ARTICULATION: // for backward compatibility
|
||
qDebug("ChordRest::add: unknown element %s", e->name());
|
||
break;
|
||
case ElementType::LYRICS:
|
||
_lyrics.push_back(toLyrics(e));
|
||
break;
|
||
default:
|
||
qFatal("ChordRest::add: unknown element %s", e->name());
|
||
break;
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// remove
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::remove(Element* e)
|
||
{
|
||
switch (e->type()) {
|
||
case ElementType::LYRICS: {
|
||
toLyrics(e)->removeFromScore();
|
||
auto i = std::find(_lyrics.begin(), _lyrics.end(), toLyrics(e));
|
||
if (i != _lyrics.end())
|
||
_lyrics.erase(i);
|
||
else
|
||
qDebug("ChordRest::remove: %s %p not found", e->name(), e);
|
||
}
|
||
break;
|
||
default:
|
||
qFatal("ChordRest::remove: unknown element <%s>", e->name());
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// removeDeleteBeam
|
||
// beamed - the chordrest is beamed (will get a (new) beam)
|
||
// remove ChordRest from beam
|
||
// delete beam if empty
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::removeDeleteBeam(bool beamed)
|
||
{
|
||
if (_beam) {
|
||
Beam* b = _beam;
|
||
_beam->remove(this);
|
||
if (b->empty())
|
||
score()->undoRemoveElement(b);
|
||
}
|
||
if (!beamed && isChord())
|
||
toChord(this)->layoutStem();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// undoSetBeamMode
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::undoSetBeamMode(Beam::Mode mode)
|
||
{
|
||
undoChangeProperty(Pid::BEAM_MODE, int(mode));
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// getProperty
|
||
//---------------------------------------------------------
|
||
|
||
QVariant ChordRest::getProperty(Pid propertyId) const
|
||
{
|
||
switch (propertyId) {
|
||
case Pid::SMALL: return QVariant(small());
|
||
case Pid::BEAM_MODE: return int(beamMode());
|
||
case Pid::STAFF_MOVE: return staffMove();
|
||
case Pid::DURATION_TYPE: return QVariant::fromValue(actualDurationType());
|
||
default: return DurationElement::getProperty(propertyId);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// setProperty
|
||
//---------------------------------------------------------
|
||
|
||
bool ChordRest::setProperty(Pid propertyId, const QVariant& v)
|
||
{
|
||
switch (propertyId) {
|
||
case Pid::SMALL:
|
||
setSmall(v.toBool());
|
||
break;
|
||
case Pid::BEAM_MODE:
|
||
setBeamMode(Beam::Mode(v.toInt()));
|
||
break;
|
||
case Pid::STAFF_MOVE:
|
||
setStaffMove(v.toInt());
|
||
break;
|
||
case Pid::VISIBLE:
|
||
setVisible(v.toBool());
|
||
measure()->checkMultiVoices(staffIdx());
|
||
break;
|
||
case Pid::DURATION_TYPE:
|
||
setDurationType(v.value<TDuration>());
|
||
break;
|
||
default:
|
||
return DurationElement::setProperty(propertyId, v);
|
||
}
|
||
triggerLayout();
|
||
return true;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// propertyDefault
|
||
//---------------------------------------------------------
|
||
|
||
QVariant ChordRest::propertyDefault(Pid propertyId) const
|
||
{
|
||
switch (propertyId) {
|
||
case Pid::SMALL:
|
||
return false;
|
||
case Pid::BEAM_MODE:
|
||
return int(Beam::Mode::AUTO);
|
||
case Pid::STAFF_MOVE:
|
||
return 0;
|
||
default:
|
||
return DurationElement::propertyDefault(propertyId);
|
||
}
|
||
// Prevent unreachable code warning
|
||
// triggerLayout();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// isGrace
|
||
//---------------------------------------------------------
|
||
|
||
bool ChordRest::isGrace() const
|
||
{
|
||
return isChord() && toChord(this)->isGrace();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// isGraceBefore
|
||
//---------------------------------------------------------
|
||
|
||
bool ChordRest::isGraceBefore() const
|
||
{
|
||
return isChord()
|
||
&& (toChord(this)->noteType() & (
|
||
NoteType::ACCIACCATURA | NoteType::APPOGGIATURA | NoteType::GRACE4 | NoteType::GRACE16 | NoteType::GRACE32
|
||
));
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// isGraceAfter
|
||
//---------------------------------------------------------
|
||
|
||
bool ChordRest::isGraceAfter() const
|
||
{
|
||
return isChord()
|
||
&& (toChord(this)->noteType() & (NoteType::GRACE8_AFTER | NoteType::GRACE16_AFTER | NoteType::GRACE32_AFTER));
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// writeBeam
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::writeBeam(XmlWriter& xml) const
|
||
{
|
||
Beam* b = beam();
|
||
if (b && b->elements().front() == this && (MScore::testMode || !b->generated())) {
|
||
b->write(xml);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// nextSegmentAfterCR
|
||
// returns first segment at tick CR->tick + CR->actualTicks
|
||
// of given types
|
||
//---------------------------------------------------------
|
||
|
||
Segment* ChordRest::nextSegmentAfterCR(SegmentType types) const
|
||
{
|
||
for (Segment* s = segment()->next1MM(types); s; s = s->next1MM(types)) {
|
||
// chordrest ends at afrac+actualFraction
|
||
// we return the segment at or after the end of the chordrest
|
||
if (s->afrac() >= afrac() + actualFraction())
|
||
return s;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// setTrack
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::setTrack(int val)
|
||
{
|
||
Element::setTrack(val);
|
||
processSiblings([val] (Element* e) { e->setTrack(val); } );
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// setScore
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::setScore(Score* s)
|
||
{
|
||
Element::setScore(s);
|
||
processSiblings([s] (Element* e) { e->setScore(s); } );
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// processSiblings
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::processSiblings(std::function<void(Element*)> func)
|
||
{
|
||
if (_beam)
|
||
func(_beam);
|
||
if (_tabDur)
|
||
func(_tabDur);
|
||
for (Lyrics* l : _lyrics)
|
||
func(l);
|
||
if (tuplet())
|
||
func(tuplet());
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// nextArticulationOrLyric
|
||
//---------------------------------------------------------
|
||
|
||
Element* ChordRest::nextArticulationOrLyric(Element* /*e*/)
|
||
{
|
||
#if 0 // TODO:fermata
|
||
auto i = std::find(_articulations.begin(), _articulations.end(), e);
|
||
if (i != _articulations.end()) {
|
||
if (i != _articulations.end()-1) {
|
||
return *(i+1);
|
||
}
|
||
else {
|
||
if (!_lyrics.empty())
|
||
return _lyrics[0];
|
||
else
|
||
return nullptr;
|
||
}
|
||
}
|
||
else {
|
||
auto i = std::find(_lyrics.begin(), _lyrics.end(), e);
|
||
if (i != _lyrics.end()) {
|
||
if (i != _lyrics.end()-1)
|
||
return *(i+1);
|
||
}
|
||
}
|
||
#endif
|
||
return 0;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// prevArticulationOrLyric
|
||
//---------------------------------------------------------
|
||
|
||
Element* ChordRest::prevArticulationOrLyric(Element* /*e*/)
|
||
{
|
||
#if 0 // TODO:fermata
|
||
auto i = std::find(_lyrics.begin(), _lyrics.end(), e);
|
||
if (i != _lyrics.end()) {
|
||
if (i != _lyrics.begin()) {
|
||
return *(i-1);
|
||
}
|
||
else {
|
||
if (!_articulations.empty())
|
||
return _articulations.back();
|
||
else
|
||
return nullptr;
|
||
}
|
||
}
|
||
else {
|
||
auto i = std::find(_articulations.begin(), _articulations.end(), e);
|
||
if (i != _articulations.end()) {
|
||
if (i != _articulations.begin())
|
||
return *(i-1);
|
||
}
|
||
}
|
||
#endif
|
||
return 0;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// nextElement
|
||
//---------------------------------------------------------
|
||
|
||
Element* ChordRest::nextElement()
|
||
{
|
||
Element* e = score()->selection().element();
|
||
#if 0 // TODO:fermata
|
||
if (!e && !score()->selection().elements().isEmpty())
|
||
e = score()->selection().elements().first();
|
||
switch (e->type()) {
|
||
case ElementType::ARTICULATION:
|
||
case ElementType::LYRICS: {
|
||
Element* next = nextArticulationOrLyric(e);
|
||
if (next)
|
||
return next;
|
||
else
|
||
break;
|
||
}
|
||
default: {
|
||
if (!_articulations.empty())
|
||
return _articulations[0];
|
||
else if (!_lyrics.empty())
|
||
return _lyrics[0];
|
||
else
|
||
break;
|
||
}
|
||
}
|
||
#endif
|
||
int staffId = e->staffIdx();
|
||
return segment()->nextElement(staffId);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// prevElement
|
||
//---------------------------------------------------------
|
||
|
||
Element* ChordRest::prevElement()
|
||
{
|
||
Element* e = score()->selection().element();
|
||
#if 0 // TODO:fermata
|
||
if (!e && !score()->selection().elements().isEmpty())
|
||
e = score()->selection().elements().last();
|
||
switch (e->type()) {
|
||
case ElementType::ARTICULATION:
|
||
case ElementType::LYRICS: {
|
||
Element* prev = prevArticulationOrLyric(e);
|
||
if (prev)
|
||
return prev;
|
||
else {
|
||
if (isChord())
|
||
return toChord(this)->lastElementBeforeSegment();
|
||
}
|
||
// fall through
|
||
}
|
||
default: {
|
||
break;
|
||
}
|
||
}
|
||
#endif
|
||
int staffId = e->staffIdx();
|
||
return segment()->prevElement(staffId);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// lastElementBeforeSegment
|
||
//---------------------------------------------------------
|
||
|
||
Element* ChordRest::lastElementBeforeSegment()
|
||
{
|
||
if (!_lyrics.empty())
|
||
return _lyrics.back();
|
||
// else if (!_articulations.empty()) { // TODO:fermata
|
||
// return _articulations.back();
|
||
// }
|
||
else
|
||
return 0;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// nextSegmentElement
|
||
//---------------------------------------------------------
|
||
|
||
Element* ChordRest::nextSegmentElement()
|
||
{
|
||
return segment()->firstInNextSegments(staffIdx());
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// prevSegmentElement
|
||
//---------------------------------------------------------
|
||
|
||
Element* ChordRest::prevSegmentElement()
|
||
{
|
||
return segment()->lastInPrevSegments(staffIdx());
|
||
}
|
||
|
||
QString ChordRest::accessibleExtraInfo() const
|
||
{
|
||
QString rez = "";
|
||
#if 0 // TODO:fermata
|
||
for (Articulation* a : articulations()) {
|
||
if (!score()->selectionFilter().canSelect(a))
|
||
continue;
|
||
rez = QString("%1 %2").arg(rez).arg(a->screenReaderInfo());
|
||
}
|
||
#endif
|
||
for (Element* l : lyrics()) {
|
||
if (!score()->selectionFilter().canSelect(l))
|
||
continue;
|
||
rez = QString("%1 %2").arg(rez).arg(l->screenReaderInfo());
|
||
}
|
||
|
||
if (segment()) {
|
||
for (Element* e : segment()->annotations()) {
|
||
if (!score()->selectionFilter().canSelect(e))
|
||
continue;
|
||
if (e->staffIdx() == staffIdx() )
|
||
rez = QString("%1 %2").arg(rez).arg(e->screenReaderInfo());
|
||
}
|
||
|
||
SpannerMap& smap = score()->spannerMap();
|
||
auto spanners = smap.findOverlapping(tick(), tick());
|
||
for (auto interval : spanners) {
|
||
Spanner* s = interval.value;
|
||
if (!score()->selectionFilter().canSelect(s))
|
||
continue;
|
||
if (s->type() == ElementType::VOLTA || //voltas are added for barlines
|
||
s->type() == ElementType::TIE ) //ties are added in notes
|
||
continue;
|
||
|
||
Segment* seg = 0;
|
||
if (s->type() == ElementType::SLUR) {
|
||
if (s->tick() == tick() && s->track() == track())
|
||
rez = QObject::tr("%1 Start of %2").arg(rez).arg(s->screenReaderInfo());
|
||
if (s->tick2() == tick() && s->track2() == track())
|
||
rez = QObject::tr("%1 End of %2").arg(rez).arg(s->screenReaderInfo());
|
||
}
|
||
else {
|
||
if (s->tick() == tick() && s->staffIdx() == staffIdx())
|
||
rez = QObject::tr("%1 Start of %2").arg(rez).arg(s->screenReaderInfo());
|
||
seg = segment()->next1MM(SegmentType::ChordRest);
|
||
if (!seg)
|
||
continue;
|
||
if (s->tick2() == seg->tick() && s->staffIdx() == staffIdx())
|
||
rez = QObject::tr("%1 End of %2").arg(rez).arg(s->screenReaderInfo());
|
||
}
|
||
}
|
||
}
|
||
return rez;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// shape
|
||
//---------------------------------------------------------
|
||
|
||
Shape ChordRest::shape() const
|
||
{
|
||
Shape shape;
|
||
qreal x1 = 1000000.0;
|
||
qreal x2 = -1000000.0;
|
||
bool adjustWidth = false;
|
||
for (Lyrics* l : _lyrics) {
|
||
if (!l->visible())
|
||
continue;
|
||
static const qreal margin = spatium() * .5;
|
||
// for horizontal spacing we only need the lyrics width:
|
||
x1 = qMin(x1, l->bbox().x() - margin + l->pos().x());
|
||
x2 = qMax(x2, l->bbox().x() + l->bbox().width() + margin + l->pos().x());
|
||
if (l->ticks() == Lyrics::TEMP_MELISMA_TICKS)
|
||
x2 += spatium();
|
||
adjustWidth = true;
|
||
}
|
||
|
||
for (Element* e : segment()->annotations()) {
|
||
if (e->isHarmony() && e->staffIdx() == staffIdx() && e->visible()) {
|
||
e->layout();
|
||
const qreal margin = styleP(Sid::minHarmonyDistance) * 0.5;
|
||
x1 = qMin(x1, e->bbox().x() - margin + e->pos().x());
|
||
x2 = qMax(x2, e->bbox().x() + e->bbox().width() + margin + e->pos().x());
|
||
adjustWidth = true;
|
||
}
|
||
}
|
||
if (adjustWidth)
|
||
shape.add(QRectF(x1, 0.0, x2-x1, 0.0));
|
||
|
||
return shape;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// lyrics
|
||
//---------------------------------------------------------
|
||
|
||
Lyrics* ChordRest::lyrics(int no, Placement p) const
|
||
{
|
||
for (Lyrics* l : _lyrics) {
|
||
if (l->placement() == p && l->no() == no)
|
||
return l;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// lastVerse
|
||
// return last verse number (starting from 0)
|
||
// return -1 if there are no lyrics;
|
||
//---------------------------------------------------------
|
||
|
||
int ChordRest::lastVerse(Placement p) const
|
||
{
|
||
int lastVerse = -1;
|
||
|
||
for (Lyrics* l : _lyrics) {
|
||
if (l->placement() == p && l->no() > lastVerse)
|
||
lastVerse = l->no();
|
||
}
|
||
|
||
return lastVerse;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// removeMarkings
|
||
// - this is normally called after cloning a chord to tie a note over the barline
|
||
// - there is no special undo handling; the assumption is that undo will simply remove the cloned chord
|
||
// - two note tremolos are converted into simple notes
|
||
// - single note tremolos are optionally retained
|
||
//---------------------------------------------------------
|
||
|
||
void ChordRest::removeMarkings(bool /* keepTremolo */)
|
||
{
|
||
qDeleteAll(el());
|
||
el().clear();
|
||
qDeleteAll(lyrics());
|
||
lyrics().clear();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// isBefore
|
||
//---------------------------------------------------------
|
||
|
||
bool ChordRest::isBefore(ChordRest* o)
|
||
{
|
||
if (!o)
|
||
return true;
|
||
if (this == o)
|
||
return true;
|
||
int otick = o->tick();
|
||
int t = tick();
|
||
if (t == otick) { // At least one of the chord is a grace, order the grace notes
|
||
bool oGraceAfter = o->isGraceAfter();
|
||
bool graceAfter = isGraceAfter();
|
||
bool oGrace = o->isGrace();
|
||
bool grace = isGrace();
|
||
// normal note are initialized at graceIndex 0 and graceIndex is 0 based
|
||
int oGraceIndex = oGrace ? toChord(o)->graceIndex() + 1 : 0;
|
||
int graceIndex = grace ? toChord(this)->graceIndex() + 1 : 0;
|
||
if (oGrace)
|
||
oGraceIndex = toChord(o->parent())->graceNotes().size() - oGraceIndex;
|
||
if (grace)
|
||
graceIndex = toChord(parent())->graceNotes().size() - graceIndex;
|
||
otick = otick + (oGraceAfter ? 1 : -1) * oGraceIndex;
|
||
t = t + (graceAfter ? 1 : -1) * graceIndex;
|
||
}
|
||
return t < otick;
|
||
}
|
||
|
||
}
|
||
|