//============================================================================= // 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; if (_beam && _beam->contains(this)) delete _beam; // Beam destructor removes references to the deleted object } //--------------------------------------------------------- // 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 (!ticks().isZero() && (!actualDurationType().fraction().isValid() || (actualDurationType().fraction() != ticks()))) { xml.tag("duration", ticks()); //xml.tagE("duration z=\"%d\" n=\"%d\"", ticks().numerator(), ticks().denominator()); } for (Lyrics* lyrics : _lyrics) lyrics->write(xml); if (!isGrace()) { Fraction t(globalTicks()); if (staff()) t /= staff()->timeStretch(xml.curTick()); xml.incCurTick(t); } 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 ticks().numerator() != 0 && // rest durations are initialized to full measure duration when // created upon reading the tag (see Measure::read() ) // so a V_WHOLE rest in a measure of 4/4 or less => V_MEASURE (actualDurationType()==TDuration::DurationType::V_WHOLE && ticks() <= 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 setTicks(actualDurationType().fraction()); } else { if (score()->mscVersion() <= 114) { SigEvent event = score()->sigmap()->timesig(e.tick()); setTicks(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") setTicks(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); setTicks(Fraction::fromTicks(i)); } else { Fraction f = Fraction::fromTicks(i); setTicks(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(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); Fraction 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: e->setPlacement(track() & 1 ? Placement::BELOW : Placement::ABOVE); for (Element* el: segment()->annotations()) if (el->isFermata() && (el->track() == track())) { if (el->subtype() == e->subtype()) { delete e; return el; } else { e->setPlacement(el->placement()); 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, ticks(), 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().ticks()) != part()->instruments()->end()) { qDebug()<<"InstrumentChange already exists at tick = "<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(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(const Fraction& ticks) { _durationType.setVal(ticks.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()); 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 { Fraction end = tick() + actualTicks(); 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 // Segment::afrac() is based on ticks; use DurationElement::afrac() if possible Element* e = s; if (s->isChordRestType()) { // Find the first non-NULL element in the segment for (Element* ee : s->elist()) { if (ee) { e = ee; break; } } } if (e->tick() >= end) 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 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().ticks(), tick().ticks()); 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 || !l->visible() || !l->autoplace()) continue; qreal lmargin = styleP(Sid::lyricsMinDistance) * .5; qreal rmargin = lmargin; Lyrics::Syllabic syl = l->syllabic(); if ((syl == Lyrics::Syllabic::BEGIN || syl == Lyrics::Syllabic::MIDDLE) && score()->styleB(Sid::lyricsDashForce)) rmargin = qMax(rmargin, styleP(Sid::lyricsDashMinLength)); // for horizontal spacing we only need the lyrics width: x1 = qMin(x1, l->bbox().x() - lmargin + l->pos().x()); x2 = qMax(x2, l->bbox().x() + l->bbox().width() + rmargin + l->pos().x()); if (l->ticks() == Fraction::fromTicks(Lyrics::TEMP_MELISMA_TICKS)) x2 += spatium(); adjustWidth = true; } if (adjustWidth) shape.addHorizontalSpacing(Shape::SPACING_LYRICS, x1, x2); } { qreal x1 = 1000000.0; qreal x2 = -1000000.0; bool adjustWidth = false; for (Element* e : segment()->annotations()) { if (!e || !e->visible() || !e->autoplace()) continue; if (e->isHarmony() && e->staffIdx() == staffIdx()) { Harmony* h = toHarmony(e); // calculate bbox only (do not reset position) h->layout1(); 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.addHorizontalSpacing(Shape::SPACING_HARMONY, x1, x2); } 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 || this == o) return true; int otick = o->tick().ticks(); int t = tick().ticks(); 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; } //--------------------------------------------------------- // undoAddAnnotation //--------------------------------------------------------- void ChordRest::undoAddAnnotation(Element* a) { Segment* seg = segment(); Measure* m = measure(); if (m && m->isMMRest()) seg = m->mmRestFirst()->findSegmentR(SegmentType::ChordRest, Fraction(0,1)); a->setTrack(a->systemFlag() ? 0 : track()); a->setParent(seg); score()->undoAddElement(a); } }