diff --git a/src/engraving/api/v1/apitypes.h b/src/engraving/api/v1/apitypes.h index cc00fbf87e2d6..3282a4e6e4909 100644 --- a/src/engraving/api/v1/apitypes.h +++ b/src/engraving/api/v1/apitypes.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_APIV1_APITYPES_H -#define MU_ENGRAVING_APIV1_APITYPES_H +#pragma once #include @@ -227,6 +226,8 @@ enum class ElementType { TIE_SEGMENT = int(mu::engraving::ElementType::TIE_SEGMENT), LAISSEZ_VIB_SEGMENT = int(mu::engraving::ElementType::LAISSEZ_VIB_SEGMENT), LAISSEZ_VIB = int(mu::engraving::ElementType::LAISSEZ_VIB), + PARTIAL_TIE_SEGMENT = int(mu::engraving::ElementType::PARTIAL_TIE_SEGMENT), + PARTIAL_TIE = int(mu::engraving::ElementType::PARTIAL_TIE), BAR_LINE = int(mu::engraving::ElementType::BAR_LINE), STAFF_LINES = int(mu::engraving::ElementType::STAFF_LINES), SYSTEM_DIVIDER = int(mu::engraving::ElementType::SYSTEM_DIVIDER), @@ -3599,5 +3600,3 @@ Q_DECLARE_METATYPE(mu::engraving::apiv1::enums::Tid); Q_DECLARE_METATYPE(mu::engraving::apiv1::enums::Syllabic); Q_DECLARE_METATYPE(mu::engraving::apiv1::enums::Anchor); Q_DECLARE_METATYPE(mu::engraving::apiv1::enums::SymId); - -#endif // MU_ENGRAVING_APIV1_APITYPES_H diff --git a/src/engraving/compat/midi/compatmidirender.cpp b/src/engraving/compat/midi/compatmidirender.cpp index e2515756a589e..35e54a6e61889 100644 --- a/src/engraving/compat/midi/compatmidirender.cpp +++ b/src/engraving/compat/midi/compatmidirender.cpp @@ -1080,7 +1080,7 @@ std::set CompatMidiRender::getNotesIndexesToRender(Chord* chord) } auto noteShouldBeRendered = [](Note* n) { - while (n->tieBack() && n != n->tieBack()->startNote()) { + while (n->tieBackNonPartial() && n != n->tieBack()->startNote()) { n = n->tieBack()->startNote(); if (findFirstTrill(n->chord())) { // The previous tied note probably has events for this note too. diff --git a/src/engraving/dom/barline.cpp b/src/engraving/dom/barline.cpp index f390d4258a2ae..290911a35bb4b 100644 --- a/src/engraving/dom/barline.cpp +++ b/src/engraving/dom/barline.cpp @@ -103,6 +103,8 @@ static void undoChangeBarLineType(BarLine* bl, BarLineType barType, bool allStav // createMMRest will then set for the mmrest directly Measure* m2 = m->isMMRest() ? m->mmRestLast() : m; + BarLineType prevBarType = bl->barLineType(); + switch (barType) { case BarLineType::END: case BarLineType::NORMAL: diff --git a/src/engraving/dom/beam.cpp b/src/engraving/dom/beam.cpp index fdf7583854cf9..14dab2822481f 100644 --- a/src/engraving/dom/beam.cpp +++ b/src/engraving/dom/beam.cpp @@ -481,15 +481,6 @@ void Beam::startEdit(EditData& ed) initBeamEditData(ed); } -//--------------------------------------------------------- -// endEdit -//--------------------------------------------------------- - -void Beam::endEdit(EditData& ed) -{ - EngravingItem::endEdit(ed); -} - //--------------------------------------------------------- // triggerLayout //--------------------------------------------------------- diff --git a/src/engraving/dom/beam.h b/src/engraving/dom/beam.h index d9e9d30dd81ec..5e21b6f2d2350 100644 --- a/src/engraving/dom/beam.h +++ b/src/engraving/dom/beam.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_BEAM_H -#define MU_ENGRAVING_BEAM_H +#pragma once #include "beambase.h" #include "engravingitem.h" @@ -64,7 +63,6 @@ class Beam final : public BeamBase bool isEditable() const override { return true; } void startEdit(EditData&) override; - void endEdit(EditData&) override; void editDrag(EditData&) override; Fraction tick() const override; @@ -226,4 +224,3 @@ class Beam final : public BeamBase std::vector m_tremAnchors; }; } // namespace mu::engraving -#endif diff --git a/src/engraving/dom/chord.cpp b/src/engraving/dom/chord.cpp index fcc5fff26a8a6..1fb3bca56b9df 100644 --- a/src/engraving/dom/chord.cpp +++ b/src/engraving/dom/chord.cpp @@ -1688,7 +1688,7 @@ void Chord::cmdUpdateNotes(AccidentalState* as, staff_idx_t staffIdx) if (vStaffIdx() == staffIdx) { std::vector lnotes(notes()); // we need a copy! for (Note* note : lnotes) { - if (note->tieBack() && note->tpc() == note->tieBack()->startNote()->tpc()) { + if (note->tieBackNonPartial() && note->tpc() == note->tieBack()->startNote()->tpc()) { // same pitch if (note->accidental() && note->accidental()->role() == AccidentalRole::AUTO) { // not courtesy @@ -2635,7 +2635,7 @@ static bool noteIsBefore(const Note* n1, const Note* n2) } if (n1->tieBack()) { - if (n2->tieBack()) { + if (n2->tieBack() && !n2->incomingPartialTie()) { const Note* sn1 = n1->tieBack()->startNote(); const Note* sn2 = n2->tieBack()->startNote(); if (sn1->chord() == sn2->chord()) { @@ -2870,6 +2870,7 @@ EngravingItem* Chord::nextElement() case ElementType::GLISSANDO_SEGMENT: case ElementType::NOTELINE_SEGMENT: case ElementType::LAISSEZ_VIB_SEGMENT: + case ElementType::PARTIAL_TIE_SEGMENT: case ElementType::TIE_SEGMENT: { SpannerSegment* s = toSpannerSegment(e); Spanner* sp = s->spanner(); diff --git a/src/engraving/dom/cmd.cpp b/src/engraving/dom/cmd.cpp index eb2b30b16a695..76d8d1c26f855 100644 --- a/src/engraving/dom/cmd.cpp +++ b/src/engraving/dom/cmd.cpp @@ -330,16 +330,17 @@ void Score::startCmd(const TranslatableString& actionName) LOGD("===startCmd()"); } + if (undoStack()->hasActiveCommand()) { + LOGD("Score::startCmd(): cmd already active"); + return; + } + MScore::setError(MsError::MS_NO_ERROR); cmdState().reset(); // Start collecting low-level undo operations for a // user-visible undo action. - if (undoStack()->hasActiveCommand()) { - LOGD("Score::startCmd(): cmd already active"); - return; - } undoStack()->beginMacro(this, actionName); } @@ -1691,8 +1692,11 @@ void Score::changeCRlen(ChordRest* cr, const Fraction& dstF, bool fillWithRest) undoRemoveElement(c->tremoloTwoChord()); } for (Note* n : c->notes()) { - if (n->tieFor()) { - undoRemoveElement(n->tieFor()); + if (Tie* tie = n->tieFor()) { + if (tie->tieJumpPoints()) { + tie->undoRemoveTiesFromJumpPoints(); + } + undoRemoveElement(tie); } for (Spanner* sp : n->spannerFor()) { if (sp->isGlissando() || sp->isGuitarBend()) { @@ -3784,7 +3788,7 @@ void Score::cmdImplode() // see if we are tying in to this chord Chord* tied = 0; for (Note* n : dstChord->notes()) { - if (n->tieBack()) { + if (n->tieBackNonPartial()) { tied = n->tieBack()->startNote()->chord(); break; } diff --git a/src/engraving/dom/dom.cmake b/src/engraving/dom/dom.cmake index 06bc544d7083b..2b0e6475c06b7 100644 --- a/src/engraving/dom/dom.cmake +++ b/src/engraving/dom/dom.cmake @@ -223,6 +223,8 @@ set(DOM_SRC ${CMAKE_CURRENT_LIST_DIR}/ottava.h ${CMAKE_CURRENT_LIST_DIR}/page.cpp ${CMAKE_CURRENT_LIST_DIR}/page.h + ${CMAKE_CURRENT_LIST_DIR}/partialtie.cpp + ${CMAKE_CURRENT_LIST_DIR}/partialtie.h ${CMAKE_CURRENT_LIST_DIR}/palmmute.cpp ${CMAKE_CURRENT_LIST_DIR}/palmmute.h ${CMAKE_CURRENT_LIST_DIR}/part.cpp diff --git a/src/engraving/dom/edit.cpp b/src/engraving/dom/edit.cpp index b57e1b00aca48..5d73c4f61f3aa 100644 --- a/src/engraving/dom/edit.cpp +++ b/src/engraving/dom/edit.cpp @@ -23,6 +23,7 @@ #include #include +#include "dom/volta.h" #include "infrastructure/messagebox.h" #include "accidental.h" @@ -65,6 +66,7 @@ #include "ornament.h" #include "ottava.h" #include "part.h" +#include "partialtie.h" #include "range.h" #include "rehearsalmark.h" #include "rest.h" @@ -602,7 +604,7 @@ Note* Score::addNoteToTiedChord(Chord* chord, const NoteVal& noteVal, bool force if (referenceNote->chord()->findNote(noteVal.pitch)) { return nullptr; } - if (!referenceNote->tieBack()) { + if (!referenceNote->tieBack() || referenceNote->incomingPartialTie()) { break; } referenceNote = referenceNote->tieBack()->startNote(); @@ -1890,6 +1892,28 @@ std::vector Score::cmdTieNoteList(const Selection& selection, bool noteEn // cmdAddTie //--------------------------------------------------------- +static Tie* createAndAddTie(Note* startNote, Note* endNote) +{ + Score* score = startNote->score(); + Tie* tie = endNote ? Factory::createTie(startNote) : Factory::createPartialTie(startNote); + tie->setStartNote(startNote); + tie->setTrack(startNote->track()); + tie->setTick(startNote->chord()->segment()->tick()); + if (endNote) { + tie->setEndNote(endNote); + tie->setTicks(endNote->chord()->segment()->tick() - startNote->chord()->segment()->tick()); + } + score->undoAddElement(tie); + + tie->addTiesToJumpPoints(); + if (!tie->endNote() && tie->tieJumpPoints() && tie->tieJumpPoints()->empty()) { + score->undoRemoveElement(tie); + tie = nullptr; + } + + return tie; +} + void Score::cmdAddTie(bool addToChord) { const std::vector noteList = cmdTieNoteList(selection(), noteEntryMode()); @@ -1968,13 +1992,8 @@ void Score::cmdAddTie(bool addToChord) // tpc was set correctly already //n->setLine(note->line()); //n->setTpc(note->tpc()); - Tie* tie = Factory::createTie(this->dummy()); - tie->setStartNote(note); - tie->setEndNote(nnote); - tie->setTrack(note->track()); - tie->setTick(note->chord()->segment()->tick()); - tie->setTicks(nnote->chord()->segment()->tick() - note->chord()->segment()->tick()); - undoAddElement(tie); + createAndAddTie(note, nnote); + if (!addFlag || nnote->chord()->tick() >= lastAddedChord->tick() || nnote->chord()->isGrace()) { break; } else { @@ -1987,13 +2006,7 @@ void Score::cmdAddTie(bool addToChord) } else { Note* note2 = searchTieNote(note); if (note2) { - Tie* tie = Factory::createTie(this->dummy()); - tie->setStartNote(note); - tie->setEndNote(note2); - tie->setTrack(note->track()); - tie->setTick(note->chord()->segment()->tick()); - tie->setTicks(note2->chord()->segment()->tick() - note->chord()->segment()->tick()); - undoAddElement(tie); + createAndAddTie(note, note2); } } } @@ -2007,13 +2020,13 @@ void Score::cmdAddTie(bool addToChord) // cmdRemoveTie //--------------------------------------------------------- -void Score::cmdToggleTie() +Tie* Score::cmdToggleTie() { const std::vector noteList = cmdTieNoteList(selection(), noteEntryMode()); if (noteList.empty()) { LOGD("no notes selected"); - return; + return nullptr; } bool canAddTies = false; @@ -2039,31 +2052,44 @@ void Score::cmdToggleTie() startCmd(actionName); + Tie* tie = nullptr; + if (canAddTies) { for (size_t i = 0; i < notes; ++i) { Note* note2 = tieNoteList[i]; if (note2) { Note* note = noteList[i]; - - Tie* tie = Factory::createTie(this->dummy()); - tie->setStartNote(note); - tie->setEndNote(note2); - tie->setTrack(note->track()); - tie->setTick(note->chord()->segment()->tick()); - tie->setTicks(note2->chord()->segment()->tick() - note->chord()->segment()->tick()); - undoAddElement(tie); + tie = createAndAddTie(note, note2); } } } else { + bool shouldTieListSelection = noteList.size() == 2; for (Note* n : noteList) { - Tie* tie = n->tieFor(); + tie = n->tieFor(); if (tie) { undoRemoveElement(tie); + tie = nullptr; + shouldTieListSelection = false; + } else if (n->hasFollowingJumpItem()) { + // Create outgoing partial tie + tie = createAndAddTie(n, nullptr); + shouldTieListSelection = false; + } + } + + if (shouldTieListSelection) { + Note* startNote = noteList.at(0); + Note* endNote = noteList.at(1); + if (startNote->part() == endNote->part() && startNote->pitch() == endNote->pitch() + && startNote->unisonIndex() == endNote->unisonIndex() && startNote->tick() != endNote->tick()) { + tie = createAndAddTie(startNote, endNote); } } } endCmd(); + + return tie; } void Score::cmdToggleLaissezVib() @@ -2876,6 +2902,7 @@ void Score::deleteItem(EngravingItem* el) case ElementType::SLUR_SEGMENT: case ElementType::TIE_SEGMENT: case ElementType::LAISSEZ_VIB_SEGMENT: + case ElementType::PARTIAL_TIE_SEGMENT: case ElementType::LYRICSLINE_SEGMENT: case ElementType::PEDAL_SEGMENT: case ElementType::GLISSANDO_SEGMENT: @@ -2890,6 +2917,16 @@ void Score::deleteItem(EngravingItem* el) case ElementType::GUITAR_BEND_SEGMENT: { el = toSpannerSegment(el)->spanner(); + if (el->isTie()) { + Tie* tie = toTie(el); + if (tie->tieJumpPoints()) { + tie->undoRemoveTiesFromJumpPoints(); + } + if (tie->jumpPoint()) { + tie->updateStartTieOnRemoval(); + } + } + undoRemoveElement(el); } break; @@ -5971,6 +6008,7 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool || et == ElementType::BEND || (et == ElementType::CHORD && toChord(element)->isGrace()) || et == ElementType::LAISSEZ_VIB + || et == ElementType::PARTIAL_TIE ) { const EngravingItem* parent = element->parentItem(); const LinkedObjects* links = parent ? parent->links() : nullptr; @@ -7029,7 +7067,7 @@ void Score::undoRemoveMeasures(Measure* m1, Measure* m2, bool preserveTies, bool for (Note* n : c->notes()) { // Remove ties crossing measure range boundaries Tie* t = n->tieBack(); - if (t && (t->startNote()->chord()->tick() < startTick)) { + if (t && t->startNote() && (t->startNote()->chord()->tick() < startTick)) { if (preserveTies) { t->setEndNote(0); } else { @@ -7093,4 +7131,60 @@ void Score::undoChangeMeasureRepeatCount(Measure* m, int newCount, staff_idx_t s } } } + +void Score::doUndoRemoveStaleTieJumpPoints(Tie* tie) +{ + std::vector oldTies; + for (TieJumpPoint* jumpPoint : *tie->tieJumpPoints()) { + if (jumpPoint->followingNote()) { + continue; + } + oldTies.push_back(jumpPoint->endTie()); + } + + tie->updatePossibleJumpPoints(); + + for (Tie* oldTie : oldTies) { + auto findEndTie = [&oldTie](const TieJumpPoint* jumpPoint) { + return jumpPoint->endTie() == oldTie; + }; + + if (std::find_if((*tie->tieJumpPoints()).begin(), (*tie->tieJumpPoints()).end(), + findEndTie) != (*tie->tieJumpPoints()).end()) { + continue; + } + startCmd(TranslatableString("engraving", "Remove stale partial tie")); + undoRemoveElement(oldTie); + endCmd(); + // These changes should be merged with the change in repeat structure which caused the ties to become invalid + undoStack()->mergeCommands(undoStack()->currentIndex() - 2); + } +} + +void Score::undoRemoveStaleTieJumpPoints() +{ + size_t tracks = nstaves() * VOICES; + Measure* m = firstMeasure(); + if (!m) { + return; + } + + SegmentType st = SegmentType::ChordRest; + for (Segment* s = m->first(st); s; s = s->next1(st)) { + for (track_idx_t i = 0; i < tracks; ++i) { + EngravingItem* e = s->element(i); + if (e == 0 || !e->isChord()) { + continue; + } + Chord* c = toChord(e); + for (Note* n : c->notes()) { + if (!n->tieFor()) { + continue; + } + + doUndoRemoveStaleTieJumpPoints(n->tieFor()); + } + } + } +} } diff --git a/src/engraving/dom/engravingitem.cpp b/src/engraving/dom/engravingitem.cpp index ed991eddf1764..c3f5ea2543d14 100644 --- a/src/engraving/dom/engravingitem.cpp +++ b/src/engraving/dom/engravingitem.cpp @@ -2727,6 +2727,7 @@ Shape EngravingItem::LayoutData::shape(LD_ACCESS mode) const case ElementType::SLUR_SEGMENT: case ElementType::TIE_SEGMENT: case ElementType::LAISSEZ_VIB_SEGMENT: + case ElementType::PARTIAL_TIE_SEGMENT: return sh; case ElementType::CHORD: case ElementType::REST: diff --git a/src/engraving/dom/engravingobject.h b/src/engraving/dom/engravingobject.h index 1bd360b049408..b9d005be50753 100644 --- a/src/engraving/dom/engravingobject.h +++ b/src/engraving/dom/engravingobject.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_OBJECT_H -#define MU_ENGRAVING_OBJECT_H +#pragma once #include "global/allocator.h" #include "types/string.h" @@ -127,6 +126,8 @@ class Page; class PalmMute; class PalmMuteSegment; class Part; +class PartialTie; +class PartialTieSegment; class Pedal; class PedalSegment; class PickScrape; @@ -369,6 +370,8 @@ class EngravingObject CONVERT(SlurSegment, SLUR_SEGMENT) CONVERT(LaissezVibSegment, LAISSEZ_VIB_SEGMENT) CONVERT(LaissezVib, LAISSEZ_VIB) + CONVERT(PartialTieSegment, PARTIAL_TIE_SEGMENT) + CONVERT(PartialTie, PARTIAL_TIE) CONVERT(Spacer, SPACER) CONVERT(StaffLines, STAFF_LINES) CONVERT(Ambitus, AMBITUS) @@ -492,12 +495,13 @@ class EngravingObject bool isTieSegment() const { - return type() == ElementType::TIE_SEGMENT || type() == ElementType::LAISSEZ_VIB_SEGMENT; + return type() == ElementType::TIE_SEGMENT || type() == ElementType::LAISSEZ_VIB_SEGMENT + || type() == ElementType::PARTIAL_TIE_SEGMENT; } bool isTie() const { - return type() == ElementType::TIE || type() == ElementType::LAISSEZ_VIB; + return type() == ElementType::TIE || type() == ElementType::LAISSEZ_VIB || type() == ElementType::PARTIAL_TIE; } bool isSpannerSegment() const @@ -605,7 +609,7 @@ static inline SlurTieSegment* toSlurTieSegment(EngravingObject* e) { assert( e == 0 || e->type() == ElementType::SLUR_SEGMENT || e->type() == ElementType::TIE_SEGMENT - || e->type() == ElementType::LAISSEZ_VIB_SEGMENT); + || e->type() == ElementType::LAISSEZ_VIB_SEGMENT || e->type() == ElementType::PARTIAL_TIE_SEGMENT); return (SlurTieSegment*)e; } @@ -613,7 +617,7 @@ static inline const SlurTieSegment* toSlurTieSegment(const EngravingObject* e) { assert( e == 0 || e->type() == ElementType::SLUR_SEGMENT || e->type() == ElementType::TIE_SEGMENT - || e->type() == ElementType::LAISSEZ_VIB_SEGMENT); + || e->type() == ElementType::LAISSEZ_VIB_SEGMENT || e->type() == ElementType::PARTIAL_TIE_SEGMENT); return (const SlurTieSegment*)e; } @@ -773,6 +777,7 @@ CONVERT(LineSegment) CONVERT(SlurSegment) CONVERT(TieSegment) CONVERT(LaissezVibSegment) +CONVERT(PartialTieSegment) CONVERT(Spacer) CONVERT(StaffLines) CONVERT(Ambitus) @@ -851,7 +856,6 @@ CONVERT(StringTunings) CONVERT(SoundFlag) CONVERT(TimeTickAnchor) CONVERT(LaissezVib) +CONVERT(PartialTie) #undef CONVERT } - -#endif diff --git a/src/engraving/dom/excerpt.cpp b/src/engraving/dom/excerpt.cpp index 7563ff17b2d82..1345868c2cf0f 100644 --- a/src/engraving/dom/excerpt.cpp +++ b/src/engraving/dom/excerpt.cpp @@ -26,6 +26,7 @@ #include "containers.h" +#include "dom/partialtie.h" #include "style/style.h" #include "barline.h" @@ -770,7 +771,7 @@ static void processLinkedClone(EngravingItem* ne, Score* score, track_idx_t stra static void addTies(Note* originalNote, Note* newNote, TieMap& tieMap, Score* score) { - if (originalNote->tieFor() && !originalNote->tieFor()->isLaissezVib()) { + if (originalNote->tieFor() && originalNote->tieFor()->type() == ElementType::TIE) { Tie* tie = toTie(originalNote->tieFor()->linkedClone()); tie->setScore(score); newNote->setTieFor(tie); @@ -778,7 +779,7 @@ static void addTies(Note* originalNote, Note* newNote, TieMap& tieMap, Score* sc tie->setTrack(newNote->track()); tieMap.add(originalNote->tieFor(), tie); } - if (originalNote->tieBack()) { + if (originalNote->tieBack() && originalNote->tieBack()->type() == ElementType::TIE) { Tie* tie = tieMap.findNew(originalNote->tieBack()); if (tie) { newNote->setTieBack(tie); @@ -787,6 +788,10 @@ static void addTies(Note* originalNote, Note* newNote, TieMap& tieMap, Score* sc LOGD("addTiesToMap: cannot find tie"); } } + + if (originalNote->outgoingPartialTie()) { + tieMap.add(originalNote->outgoingPartialTie(), newNote->outgoingPartialTie()); + } } static void addGraceNoteTies(GraceNotesGroup& originalGraceNotes, Chord* newChord, TieMap& tieMap, Score* score) @@ -827,6 +832,16 @@ static void addTremoloTwoChord(Chord* oldChord, Chord* newChord, TremoloTwoChord } } +static void collectTieEndPoints(TieMap& tieMap) +{ + for (auto& tie : tieMap) { + Tie* newTie = toTie(tie.second); + if (newTie->type() == ElementType::TIE || (newTie->type() == ElementType::PARTIAL_TIE && toPartialTie(newTie)->isOutgoing())) { + newTie->updatePossibleJumpPoints(); + } + } +} + static MeasureBase* cloneMeasure(MeasureBase* mb, Score* score, const Score* oscore, const std::vector& sourceStavesIndexes, const TracksMap& trackList, TieMap& tieMap) @@ -1187,6 +1202,7 @@ void Excerpt::cloneStaves(Score* sourceScore, Score* dstScore, const std::vector } } } + collectTieEndPoints(tieMap); } void Excerpt::cloneMeasures(Score* oscore, Score* score) @@ -1198,6 +1214,8 @@ void Excerpt::cloneMeasures(Score* oscore, Score* score) MeasureBase* newMeasure = cloneMeasure(mb, score, oscore, {}, {}, tieMap); measures->add(newMeasure); } + + collectTieEndPoints(tieMap); } //! NOTE For staves in the same score @@ -1384,6 +1402,8 @@ void Excerpt::cloneStaff(Staff* srcStaff, Staff* dstStaff, bool cloneSpanners) cloneSpanner(s, score, dstTrack, dstTrack2); } } + + collectTieEndPoints(tieMap); } //! NOTE For staves potentially in different scores @@ -1674,6 +1694,8 @@ void Excerpt::cloneStaff2(Staff* srcStaff, Staff* dstStaff, const Fraction& star score->transposeKeys(dstStaffIdx, dstStaffIdx + 1, startTick, endTick, !scoreConcertPitch); } + + collectTieEndPoints(tieMap); } void Excerpt::promoteGapRestsToRealRests(const Measure* measure, staff_idx_t staffIdx) diff --git a/src/engraving/dom/factory.cpp b/src/engraving/dom/factory.cpp index 7619a853057f1..34e7a186cf21a 100644 --- a/src/engraving/dom/factory.cpp +++ b/src/engraving/dom/factory.cpp @@ -75,6 +75,7 @@ #include "ottava.h" #include "page.h" #include "palmmute.h" +#include "partialtie.h" #include "pedal.h" #include "pickscrape.h" #include "playtechannotation.h" @@ -236,6 +237,7 @@ EngravingItem* Factory::doCreateItem(ElementType type, EngravingItem* parent) case ElementType::STRING_TUNINGS: return new StringTunings(parent->isSegment() ? toSegment(parent) : dummy->segment()); case ElementType::TIME_TICK_ANCHOR: return new TimeTickAnchor(parent->isSegment() ? toSegment(parent) : dummy->segment()); case ElementType::LAISSEZ_VIB: return new LaissezVib(parent->isNote() ? toNote(parent) : dummy->note()); + case ElementType::PARTIAL_TIE: return new PartialTie(parent->isNote() ? toNote(parent) : dummy->note()); case ElementType::LYRICSLINE: case ElementType::TEXTLINE_BASE: @@ -249,6 +251,7 @@ EngravingItem* Factory::doCreateItem(ElementType type, EngravingItem* parent) case ElementType::SLUR_SEGMENT: case ElementType::TIE_SEGMENT: case ElementType::LAISSEZ_VIB_SEGMENT: + case ElementType::PARTIAL_TIE_SEGMENT: case ElementType::STEM_SLASH: case ElementType::PAGE: case ElementType::BEAM: @@ -471,6 +474,9 @@ Page* Factory::createPage(RootItem* parent, bool isAccessibleEnabled) return page; } +CREATE_ITEM_IMPL(PartialTie, ElementType::PARTIAL_TIE, Note, isAccessibleEnabled) +COPY_ITEM_IMPL(PartialTie); + Rest* Factory::createRest(Segment* parent, bool isAccessibleEnabled) { Rest* r = new Rest(parent); diff --git a/src/engraving/dom/factory.h b/src/engraving/dom/factory.h index a7fbc6a2d3eca..7b040fca7da73 100644 --- a/src/engraving/dom/factory.h +++ b/src/engraving/dom/factory.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_FACTORY_H -#define MU_ENGRAVING_FACTORY_H +#pragma once #include @@ -144,6 +143,9 @@ class Factory static Page* createPage(RootItem* parent, bool isAccessibleEnabled = true); + static PartialTie* createPartialTie(Note* parent, bool isAccessibleEnabled = true); + static PartialTie* copyPartialTie(const PartialTie& src); + static Rest* createRest(Segment* parent, bool isAccessibleEnabled = true); static Rest* createRest(Segment* parent, const TDuration& t, bool isAccessibleEnabled = true); static Rest* copyRest(const Rest& src, bool link = false); @@ -307,5 +309,3 @@ class Factory static EngravingItem* doCreateItem(ElementType type, EngravingItem* parent); }; } - -#endif // MU_ENGRAVING_FACTORY_H diff --git a/src/engraving/dom/laissezvib.h b/src/engraving/dom/laissezvib.h index 7a333aa5487e3..9786b75a1d245 100644 --- a/src/engraving/dom/laissezvib.h +++ b/src/engraving/dom/laissezvib.h @@ -42,7 +42,7 @@ class LaissezVibSegment : public TieSegment int gripsCount() const override { return 0; } void editDrag(EditData&) override; - struct LayoutData : public SlurTieSegment::LayoutData { + struct LayoutData : public TieSegment::LayoutData { SymId symbol = SymId::noSym; ld_field posRelativeToNote = { "[LaissezVibSegment] posRelativeToNote", PointF() }; }; diff --git a/src/engraving/dom/marker.h b/src/engraving/dom/marker.h index 13e968218758d..1995c271a0c39 100644 --- a/src/engraving/dom/marker.h +++ b/src/engraving/dom/marker.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_MARKER_H -#define MU_ENGRAVING_MARKER_H +#pragma once #include "textbase.h" @@ -70,10 +69,15 @@ class Marker final : public TextBase void setLayoutToParentWidth(bool v) { m_layoutToParentWidth = v; } + static constexpr std::array RIGHT_MARKERS { + MarkerType::TOCODA, + MarkerType::TOCODASYM, + MarkerType::DA_CODA, + MarkerType::DA_DBLCODA, + }; + private: MarkerType m_markerType = MarkerType::SEGNO; String m_label; ///< referenced from Jump() element }; } // namespace mu::engraving - -#endif diff --git a/src/engraving/dom/masterscore.cpp b/src/engraving/dom/masterscore.cpp index 34c7fb61ecad4..0c52047a93b1d 100644 --- a/src/engraving/dom/masterscore.cpp +++ b/src/engraving/dom/masterscore.cpp @@ -202,14 +202,14 @@ const RepeatList& MasterScore::repeatList() const return *m_nonExpandedRepeatList; } -const RepeatList& MasterScore::repeatList(bool expandRepeats) const +const RepeatList& MasterScore::repeatList(bool expandRepeats, bool updateTies) const { if (expandRepeats) { - m_expandedRepeatList->update(true); + m_expandedRepeatList->update(true, updateTies); return *m_expandedRepeatList; } - m_nonExpandedRepeatList->update(false); + m_nonExpandedRepeatList->update(false, updateTies); return *m_nonExpandedRepeatList; } diff --git a/src/engraving/dom/masterscore.h b/src/engraving/dom/masterscore.h index 4b7013f03db37..bc48661f2beab 100644 --- a/src/engraving/dom/masterscore.h +++ b/src/engraving/dom/masterscore.h @@ -112,8 +112,9 @@ class MasterScore : public Score void updateRepeatListTempo(); void updateRepeatList(); + const RepeatList& repeatList() const override; - const RepeatList& repeatList(bool expandRepeats) const override; + const RepeatList& repeatList(bool expandRepeats, bool updateTies = true) const override; std::vector& excerpts() { return m_excerpts; } const std::vector& excerpts() const { return m_excerpts; } diff --git a/src/engraving/dom/measure.cpp b/src/engraving/dom/measure.cpp index c87c9b20e475f..14cb8395ffcb5 100644 --- a/src/engraving/dom/measure.cpp +++ b/src/engraving/dom/measure.cpp @@ -45,6 +45,7 @@ #include "keysig.h" #include "layoutbreak.h" #include "linkedobjects.h" +#include "marker.h" #include "masterscore.h" #include "measure.h" #include "measurenumber.h" @@ -942,7 +943,6 @@ void Measure::remove(EngravingItem* e) case ElementType::JUMP: setRepeatJump(false); // fall through - case ElementType::MARKER: case ElementType::HBOX: if (!el().remove(e)) { @@ -3069,6 +3069,38 @@ bool Measure::prevIsOneMeasureRepeat(staff_idx_t staffIdx) const return prevMeasure()->isOneMeasureRepeat(staffIdx); } +ChordRest* Measure::lastChordRest(track_idx_t track) const +{ + for (const Segment* seg = last(); seg; seg = seg->prev()) { + if (!seg->isChordRestType()) { + continue; + } + ChordRest* cr = seg->cr(track); + if (!cr) { + continue; + } + + return cr; + } + return nullptr; +} + +ChordRest* Measure::firstChordRest(track_idx_t track) const +{ + for (const Segment* seg = first(); seg; seg = seg->next()) { + if (!seg->isChordRestType()) { + continue; + } + ChordRest* cr = seg->cr(track); + if (!cr) { + continue; + } + + return cr; + } + return nullptr; +} + //------------------------------------------------------------------- // userStretch //------------------------------------------------------------------- @@ -3246,6 +3278,24 @@ bool Measure::endBarLineVisible() const return bl ? bl->visible() : true; } +const BarLine* Measure::startBarLine() const +{ + // search barline segment: + Segment* s = first(); + while (s && !(s->isStartRepeatBarLineType() || s->isBeginBarLineType())) { + s = s->next(); + } + // search first element + if (s) { + for (const EngravingItem* e : s->elist()) { + if (e) { + return toBarLine(e); + } + } + } + return nullptr; +} + //--------------------------------------------------------- // triggerLayout //--------------------------------------------------------- diff --git a/src/engraving/dom/measure.h b/src/engraving/dom/measure.h index 30e111fc71d9b..3932072f258c4 100644 --- a/src/engraving/dom/measure.h +++ b/src/engraving/dom/measure.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_MEASURE_H -#define MU_ENGRAVING_MEASURE_H +#pragma once /** \file @@ -207,6 +206,7 @@ class Measure final : public MeasureBase Segment* firstActive() const { return m_segments.firstActive(); } Segment* last() const { return m_segments.last(); } + Segment* last(SegmentType t) const { return m_segments.last(t); } Segment* lastEnabled() const { return m_segments.last(ElementFlag::ENABLED); } SegmentList& segments() { return m_segments; } const SegmentList& segments() const { return m_segments; } @@ -328,6 +328,9 @@ class Measure final : public MeasureBase bool nextIsOneMeasureRepeat(staff_idx_t staffidx) const; bool prevIsOneMeasureRepeat(staff_idx_t staffIdx) const; + ChordRest* lastChordRest(track_idx_t track) const; + ChordRest* firstChordRest(track_idx_t track) const; + EngravingItem* nextElementStaff(staff_idx_t staff, EngravingItem* fromItem = nullptr); EngravingItem* prevElementStaff(staff_idx_t staff, EngravingItem* fromItem = nullptr); @@ -340,6 +343,7 @@ class Measure final : public MeasureBase const BarLine* endBarLine() const; BarLineType endBarLineType() const; bool endBarLineVisible() const; + const BarLine* startBarLine() const; void triggerLayout() const override; void checkHeader(); @@ -387,4 +391,3 @@ class Measure final : public MeasureBase bool m_breakMultiMeasureRest = false; }; } // namespace mu::engraving -#endif diff --git a/src/engraving/dom/navigate.cpp b/src/engraving/dom/navigate.cpp index f125d5d896087..3db02fb20165d 100644 --- a/src/engraving/dom/navigate.cpp +++ b/src/engraving/dom/navigate.cpp @@ -801,6 +801,7 @@ EngravingItem* Score::nextElement() case ElementType::GLISSANDO_SEGMENT: case ElementType::NOTELINE_SEGMENT: case ElementType::LAISSEZ_VIB_SEGMENT: + case ElementType::PARTIAL_TIE_SEGMENT: case ElementType::TIE_SEGMENT: { EngravingItem* next = nextElementForSpannerSegment(toSpannerSegment(e)); if (next) { @@ -993,6 +994,7 @@ EngravingItem* Score::prevElement() case ElementType::GLISSANDO_SEGMENT: case ElementType::NOTELINE_SEGMENT: case ElementType::LAISSEZ_VIB_SEGMENT: + case ElementType::PARTIAL_TIE_SEGMENT: case ElementType::TIE_SEGMENT: { EngravingItem* prev = prevElementForSpannerSegment(toSpannerSegment(e)); if (prev) { diff --git a/src/engraving/dom/note.cpp b/src/engraving/dom/note.cpp index 5d4b6fb3d7756..d6019c2505fe7 100644 --- a/src/engraving/dom/note.cpp +++ b/src/engraving/dom/note.cpp @@ -30,6 +30,7 @@ #include #include "dom/noteline.h" +#include "dom/volta.h" #include "translation.h" #include "types/typesconv.h" #include "iengravingfont.h" @@ -42,6 +43,7 @@ #include "bagpembell.h" #include "beam.h" +#include "barline.h" #include "chord.h" #include "chordline.h" @@ -53,9 +55,11 @@ #include "guitarbend.h" #include "laissezvib.h" #include "linkedobjects.h" +#include "marker.h" #include "measure.h" #include "notedot.h" #include "part.h" +#include "partialtie.h" #include "pitchspelling.h" #include "score.h" #include "segment.h" @@ -653,17 +657,35 @@ Note::Note(const Note& n, bool link) m_tieFor = Factory::copyLaissezVib(*toLaissezVib(n.m_tieFor)); m_tieFor->setParent(this); m_tieFor->setStartNote(this); - m_tieFor->setTick(m_tieFor->startNote()->tick()); + m_tieFor->setTick(tick()); + if (link) { + score()->undo(new Link(m_tieFor, n.m_tieFor)); + } + } else if (n.outgoingPartialTie()) { + setTieFor(Factory::copyPartialTie(*toPartialTie(n.m_tieFor))); + m_tieFor->setParent(this); + m_tieFor->setStartNote(this); + m_tieFor->setTick(tick()); if (link) { score()->undo(new Link(m_tieFor, n.m_tieFor)); } } else if (n.m_tieFor) { m_tieFor = Factory::copyTie(*n.m_tieFor); m_tieFor->setStartNote(this); - m_tieFor->setTick(m_tieFor->startNote()->tick()); + m_tieFor->setTick(tick()); m_tieFor->setEndNote(0); } + if (n.incomingPartialTie()) { + setTieBack(Factory::copyPartialTie(*toPartialTie(n.m_tieBack))); + m_tieBack->setParent(this); + m_tieBack->setEndNote(this); + m_tieBack->setTick(tick()); + if (link) { + score()->undo(new Link(m_tieBack, n.m_tieBack)); + } + } + for (NoteDot* dot : n.m_dots) { add(Factory::copyNoteDot(*dot)); } @@ -1282,15 +1304,25 @@ void Note::add(EngravingItem* e) LaissezVib* lv = toLaissezVib(e); lv->setStartNote(this); lv->setTick(lv->startNote()->tick()); - lv->setTrack(track()); setTieFor(lv); break; } + case ElementType::PARTIAL_TIE: { + PartialTie* pt = toPartialTie(e); + pt->setTick(tick()); + if (pt->isOutgoing()) { + pt->setStartNote(this); + setTieFor(pt); + } else { + pt->setEndNote(this); + setTieBack(pt); + } + break; + } case ElementType::TIE: { Tie* tie = toTie(e); tie->setStartNote(this); tie->setTick(tie->startNote()->tick()); - tie->setTrack(track()); setTieFor(tie); if (tie->endNote()) { tie->endNote()->setTieBack(tie); @@ -1348,11 +1380,23 @@ void Note::remove(EngravingItem* e) } break; + case ElementType::PARTIAL_TIE: { + PartialTie* pt = toPartialTie(e); + assert((pt->isOutgoing() ? pt->startNote() : pt->endNote()) == this); + if (pt->isOutgoing()) { + setTieFor(nullptr); + } else { + setTieBack(nullptr); + pt->setJumpPoint(nullptr); + } + break; + } case ElementType::LAISSEZ_VIB: case ElementType::TIE: { Tie* tie = toTie(e); assert(tie->startNote() == this); - setTieFor(0); + setTieFor(nullptr); + tie->setJumpPoint(nullptr); if (tie->endNote()) { tie->endNote()->setTieBack(0); } @@ -1722,7 +1766,7 @@ bool Note::acceptDrop(EditData& data) const || (type == ElementType::FIGURED_BASS) || (type == ElementType::LYRICS) || (type == ElementType::HARP_DIAGRAM) - || (type != ElementType::TIE && e->isSpanner()) + || (type != ElementType::TIE && type != ElementType::PARTIAL_TIE && e->isSpanner()) || (type == ElementType::ACTION_ICON && toActionIcon(e)->actionType() == ActionIconType::STANDARD_BEND) || (type == ElementType::ACTION_ICON && toActionIcon(e)->actionType() == ActionIconType::PRE_BEND) || (type == ElementType::ACTION_ICON && toActionIcon(e)->actionType() == ActionIconType::GRACE_NOTE_BEND) @@ -2188,7 +2232,7 @@ void Note::updateAccidental(AccidentalState* as) as->setAccidentalVal(eAbsLine, accVal, m_tieBack != 0 && m_accidental == 0); acci = Accidental::value2subtype(accVal); // if previous tied note has same tpc, don't show accidental - if (m_tieBack && m_tieBack->startNote()->tpc1() == tpc1()) { + if (tieBackNonPartial() && m_tieBack->startNote()->tpc1() == tpc1()) { acci = AccidentalType::NONE; } else if (acci == AccidentalType::NONE) { acci = AccidentalType::NATURAL; @@ -2417,6 +2461,24 @@ GuitarBend* Note::bendBack() const return nullptr; } +Tie* Note::tieForNonPartial() const +{ + if (!m_tieFor || m_tieFor->type() != ElementType::TIE) { + return nullptr; + } + + return m_tieFor; +} + +Tie* Note::tieBackNonPartial() const +{ + if (!m_tieBack || m_tieBack->type() != ElementType::TIE) { + return nullptr; + } + + return m_tieBack; +} + LaissezVib* Note::laissezVib() const { if (!m_tieFor || !m_tieFor->isLaissezVib()) { @@ -2426,6 +2488,38 @@ LaissezVib* Note::laissezVib() const return toLaissezVib(m_tieFor); } +PartialTie* Note::incomingPartialTie() const +{ + if (!m_tieBack || !m_tieBack->isPartialTie()) { + return nullptr; + } + + return toPartialTie(m_tieBack); +} + +PartialTie* Note::outgoingPartialTie() const +{ + if (!m_tieFor || !m_tieFor->isPartialTie()) { + return nullptr; + } + + return toPartialTie(m_tieFor); +} + +void Note::setTieFor(Tie* t) +{ + m_tieFor = t; + m_jumpPoints.setStartTie(m_tieFor); +} + +void Note::setTieBack(Tie* t) +{ + if (m_tieBack && t && m_tieBack->jumpPoint()) { + t->setJumpPoint(m_tieBack->jumpPoint()); + } + m_tieBack = t; +} + //--------------------------------------------------------- // line //--------------------------------------------------------- @@ -3437,6 +3531,7 @@ EngravingItem* Note::nextElement() case ElementType::TIE_SEGMENT: case ElementType::LAISSEZ_VIB_SEGMENT: + case ElementType::PARTIAL_TIE_SEGMENT: if (!m_spannerFor.empty()) { for (auto i : m_spannerFor) { if (i->type() == ElementType::GLISSANDO) { @@ -3526,6 +3621,7 @@ EngravingItem* Note::prevElement() } return this; case ElementType::TIE_SEGMENT: + case ElementType::PARTIAL_TIE_SEGMENT: case ElementType::LAISSEZ_VIB_SEGMENT: if (!m_el.empty()) { return m_el.back(); @@ -3642,6 +3738,9 @@ Note* Note::firstTiedNote(bool ignorePlayback) const if (std::find(notes.begin(), notes.end(), note->tieBack()->startNote()) != notes.end()) { break; } + if (!note->tieBack()->startNote()) { + break; + } note = note->tieBack()->startNote(); notes.push_back(note); } @@ -3652,13 +3751,31 @@ Note* Note::firstTiedNote(bool ignorePlayback) const // tiedNotes //--------------------------------------------------------- -std::vector Note::tiedNotes() const +std::vector Note::findTiedNotes(Note* startNote, bool followPartialTies) { + // Returns all notes ahead of startNote in a chain of ties + // Follows partial tie paths recursively + Note* note = startNote; std::vector notes; - Note* note = firstTiedNote(); - notes.push_back(note); + while (note->tieFor()) { + if (followPartialTies) { + for (TieJumpPoint* jumpPoint : *note->tieJumpPoints()) { + if (!jumpPoint->active() || jumpPoint->followingNote()) { + continue; + } + if (!jumpPoint->note() || std::find(notes.begin(), notes.end(), jumpPoint->note()) != notes.end()) { + continue; + } + // ONLY backtrack when end point is a full tie eg. around a segno + const bool endTieIsFullTie = jumpPoint->endTie() && !jumpPoint->endTie()->isPartialTie(); + Note* jumpPointNote = endTieIsFullTie ? jumpPoint->endTie()->startNote()->firstTiedNote() : jumpPoint->note(); + std::vector partialTieNotes = findTiedNotes(jumpPointNote, !endTieIsFullTie); + notes.insert(notes.end(), partialTieNotes.begin(), partialTieNotes.end()); + } + } + Note* endNote = note->tieFor()->endNote(); if (!endNote || std::find(notes.begin(), notes.end(), endNote) != notes.end()) { break; @@ -3666,6 +3783,17 @@ std::vector Note::tiedNotes() const note = endNote; notes.push_back(note); } + + return notes; +} + +std::vector Note::tiedNotes() const +{ + // Backtrack to the first tied note in a chain, then return all notes in the chain ahead of it + Note* note = firstTiedNote(); + + std::vector notes = findTiedNotes(note); + return notes; } @@ -3708,17 +3836,72 @@ void Note::disconnectTiedNotes() void Note::connectTiedNotes() { - if (tieBack()) { + if (tieBack() && !tieBack()->isPartialTie()) { tieBack()->setEndNote(this); if (tieBack()->startNote()) { tieBack()->startNote()->add(tieBack()); } } - if (tieFor() && tieFor()->endNote()) { + if (tieFor() && tieFor()->endNote() && !tieFor()->isPartialTie()) { tieFor()->endNote()->setTieBack(tieFor()); } } +bool Note::hasFollowingJumpItem() +{ + const Chord* startChord = chord(); + const Segment* seg = startChord->segment(); + const Measure* measure = seg->measure(); + const Fraction nextTick = seg->tick() + startChord->actualTicks(); + + // Jumps & markers + for (const EngravingItem* e : measure->el()) { + if (!e->isJump() && !e->isMarker()) { + continue; + } + + if (e->isJump()) { + return true; + } + + const Marker* marker = toMarker(e); + + if (muse::contains(Marker::RIGHT_MARKERS, marker->markerType())) { + return true; + } + } + + // Voltas + auto spanners = score()->spannerMap().findOverlapping(measure->endTick().ticks(), measure->endTick().ticks()); + for (auto& spanner : spanners) { + if (!spanner.value->isVolta() || Fraction::fromTicks(spanner.start) != nextTick) { + continue; + } + + return true; + } + + // Repeats + if (measure->endTick() == nextTick && measure->repeatEnd()) { + return true; + } + + for (Segment* nextSeg = seg->next(SegmentType::BarLineType); nextSeg && nextSeg->tick() == nextTick; + nextSeg = nextSeg->next(SegmentType::BarLineType)) { + const EngravingItem* el = nextSeg->element(startChord->track()); + if (!el || !el->isBarLine()) { + continue; + } + const BarLine* bl = toBarLine(el); + + if (bl->barLineType() & (BarLineType::END_REPEAT | BarLineType::END_START_REPEAT)) { + return true; + } + } + + return false; +} + //--------------------------------------------------------- // accidentalType //--------------------------------------------------------- @@ -3870,12 +4053,12 @@ void Note::setIsTrillCueNote(bool v) } } -void Note::addLineAttachPoint(PointF point, EngravingItem* line) +void Note::addLineAttachPoint(PointF point, EngravingItem* line, bool start) { // IMPORTANT: the point is expected in *staff* coordinates // We transform into note coordinates by subtracting the note position in staff coordinates point -= posInStaffCoordinates(); - m_lineAttachPoints.push_back(LineAttachPoint(line, point.x(), point.y())); + m_lineAttachPoints.push_back(LineAttachPoint(line, point.x(), point.y(), start)); } bool Note::negativeFretUsed() const diff --git a/src/engraving/dom/note.h b/src/engraving/dom/note.h index c7525b4e5a368..84d8e59b5dff1 100644 --- a/src/engraving/dom/note.h +++ b/src/engraving/dom/note.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_NOTE_H -#define MU_ENGRAVING_NOTE_H +#pragma once /** \file @@ -36,6 +35,7 @@ #include "noteevent.h" #include "pitchspelling.h" #include "symbol.h" +#include "tie.h" #include "types.h" namespace mu::engraving { @@ -64,15 +64,17 @@ static constexpr int MAX_DOTS = 4; class LineAttachPoint { public: - LineAttachPoint(EngravingItem* l, double x, double y) - : m_line(l), m_pos(PointF(x, y)) {} + LineAttachPoint(EngravingItem* l, double x, double y, bool start) + : m_line(l), m_pos(PointF(x, y)), m_start(start) {} const EngravingItem* line() const { return m_line; } const PointF pos() const { return m_pos; } + bool start() const { return m_start; } private: EngravingItem* m_line = nullptr; PointF m_pos = PointF(0.0, 0.0); + bool m_start = true; }; //--------------------------------------------------------- @@ -295,9 +297,13 @@ class Note final : public EngravingItem GuitarBend* bendBack() const; Tie* tieFor() const { return m_tieFor; } Tie* tieBack() const { return m_tieBack; } + Tie* tieForNonPartial() const; + Tie* tieBackNonPartial() const; LaissezVib* laissezVib() const; - void setTieFor(Tie* t) { m_tieFor = t; } - void setTieBack(Tie* t) { m_tieBack = t; } + PartialTie* incomingPartialTie() const; + PartialTie* outgoingPartialTie() const; + void setTieFor(Tie* t); + void setTieBack(Tie* t); Note* firstTiedNote(bool ignorePlayback = true) const; const Note* lastTiedNote(bool ignorePlayback = true) const; Note* lastTiedNote(bool ignorePlayback = true) @@ -309,6 +315,8 @@ class Note final : public EngravingItem void disconnectTiedNotes(); void connectTiedNotes(); + bool hasFollowingJumpItem(); + void setupAfterRead(const Fraction& tick, bool pasteMode); bool acceptDrop(EditData&) const override; @@ -441,9 +449,10 @@ class Note final : public EngravingItem bool hasAnotherStraightAboveOrBelow(bool above) const; - void addLineAttachPoint(PointF point, EngravingItem* line); std::vector& lineAttachPoints() { return m_lineAttachPoints; } const std::vector& lineAttachPoints() const { return m_lineAttachPoints; } + void addStartLineAttachPoint(PointF point, EngravingItem* line) { addLineAttachPoint(point, line, true); } + void addEndLineAttachPoint(PointF point, EngravingItem* line) { addLineAttachPoint(point, line, false); } PointF posInStaffCoordinates(); @@ -459,6 +468,9 @@ class Note final : public EngravingItem void setVisible(bool v) override; + TieJumpPointList* tieJumpPoints() { return &m_jumpPoints; } + const TieJumpPointList* tieJumpPoints() const { return &m_jumpPoints; } + struct LayoutData : public EngravingItem::LayoutData { ld_field useTablature = { "[Note] useTablature", false }; ld_field cachedNoteheadSym = { "[Note] cachedNoteheadSym", SymId::noSym }; // use in draw to avoid recomputing at every update @@ -486,12 +498,16 @@ class Note final : public EngravingItem int concertPitchIdx() const; void updateRelLine(int absLine, bool undoable); + static std::vector findTiedNotes(Note* startNote, bool followPartialTies = true); + void normalizeLeftDragDelta(Segment* seg, EditData& ed, NoteEditData* ned); static String tpcUserName(int tpc, int pitch, bool explicitAccidental, bool full = false); void getNoteListForDots(std::vector& topDownNotes, std::vector& bottomUpNotes, std::vector& anchoredDots); + void addLineAttachPoint(PointF point, EngravingItem* line, bool start); + bool m_ghost = false; // ghost note bool m_deadNote = false; // dead note @@ -560,6 +576,6 @@ class Note final : public EngravingItem String m_fretString; std::vector m_lineAttachPoints; + TieJumpPointList m_jumpPoints; }; } // namespace mu::engraving -#endif diff --git a/src/engraving/dom/partialtie.cpp b/src/engraving/dom/partialtie.cpp new file mode 100644 index 0000000000000..fa4981ebab041 --- /dev/null +++ b/src/engraving/dom/partialtie.cpp @@ -0,0 +1,119 @@ +#include "partialtie.h" +#include "chord.h" +#include "measure.h" +#include "score.h" +#include "note.h" +#include "staff.h" + +namespace mu::engraving { +PartialTie::PartialTie(Note* parent) + : Tie(ElementType::PARTIAL_TIE, parent) +{ +} + +mu::engraving::PropertyValue mu::engraving::PartialTie::getProperty(Pid propertyId) const +{ + switch (propertyId) { + case Pid::PARTIAL_SPANNER_DIRECTION: + return partialSpannerDirection(); + default: + return Tie::getProperty(propertyId); + } +} + +PropertyValue PartialTie::propertyDefault(Pid propertyId) const +{ + switch (propertyId) { + case Pid::PARTIAL_SPANNER_DIRECTION: + return PartialSpannerDirection::OUTGOING; + default: + return Tie::propertyDefault(propertyId); + } +} + +bool PartialTie::setProperty(Pid propertyId, const PropertyValue& v) +{ + switch (propertyId) { + case Pid::PARTIAL_SPANNER_DIRECTION: + setPartialSpannerDirection(v.value()); + break; + default: + return Tie::setProperty(propertyId, v); + } + triggerLayout(); + return true; +} + +void PartialTie::setStartNote(Note* note) +{ + setPartialSpannerDirection(PartialSpannerDirection::OUTGOING); + Tie::setStartNote(note); + Tie::setEndNote(nullptr); +} + +void PartialTie::setEndNote(Note* note) +{ + setPartialSpannerDirection(PartialSpannerDirection::INCOMING); + Tie::setStartNote(nullptr); + Tie::setEndNote(note); + setParent(note); +} + +bool PartialTie::allJumpPointsInactive() const +{ + if (!isOutgoing()) { + return false; + } + return Tie::allJumpPointsInactive(); +} + +TieJumpPointList* PartialTie::tieJumpPoints() +{ + if (!isOutgoing()) { + return nullptr; + } + + return Tie::tieJumpPoints(); +} + +const TieJumpPointList* PartialTie::tieJumpPoints() const +{ + if (!isOutgoing()) { + return nullptr; + } + + return Tie::tieJumpPoints(); +} + +Note* PartialTie::startNote() const +{ + if (isOutgoing()) { + return Tie::startNote(); + } + return startTie() ? startTie()->startNote() : nullptr; +} + +PartialTieSegment::PartialTieSegment(System* parent) + : TieSegment(ElementType::PARTIAL_TIE_SEGMENT, parent) +{ +} + +PartialTieSegment::PartialTieSegment(const PartialTieSegment& s) + : TieSegment(s) +{ +} + +String PartialTieSegment::formatBarsAndBeats() const +{ + const PartialTie* pt = this->partialTie(); + const Note* note = pt ? pt->note() : nullptr; + const Chord* chord = note ? note->chord() : nullptr; + const Segment* seg = chord ? chord->segment() : nullptr; + + if (!seg) { + return EngravingItem::formatBarsAndBeats(); + } + + return seg->formatBarsAndBeats(); +} +} diff --git a/src/engraving/dom/partialtie.h b/src/engraving/dom/partialtie.h new file mode 100644 index 0000000000000..37d191aebc255 --- /dev/null +++ b/src/engraving/dom/partialtie.h @@ -0,0 +1,84 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2021 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "tie.h" + +namespace mu::engraving { +class PartialTieSegment : public TieSegment +{ + OBJECT_ALLOCATOR(engraving, PartialTieSegment) + DECLARE_CLASSOF(ElementType::PARTIAL_TIE_SEGMENT) + +public: + PartialTieSegment(System* parent); + PartialTieSegment(const PartialTieSegment& s); + + PartialTieSegment* clone() const override { return new PartialTieSegment(*this); } + + PartialTie* partialTie() const { return (PartialTie*)spanner(); } + int subtype() const override { return static_cast(spanner()->type()); } +private: + String formatBarsAndBeats() const override; +}; + +class PartialTie : public Tie +{ + OBJECT_ALLOCATOR(engraving, PartialTie) + DECLARE_CLASSOF(ElementType::PARTIAL_TIE); + + M_PROPERTY2(PartialSpannerDirection, partialSpannerDirection, setPartialSpannerDirection, PartialSpannerDirection::OUTGOING) + +public: + PartialTie(Note* parent); + + Note* parentNote() const { return parent() ? toNote(parent()) : nullptr; } + + PartialTie* clone() const override { return new PartialTie(*this); } + + inline bool isOutgoing() const { return _partialSpannerDirection == PartialSpannerDirection::OUTGOING; } + + PropertyValue getProperty(Pid propertyId) const override; + PropertyValue propertyDefault(Pid propertyId) const override; + bool setProperty(Pid propertyId, const PropertyValue& v) override; + + SlurTieSegment* newSlurTieSegment(System* parent) override { return new PartialTieSegment(parent); } + + Note* note() const { return isOutgoing() ? startNote() : endNote(); } + void setStartNote(Note* note) override; + void setEndNote(Note* note) override; + + TieJumpPointList* tieJumpPoints() override; + const TieJumpPointList* tieJumpPoints() const override; + bool allJumpPointsInactive() const override; + + Note* startNote() const override; + + PartialTieSegment* frontSegment() { return toPartialTieSegment(Spanner::frontSegment()); } + const PartialTieSegment* frontSegment() const { return toPartialTieSegment(Spanner::frontSegment()); } + PartialTieSegment* backSegment() { return toPartialTieSegment(Spanner::backSegment()); } + const PartialTieSegment* backSegment() const { return toPartialTieSegment(Spanner::backSegment()); } + PartialTieSegment* segmentAt(int n) { return toPartialTieSegment(Spanner::segmentAt(n)); } + const PartialTieSegment* segmentAt(int n) const { return toPartialTieSegment(Spanner::segmentAt(n)); } +}; +} diff --git a/src/engraving/dom/property.cpp b/src/engraving/dom/property.cpp index 7b8d4119a4318..c04cc1c11e977 100644 --- a/src/engraving/dom/property.cpp +++ b/src/engraving/dom/property.cpp @@ -431,6 +431,7 @@ static constexpr PropertyMetaData propertyList[] = { { Pid::TIE_PLACEMENT, true, "tiePlacement", P_TYPE::TIE_PLACEMENT, PropertyGroup::APPEARANCE, DUMMY_QT_TR_NOOP("propertyName", "tie placement") }, { Pid::MIN_LENGTH, true, "minLength", P_TYPE::SPATIUM, PropertyGroup::APPEARANCE, DUMMY_QT_TR_NOOP("propertyName", "minimum length") }, + { Pid::PARTIAL_SPANNER_DIRECTION, true, "partialSpannerDirection", P_TYPE::PARTIAL_SPANNER_DIRECTION, PropertyGroup::NONE, DUMMY_QT_TR_NOOP("propertyName", "partial spanner direction") }, { Pid::POSITION_LINKED_TO_MASTER, false, "positionLinkedToMaster", P_TYPE::BOOL, PropertyGroup::NONE, DUMMY_QT_TR_NOOP("propertyName", "position linked to master") }, { Pid::APPEARANCE_LINKED_TO_MASTER, false, "appearanceLinkedToMaster", P_TYPE::BOOL, PropertyGroup::NONE, DUMMY_QT_TR_NOOP("propertyName", "appearance linked to master") }, diff --git a/src/engraving/dom/property.h b/src/engraving/dom/property.h index fb8615da04019..4b2cafe0f76d2 100644 --- a/src/engraving/dom/property.h +++ b/src/engraving/dom/property.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_PROPERTY_H -#define MU_ENGRAVING_PROPERTY_H +#pragma once #include "global/types/string.h" @@ -440,6 +439,8 @@ enum class Pid { TIE_PLACEMENT, MIN_LENGTH, + PARTIAL_SPANNER_DIRECTION, + POSITION_LINKED_TO_MASTER, APPEARANCE_LINKED_TO_MASTER, TEXT_LINKED_TO_MASTER, @@ -484,5 +485,3 @@ extern PropertyGroup propertyGroup(Pid id); extern Pid propertyId(const muse::AsciiStringView& name); extern String propertyUserName(Pid); } // namespace mu::engraving - -#endif diff --git a/src/engraving/dom/repeatlist.cpp b/src/engraving/dom/repeatlist.cpp index d6830bfaeb053..d9709625562ec 100644 --- a/src/engraving/dom/repeatlist.cpp +++ b/src/engraving/dom/repeatlist.cpp @@ -90,6 +90,15 @@ bool RepeatSegment::containsMeasure(Measure const* const m) const return false; } +bool RepeatSegment::endsWithMeasure(Measure const* const m) const +{ + if (m_measureList.empty()) { + return false; + } + + return m_measureList.back() == m; +} + bool RepeatSegment::isEmpty() const { return m_measureList.empty(); @@ -149,7 +158,7 @@ int RepeatList::ticks() const // update //--------------------------------------------------------- -void RepeatList::update(bool expand) +void RepeatList::update(bool expand, bool updateTies) { if (!m_scoreChanged && expand == m_expanded) { return; @@ -162,6 +171,10 @@ void RepeatList::update(bool expand) } m_scoreChanged = false; + + if (updateTies) { + m_score->undoRemoveStaleTieJumpPoints(); + } } //--------------------------------------------------------- diff --git a/src/engraving/dom/repeatlist.h b/src/engraving/dom/repeatlist.h index 51444efdf433e..640b462542ee4 100644 --- a/src/engraving/dom/repeatlist.h +++ b/src/engraving/dom/repeatlist.h @@ -57,6 +57,7 @@ class RepeatSegment void addMeasure(Measure const* const); void addMeasures(Measure const* const); bool containsMeasure(Measure const* const) const; + bool endsWithMeasure(Measure const* const) const; bool isEmpty() const; int len() const; void popMeasure(); @@ -85,7 +86,7 @@ class RepeatList : public std::vector RepeatList& operator=(const RepeatList&) = delete; ~RepeatList(); - void update(bool expand); + void update(bool expand, bool updateTies = true); void setScoreChanged() { m_scoreChanged = true; } const Score* score() const { return m_score; } diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index 4e2b9ad08c5fe..cfe676952b54b 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -5741,7 +5741,10 @@ void Score::connectTies(bool silent) } // connect a tie without end note Tie* tie = n->tieFor(); - if (tie && !tie->endNote()) { + if (tie) { + tie->updatePossibleJumpPoints(); + } + if (tie && !tie->isPartialTie() && !tie->endNote()) { Note* nnote; if (m_mscVersion <= 114) { nnote = searchTieNote114(n); @@ -6073,7 +6076,11 @@ void Score::updateChannel() UndoStack* Score::undoStack() const { return m_masterScore->undoStack(); } const RepeatList& Score::repeatList() const { return m_masterScore->repeatList(); } -const RepeatList& Score::repeatList(bool expandRepeats) const { return m_masterScore->repeatList(expandRepeats); } +const RepeatList& Score::repeatList(bool expandRepeats, bool updateTies) const +{ + return m_masterScore->repeatList(expandRepeats, updateTies); +} + TempoMap* Score::tempomap() const { return m_masterScore->tempomap(); } TimeSigMap* Score::sigmap() const { return m_masterScore->sigmap(); } //QQueue* Score::midiInputQueue() { return _masterScore->midiInputQueue(); } diff --git a/src/engraving/dom/score.h b/src/engraving/dom/score.h index c62160725ddf9..00dea94602de2 100644 --- a/src/engraving/dom/score.h +++ b/src/engraving/dom/score.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_SCORE_H -#define MU_ENGRAVING_SCORE_H +#pragma once /** \file @@ -340,7 +339,7 @@ class Score : public EngravingObject, public muse::Injectable void cmdSetBeamMode(BeamMode); void cmdRemovePart(Part*); void cmdAddTie(bool addToChord = false); - void cmdToggleTie(); + Tie* cmdToggleTie(); void cmdToggleLaissezVib(); static std::vector cmdTieNoteList(const Selection& selection, bool noteEntryMode); void cmdAddOttava(OttavaType); @@ -767,7 +766,7 @@ class Score : public EngravingObject, public muse::Injectable /// where those need to have the same value for expandRepeats. virtual const RepeatList& repeatList() const; /// For small, one-step operations, where you need to get the relevant repeatList just once - virtual const RepeatList& repeatList(bool expandRepeats) const; + virtual const RepeatList& repeatList(bool expandRepeats, bool updateTies = true) const; double utick2utime(int tick) const; int utime2utick(double utime) const; @@ -803,6 +802,8 @@ class Score : public EngravingObject, public muse::Injectable Segment* lastSegmentMM() const; void connectTies(bool silent = false); + void undoRemoveStaleTieJumpPoints(); + void doUndoRemoveStaleTieJumpPoints(Tie* tie); void scanElementsInRange(void* data, void (* func)(void*, EngravingItem*), bool all = true); int fileDivision() const { return m_fileDivision; } ///< division of current loading *.msc file @@ -1204,5 +1205,3 @@ class ScoreLoad DECLARE_OPERATORS_FOR_FLAGS(LayoutFlags) } // namespace mu::engraving - -#endif diff --git a/src/engraving/dom/segment.cpp b/src/engraving/dom/segment.cpp index 70b859ca3e20b..63777ff19f8f3 100644 --- a/src/engraving/dom/segment.cpp +++ b/src/engraving/dom/segment.cpp @@ -1171,17 +1171,17 @@ bool Segment::setProperty(Pid propertyId, const PropertyValue& v) // widthInStaff //--------------------------------------------------------- -double Segment::widthInStaff(staff_idx_t staffIdx, SegmentType t) const +double Segment::widthInStaff(staff_idx_t staffIdx, SegmentType nextSegType) const { const double segX = x(); double nextSegX = segX; - Segment* nextSeg = nextInStaff(staffIdx, t); + Segment* nextSeg = nextInStaff(staffIdx, nextSegType); if (nextSeg) { nextSegX = nextSeg->x(); } else { Segment* lastSeg = measure()->lastEnabled(); - if (lastSeg->segmentType() & t) { + if (lastSeg->segmentType() & nextSegType) { nextSegX = lastSeg->x() + lastSeg->width(); } else { nextSegX = lastSeg->x(); @@ -2219,6 +2219,7 @@ EngravingItem* Segment::prevElement(staff_idx_t activeStaff) EngravingItem* el = e; Segment* seg = this; if (e->type() == ElementType::TIE_SEGMENT || e->type() == ElementType::LAISSEZ_VIB_SEGMENT + || e->type() == ElementType::PARTIAL_TIE_SEGMENT || e->type() == ElementType::GLISSANDO_SEGMENT || e->type() == ElementType::NOTELINE_SEGMENT) { SpannerSegment* s = toSpannerSegment(e); Spanner* sp = s->spanner(); diff --git a/src/engraving/dom/segment.h b/src/engraving/dom/segment.h index 1eef2b1058069..d059e7f711b68 100644 --- a/src/engraving/dom/segment.h +++ b/src/engraving/dom/segment.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_SEGMENT_H -#define MU_ENGRAVING_SEGMENT_H +#pragma once #include "engravingitem.h" @@ -191,7 +190,7 @@ class Segment final : public EngravingItem Fraction ticks() const { return m_ticks; } void setTicks(const Fraction& v) { m_ticks = v; } - double widthInStaff(staff_idx_t staffIdx, SegmentType t = SegmentType::ChordRest) const; + double widthInStaff(staff_idx_t staffIdx, SegmentType nextSegType = SegmentType::ChordRest) const; Fraction ticksInStaff(staff_idx_t staffIdx) const; bool splitsTuplet() const; @@ -340,5 +339,3 @@ class Segment final : public EngravingItem #ifndef NO_QT_SUPPORT Q_DECLARE_METATYPE(mu::engraving::SegmentType) #endif - -#endif diff --git a/src/engraving/dom/segmentlist.cpp b/src/engraving/dom/segmentlist.cpp index 2b97b6809f10f..23efb9841e447 100644 --- a/src/engraving/dom/segmentlist.cpp +++ b/src/engraving/dom/segmentlist.cpp @@ -267,4 +267,14 @@ Segment* SegmentList::last(ElementFlag flags) const } return nullptr; } + +Segment* SegmentList::last(SegmentType types) const +{ + for (Segment* s = m_last; s; s = s->prev()) { + if (s->segmentType() & types) { + return s; + } + } + return nullptr; +} } diff --git a/src/engraving/dom/segmentlist.h b/src/engraving/dom/segmentlist.h index 50944ea7d3398..d81835d822eb4 100644 --- a/src/engraving/dom/segmentlist.h +++ b/src/engraving/dom/segmentlist.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_SEGMENTLIST_H -#define MU_ENGRAVING_SEGMENTLIST_H +#pragma once #include "segment.h" @@ -52,6 +51,7 @@ class SegmentList Segment* last() const { return m_last; } Segment* last(ElementFlag) const; + Segment* last(SegmentType) const; Segment* firstCRSegment() const; void remove(Segment*); void push_back(Segment*); @@ -92,4 +92,3 @@ class SegmentList // Segment* begin(SegmentList& l) { return l.first(); } // Segment* end(SegmentList&) { return 0; } } // namespace mu::engraving -#endif diff --git a/src/engraving/dom/select.cpp b/src/engraving/dom/select.cpp index cf4a2f87bf8c7..9e8c37cc31526 100644 --- a/src/engraving/dom/select.cpp +++ b/src/engraving/dom/select.cpp @@ -51,6 +51,7 @@ #include "note.h" #include "notedot.h" #include "part.h" +#include "partialtie.h" #include "rest.h" #include "score.h" #include "segment.h" @@ -604,6 +605,14 @@ void Selection::appendChord(Chord* chord) if (note->laissezVib()) { appendFiltered(note->laissezVib()->frontSegment()); } + + if (note->incomingPartialTie()) { + appendFiltered(note->incomingPartialTie()->frontSegment()); + } + + if (note->outgoingPartialTie()) { + appendFiltered(note->outgoingPartialTie()->frontSegment()); + } } } diff --git a/src/engraving/dom/slurtie.h b/src/engraving/dom/slurtie.h index 7fb4f0367e830..18076d3169fca 100644 --- a/src/engraving/dom/slurtie.h +++ b/src/engraving/dom/slurtie.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_SLURTIE_H -#define MU_ENGRAVING_SLURTIE_H +#pragma once #include "spanner.h" @@ -201,5 +200,3 @@ class SlurTie : public Spanner SlurStyleType m_styleType = SlurStyleType::Undefined; }; } - -#endif diff --git a/src/engraving/dom/system.cpp b/src/engraving/dom/system.cpp index ffe5f5c142c58..8427bffbd2353 100644 --- a/src/engraving/dom/system.cpp +++ b/src/engraving/dom/system.cpp @@ -474,6 +474,7 @@ void System::add(EngravingItem* el) case ElementType::SLUR_SEGMENT: case ElementType::TIE_SEGMENT: case ElementType::LAISSEZ_VIB_SEGMENT: + case ElementType::PARTIAL_TIE_SEGMENT: case ElementType::PEDAL_SEGMENT: case ElementType::LYRICSLINE_SEGMENT: case ElementType::GLISSANDO_SEGMENT: @@ -555,6 +556,7 @@ void System::remove(EngravingItem* el) case ElementType::SLUR_SEGMENT: case ElementType::TIE_SEGMENT: case ElementType::LAISSEZ_VIB_SEGMENT: + case ElementType::PARTIAL_TIE_SEGMENT: case ElementType::PEDAL_SEGMENT: case ElementType::LYRICSLINE_SEGMENT: case ElementType::GRADUAL_TEMPO_CHANGE_SEGMENT: @@ -989,7 +991,7 @@ Spacer* System::downSpacer(staff_idx_t staffIdx) const // or the position just after the last non-chordrest segment //--------------------------------------------------------- -double System::firstNoteRestSegmentX(bool leading) +double System::firstNoteRestSegmentX(bool leading) const { double margin = style().styleMM(Sid::headerToLineStartDistance); for (const MeasureBase* mb : measures()) { @@ -1043,7 +1045,7 @@ double System::firstNoteRestSegmentX(bool leading) double System::endingXForOpenEndedLines() const { - double margin = style().spatium() / 4; // TODO: this can be parameterizable + double margin = style().styleMM(Sid::lineEndToSystemEndDistance); double systemEndX = ldata()->bbox().width(); Measure* lastMeas = lastMeasure(); @@ -1067,19 +1069,12 @@ double System::endingXForOpenEndedLines() const // returns the last chordrest of a system for a particular track //--------------------------------------------------------- -ChordRest* System::lastChordRest(track_idx_t track) +ChordRest* System::lastChordRest(track_idx_t track) const { for (auto measureBaseIter = measures().rbegin(); measureBaseIter != measures().rend(); measureBaseIter++) { if ((*measureBaseIter)->isMeasure()) { const Measure* measure = static_cast(*measureBaseIter); - for (const Segment* seg = measure->last(); seg; seg = seg->prev()) { - if (seg->isChordRestType()) { - ChordRest* cr = seg->cr(track); - if (cr) { - return cr; - } - } - } + return measure->lastChordRest(track); } } return nullptr; @@ -1090,23 +1085,16 @@ ChordRest* System::lastChordRest(track_idx_t track) // returns the last chordrest of a system for a particular track //--------------------------------------------------------- -ChordRest* System::firstChordRest(track_idx_t track) +ChordRest* System::firstChordRest(track_idx_t track) const { for (const MeasureBase* mb : measures()) { if (!mb->isMeasure()) { continue; } const Measure* measure = static_cast(mb); - for (const Segment* seg = measure->first(); seg; seg = seg->next()) { - if (seg->isChordRestType()) { - ChordRest* cr = seg->cr(track); - if (cr) { - return cr; - } - } - } + return measure->firstChordRest(track); } - return 0; + return nullptr; } //--------------------------------------------------------- diff --git a/src/engraving/dom/system.h b/src/engraving/dom/system.h index 3f52f5cc4c07f..89afd26aa8b6f 100644 --- a/src/engraving/dom/system.h +++ b/src/engraving/dom/system.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_SYSTEM_H -#define MU_ENGRAVING_SYSTEM_H +#pragma once /** \file @@ -177,10 +176,10 @@ class System final : public EngravingItem Spacer* upSpacer(staff_idx_t staffIdx, Spacer* prevDownSpacer) const; Spacer* downSpacer(staff_idx_t staffIdx) const; - double firstNoteRestSegmentX(bool leading = false); + double firstNoteRestSegmentX(bool leading = false) const; double endingXForOpenEndedLines() const; - ChordRest* lastChordRest(track_idx_t track); - ChordRest* firstChordRest(track_idx_t track); + ChordRest* lastChordRest(track_idx_t track) const; + ChordRest* firstChordRest(track_idx_t track) const; bool hasFixedDownDistance() const { return m_fixedDownDistance; } void setFixedDownDistance(bool val) const { m_fixedDownDistance = val; } @@ -240,4 +239,3 @@ class System final : public EngravingItem typedef std::vector::iterator iSystem; typedef std::vector::const_iterator ciSystem; } // namespace mu::engraving -#endif diff --git a/src/engraving/dom/tie.cpp b/src/engraving/dom/tie.cpp index 992522e4ba4f3..253ee256584c0 100644 --- a/src/engraving/dom/tie.cpp +++ b/src/engraving/dom/tie.cpp @@ -26,19 +26,29 @@ #include "draw/types/transform.h" #include "accidental.h" +#include "barline.h" #include "chord.h" +#include "factory.h" #include "hook.h" #include "ledgerline.h" +#include "marker.h" +#include "masterscore.h" #include "measure.h" #include "mscoreview.h" #include "note.h" #include "notedot.h" +#include "part.h" +#include "partialtie.h" +#include "repeatlist.h" #include "score.h" #include "staff.h" #include "stafftype.h" #include "stem.h" #include "system.h" +#include "types/typesconv.h" +#include "undo.h" #include "utils.h" +#include "volta.h" #include "log.h" @@ -46,8 +56,9 @@ using namespace mu; using namespace muse::draw; namespace mu::engraving { -Note* Tie::editStartNote; -Note* Tie::editEndNote; +//--------------------------------------------------------- +// TieSegment +//--------------------------------------------------------- TieSegment::TieSegment(System* parent) : SlurTieSegment(ElementType::TIE_SEGMENT, parent) @@ -64,10 +75,6 @@ TieSegment::TieSegment(const TieSegment& s) { } -//--------------------------------------------------------- -// changeAnchor -//--------------------------------------------------------- - void TieSegment::changeAnchor(EditData& ed, EngravingItem* element) { if (ed.curGrip == Grip::START) { @@ -103,10 +110,6 @@ void TieSegment::changeAnchor(EditData& ed, EngravingItem* element) } } -//--------------------------------------------------------- -// editDrag -//--------------------------------------------------------- - void TieSegment::editDrag(EditData& ed) { consolidateAdjustmentOffsetIntoUserOffset(); @@ -118,6 +121,9 @@ void TieSegment::editDrag(EditData& ed) // // move anchor for slurs/ties // + if (isPartialTieSegment()) { + return; + } if ((g == Grip::START && isSingleBeginType()) || (g == Grip::END && isSingleEndType())) { Spanner* spanner = tie(); EngravingItem* e = ed.view()->elementNear(ed.pos); @@ -164,10 +170,6 @@ void TieSegment::consolidateAdjustmentOffsetIntoUserOffset() resetAdjustmentOffset(); } -//--------------------------------------------------------- -// isEdited -//--------------------------------------------------------- - bool TieSegment::isEdited() const { for (int i = 0; i < int(Grip::GRIPS); ++i) { @@ -213,6 +215,132 @@ Tie::Tie(const ElementType& type, EngravingItem* parent) setAnchor(Anchor::NOTE); } +void Tie::updatePossibleJumpPoints() +{ + MasterScore* master = masterScore(); + const Note* note = toNote(parentItem()); + const Chord* chord = note->chord(); + const Measure* measure = chord->measure(); + const MeasureBase* masterMeasureBase = master->measure(measure->index()); + const Measure* masterMeasure = masterMeasureBase && masterMeasureBase->isMeasure() ? toMeasure(masterMeasureBase) : nullptr; + if (!tieJumpPoints()) { + return; + } + + tieJumpPoints()->clear(); + + if (!startNote()->hasFollowingJumpItem()) { + return; + } + + int jumpPointIdx = 0; + + Note* nextNote = searchTieNote(note); + nextNote = nextNote ? nextNote : endNote(); + + if (nextNote) { + const bool hasTie = nextNote->tieBack(); + TieJumpPoint* jumpPoint = new TieJumpPoint(nextNote, hasTie, jumpPointIdx, true); + tieJumpPoints()->add(jumpPoint); + jumpPointIdx++; + } + + // Get following notes by taking repeats + const RepeatList& repeatList = master->repeatList(true, false); + + for (auto it = repeatList.begin(); it != repeatList.end(); it++) { + const RepeatSegment* rs = *it; + const auto nextSegIt = std::next(it); + if (!rs->endsWithMeasure(masterMeasure) || nextSegIt == repeatList.end()) { + continue; + } + + // Get next segment + const RepeatSegment* nextSeg = *nextSegIt; + const Measure* firstMasterMeasure = nextSeg->firstMeasure(); + const MeasureBase* firstMeasureBase = firstMasterMeasure ? score()->measure(firstMasterMeasure->index()) : nullptr; + const Measure* firstMeasure = firstMeasureBase && firstMeasureBase->isMeasure() ? toMeasure(firstMeasureBase) : nullptr; + const Segment* firstCrSeg = firstMeasure ? firstMeasure->first(SegmentType::ChordRest) : nullptr; + if (!firstCrSeg) { + continue; + } + + Note* nextNote = searchTieNote(note, firstCrSeg); + + if (nextNote) { + bool hasIncomingTie = nextNote->tieBack(); + TieJumpPoint* jumpPoint = new TieJumpPoint(nextNote, hasIncomingTie, jumpPointIdx, false); + tieJumpPoints()->add(jumpPoint); + jumpPointIdx++; + } + } + + if (jumpPointIdx < 2 && !isPartialTie()) { + tieJumpPoints()->clear(); + } +} + +void Tie::addTiesToJumpPoints() +{ + updatePossibleJumpPoints(); + TieJumpPointList* jumpPoints = tieJumpPoints(); + if (!jumpPoints) { + return; + } + + for (TieJumpPoint* jumpPoint : *jumpPoints) { + if (jumpPoint->followingNote()) { + jumpPoint->undoSetActive(true); + continue; + } + jumpPoints->addTieToScore(jumpPoint); + } +} + +void Tie::undoRemoveTiesFromJumpPoints() +{ + TieJumpPointList* jumpPoints = tieJumpPoints(); + if (!jumpPoints) { + return; + } + for (TieJumpPoint* jumpPoint : *jumpPoints) { + if (jumpPoint->followingNote() || !jumpPoint->active()) { + jumpPoint->undoSetActive(false); + continue; + } + + jumpPoints->undoRemoveTieFromScore(jumpPoint); + } +} + +bool Tie::allJumpPointsInactive() const +{ + if (endNote()) { + return false; + } + if (!tieJumpPoints()) { + return true; + } + + for (const TieJumpPoint* jumpPoint : *tieJumpPoints()) { + if (jumpPoint->active()) { + return false; + } + } + + return true; +} + +TieJumpPointList* Tie::tieJumpPoints() +{ + return startNote() ? startNote()->tieJumpPoints() : nullptr; +} + +const TieJumpPointList* Tie::tieJumpPoints() const +{ + return startNote() ? startNote()->tieJumpPoints() : nullptr; +} + Tie::Tie(EngravingItem* parent) : SlurTie(ElementType::TIE, parent) { @@ -274,30 +402,18 @@ double Tie::scalingFactor() const return primaryNote->chord()->intrinsicMag(); } -//--------------------------------------------------------- -// setStartNote -//--------------------------------------------------------- - void Tie::setStartNote(Note* note) { setStartElement(note); setParent(note); } -//--------------------------------------------------------- -// startNote -//--------------------------------------------------------- - Note* Tie::startNote() const { assert(!startElement() || startElement()->type() == ElementType::NOTE); return toNote(startElement()); } -//--------------------------------------------------------- -// endNote -//--------------------------------------------------------- - Note* Tie::endNote() const { return toNote(endElement()); @@ -351,4 +467,293 @@ bool Tie::isCrossStaff() const return (startChord && (startChord->staffMove() != 0 || startChord->vStaffIdx() != staff)) || (endChord && (endChord->staffMove() != 0 || endChord->vStaffIdx() != staff)); } + +//--------------------------------------------------------- +// PartialTieJumpPoint +//--------------------------------------------------------- + +TieJumpPoint::TieJumpPoint(Note* note, bool active, int idx, bool followingNote) + : m_note(note), m_active(active), m_followingNote(followingNote) +{ + m_id = u"jumpPoint" + String::fromStdString(std::to_string(idx)); + if (active && endTie()) { + endTie()->setJumpPoint(this); + } +} + +Tie* TieJumpPoint::endTie() const +{ + return m_note ? m_note->tieBack() : nullptr; +} + +void TieJumpPoint::undoSetActive(bool v) +{ + Score* score = m_note ? m_note->score() : nullptr; + if (!score || m_active == v) { + return; + } + score->undo(new ChangeTieJumpPointActive(m_jumpPointList, m_id, v)); +} + +const String TieJumpPoint::menuTitle() const +{ + const Measure* measure = m_note->findMeasure(); + const int measureNo = measure ? measure->no() + 1 : 0; + const TranslatableString tieTo("engraving", "Tie to "); + const String title = tieTo.str + precedingJumpItemName() + u" " + muse::mtrc("engraving", "(m. %1)").arg(measureNo); + + return title; +} + +String TieJumpPoint::precedingJumpItemName() const +{ + const Chord* startChord = m_note->chord(); + const Segment* seg = startChord->segment(); + const Measure* measure = seg->measure(); + + if (seg->score()->firstSegment(SegmentType::ChordRest) == seg) { + return muse::mtrc("engraving", "start of score"); + } + + // Markers + for (const EngravingItem* e : measure->el()) { + if (!e->isMarker()) { + continue; + } + + const Marker* marker = toMarker(e); + if (muse::contains(Marker::RIGHT_MARKERS, marker->markerType())) { + continue; + } + + if (marker->markerType() == MarkerType::CODA || marker->markerType() == MarkerType::VARCODA) { + return muse::mtrc("engraving", "coda"); + } else { + return muse::mtrc("engraving", "segno"); + } + } + + // Voltas + auto spanners = m_note->score()->spannerMap().findOverlapping(measure->tick().ticks(), measure->tick().ticks()); + for (auto& spanner : spanners) { + if (!spanner.value->isVolta() || Fraction::fromTicks(spanner.start) != startChord->tick()) { + continue; + } + + Volta* volta = toVolta(spanner.value); + + return muse::mtrc("engraving", "“%1” volta").arg(volta->beginText()); + } + + // Repeat barlines + if (measure->repeatStart()) { + return muse::mtrc("engraving", "start repeat"); + } + + for (Segment* prevSeg = seg->prev(SegmentType::BarLineType); prevSeg && prevSeg->tick() == seg->tick(); + prevSeg = prevSeg->prev(SegmentType::BarLineType)) { + EngravingItem* el = prevSeg->element(startChord->track()); + if (!el || !el->isBarLine()) { + continue; + } + + BarLine* bl = toBarLine(el); + if (bl->barLineType() & (BarLineType::START_REPEAT | BarLineType::END_START_REPEAT)) { + return muse::mtrc("engraving", "start repeat"); + } + } + + if (m_note->tieBack() && m_note->tieBack()->startNote()) { + return muse::mtrc("engraving", "next note"); + } + + return muse::mtrc("engraving", "invalid"); +} + +//--------------------------------------------------------- +// PartialTieJumpPointList +//--------------------------------------------------------- + +TieJumpPointList::~TieJumpPointList() +{ + muse::DeleteAll(m_jumpPoints); + m_jumpPoints.clear(); +} + +void TieJumpPointList::add(TieJumpPoint* item) +{ + item->setJumpPointList(this); + m_jumpPoints.push_back(item); +} + +void TieJumpPointList::clear() +{ + for (const TieJumpPoint* jumpPoint : m_jumpPoints) { + Tie* endTie = jumpPoint->endTie(); + if (!endTie) { + continue; + } + endTie->setJumpPoint(nullptr); + } + muse::DeleteAll(m_jumpPoints); + m_jumpPoints.clear(); +} + +TieJumpPoint* TieJumpPointList::findJumpPoint(const String& id) +{ + for (TieJumpPoint* jumpPoint : m_jumpPoints) { + if (jumpPoint->id() != id) { + continue; + } + + return jumpPoint; + } + return nullptr; +} + +void TieJumpPointList::toggleJumpPoint(const String& id) +{ + TieJumpPoint* end = findJumpPoint(id); + + if (!end) { + LOGE() << "No partial tie end point found with id: " << id; + return; + } + + Score* score = end->note() ? end->note()->score() : nullptr; + if (!score) { + return; + } + + score->startCmd(TranslatableString("engraving", "Toggle partial tie")); + const bool checked = end->active(); + if (checked) { + undoRemoveTieFromScore(end); + } else { + addTieToScore(end); + } + score->endCmd(); +} + +void TieJumpPointList::addTieToScore(TieJumpPoint* jumpPoint) +{ + Note* note = jumpPoint->note(); + Score* score = note ? note->score() : nullptr; + if (!m_startTie || !score) { + return; + } + + if (jumpPoint->followingNote()) { + // Remove partial tie and add full tie + if (!m_startTie->isPartialTie() || !toPartialTie(m_startTie)->isOutgoing()) { + return; + } + jumpPoint->undoSetActive(true); + m_startTie = Tie::changeTieType(m_startTie, note); + return; + } + + jumpPoint->undoSetActive(true); + + // Check if there is already a tie. If so, add partial tie info to it + Tie* tieBack = note->tieBack(); + if (tieBack && !tieBack->isPartialTie()) { + tieBack->setJumpPoint(jumpPoint); + return; + } + // Otherwise create incoming partial tie on note + PartialTie* pt = Factory::createPartialTie(note); + pt->setParent(note); + pt->setEndNote(note); + pt->setJumpPoint(jumpPoint); + score->undoAddElement(pt); +} + +void TieJumpPointList::undoRemoveTieFromScore(TieJumpPoint* jumpPoint) +{ + Note* note = jumpPoint->note(); + Score* score = note ? note->score() : nullptr; + if (!m_startTie || !score) { + return; + } + + if (jumpPoint->followingNote()) { + // Remove full tie and add partial tie + if (m_startTie->isPartialTie()) { + return; + } + jumpPoint->undoSetActive(false); + + m_startTie = Tie::changeTieType(m_startTie); + return; + } + + jumpPoint->undoSetActive(false); + + // Check if there is a full tie. If so, remove partial tie info from it + Tie* tieBack = note->tieBack(); + if (tieBack && !tieBack->isPartialTie()) { + tieBack->setJumpPoint(nullptr); + return; + } + // Otherwise remove incoming partial tie on note + PartialTie* pt = note->incomingPartialTie(); + if (!pt) { + return; + } + score->undoRemoveElement(pt); +} + +Tie* Tie::changeTieType(Tie* oldTie, Note* endNote) +{ + // Replaces oldTie with an outgoing partial tie if no endNote is specified. Otherwise replaces oldTie with a regular tie + Note* startNote = oldTie->startNote(); + bool addPartialTie = !endNote; + Score* score = startNote ? startNote->score() : nullptr; + if (!score) { + return nullptr; + } + + TranslatableString undoCmd = addPartialTie ? TranslatableString("engraving", "Replace full tie with partial tie") : TranslatableString( + "engraving", "Replace partial tie with full tie"); + Tie* newTie = addPartialTie ? Factory::createPartialTie(score->dummy()->note()) : Factory::createTie(score->dummy()->note()); + + score->undoRemoveElement(oldTie); + + newTie->setParent(startNote); + newTie->setStartNote(startNote); + startNote->setTieFor(newTie); + if (!addPartialTie) { + newTie->setEndNote(endNote); + endNote->setTieBack(newTie); + } + + newTie->setTick(startNote->tick()); + newTie->setTrack(startNote->track()); + + newTie->setStyleType(oldTie->styleType()); + newTie->setTiePlacement(oldTie->tiePlacement()); + newTie->setSlurDirection(oldTie->slurDirection()); + + newTie->setVisible(oldTie->visible()); + newTie->setOffset(oldTie->offset()); + + score->undoAddElement(newTie); + + score->endCmd(); + + return newTie; +} + +void Tie::updateStartTieOnRemoval() +{ + if (!jumpPoint() || !startTie() || !startTieJumpPoints()) { + return; + } + jumpPoint()->undoSetActive(false); + Tie* _startTie = startTie(); + if (startTieJumpPoints()->size() <= 1 || _startTie->allJumpPointsInactive()) { + score()->undoRemoveElement(_startTie); + } +} } diff --git a/src/engraving/dom/tie.h b/src/engraving/dom/tie.h index ea05f3f75efbb..d2ca9e4c3c81c 100644 --- a/src/engraving/dom/tie.h +++ b/src/engraving/dom/tie.h @@ -25,6 +25,64 @@ #include "slurtie.h" namespace mu::engraving { +class TieJumpPointList; +class TieJumpPoint +{ +public: + TieJumpPoint(Note* note, bool active, int idx, bool followingNote); + TieJumpPoint() {} + + Note* note() const { return m_note; } + Tie* endTie() const; + bool followingNote() const { return m_followingNote; } + const String& id() const { return m_id; } + bool active() const { return m_active; } + void setActive(bool v) { m_active = v; } + void undoSetActive(bool v); + void setJumpPointList(TieJumpPointList* jumpPointList) { m_jumpPointList = jumpPointList; } + TieJumpPointList* jumpPointList() { return m_jumpPointList; } + + const String menuTitle() const; + +private: + String precedingJumpItemName() const; + Note* m_note = nullptr; + bool m_active = false; + String m_id; + TieJumpPointList* m_jumpPointList = nullptr; + bool m_followingNote = false; +}; + +class TieJumpPointList +{ +public: + TieJumpPointList() = default; + ~TieJumpPointList(); + + void add(TieJumpPoint* item); + void clear(); + size_t size() const { return m_jumpPoints.size(); } + bool empty() const { return m_jumpPoints.empty(); } + + void setStartTie(Tie* startTie) { m_startTie = startTie; } + Tie* startTie() const { return m_startTie; } + + TieJumpPoint* findJumpPoint(const String& id); + void toggleJumpPoint(const String& id); + + void addTieToScore(TieJumpPoint* jumpPoint); + void undoRemoveTieFromScore(TieJumpPoint* jumpPoint); + + std::vector::iterator begin() { return m_jumpPoints.begin(); } + std::vector::const_iterator begin() const { return m_jumpPoints.begin(); } + std::vector::iterator end() { return m_jumpPoints.end(); } + std::vector::const_iterator end() const { return m_jumpPoints.end(); } + +private: + std::vector m_jumpPoints; + Tie* m_startTie = nullptr; +}; + //--------------------------------------------------------- // @@ TieSegment /// a single segment of a tie @@ -62,12 +120,16 @@ class TieSegment : public SlurTieSegment double midWidth() const override; double dottedWidth() const override; + struct LayoutData : public SlurTieSegment::LayoutData { + bool allJumpPointsInactive = false; + }; + DECLARE_LAYOUTDATA_METHODS(TieSegment) + protected: TieSegment(const ElementType& type, System* parent); void changeAnchor(EditData&, EngravingItem*) override; private: - int m_staffMove = 0; std::array(Grip::GRIPS)> m_adjustmentOffsets; }; @@ -89,8 +151,8 @@ class Tie : public SlurTie virtual ~Tie() {} - Note* startNote() const; - void setStartNote(Note* note); + virtual Note* startNote() const; + virtual void setStartNote(Note* note); virtual Note* endNote() const; virtual void setEndNote(Note* note) { setEndElement((EngravingItem*)note); } @@ -115,14 +177,30 @@ class Tie : public SlurTie double scalingFactor() const override; + // Outgoing ties before repeats + void updatePossibleJumpPoints(); + void addTiesToJumpPoints(); + void undoRemoveTiesFromJumpPoints(); + virtual bool allJumpPointsInactive() const; + virtual TieJumpPointList* tieJumpPoints(); + virtual const TieJumpPointList* tieJumpPoints() const; + + // Incoming ties after repeats + void setJumpPoint(TieJumpPoint* jumpPoint) { m_jumpPoint = jumpPoint; } + void updateStartTieOnRemoval(); + TieJumpPoint* jumpPoint() const { return m_jumpPoint; } + Tie* startTie() const { return startTieJumpPoints() ? startTieJumpPoints()->startTie() : nullptr; } + + static Tie* changeTieType(Tie* oldTie, Note* endNote = nullptr); + protected: Tie(const ElementType& type, EngravingItem* parent = nullptr); bool m_isInside = false; M_PROPERTY2(TiePlacement, tiePlacement, setTiePlacement, TiePlacement::AUTO) -private: - static Note* editStartNote; - static Note* editEndNote; + // Jump point information for incoming ties after repeats + TieJumpPoint* m_jumpPoint = nullptr; + TieJumpPointList* startTieJumpPoints() const { return m_jumpPoint ? m_jumpPoint->jumpPointList() : nullptr; } }; } // namespace mu::engraving diff --git a/src/engraving/dom/tremolotwochord.cpp b/src/engraving/dom/tremolotwochord.cpp index ea072c22a337c..c0adff3dc784c 100644 --- a/src/engraving/dom/tremolotwochord.cpp +++ b/src/engraving/dom/tremolotwochord.cpp @@ -354,15 +354,6 @@ std::vector TremoloTwoChord::gripsPositions(const EditData&) const }; } -//--------------------------------------------------------- -// endEdit -//--------------------------------------------------------- - -void TremoloTwoChord::endEdit(EditData& ed) -{ - EngravingItem::endEdit(ed); -} - //--------------------------------------------------------- // editDrag //--------------------------------------------------------- diff --git a/src/engraving/dom/tremolotwochord.h b/src/engraving/dom/tremolotwochord.h index 708de9682e891..58fe906de16f6 100644 --- a/src/engraving/dom/tremolotwochord.h +++ b/src/engraving/dom/tremolotwochord.h @@ -111,7 +111,6 @@ class TremoloTwoChord final : public BeamBase std::vector gripsPositions(const EditData&) const override; bool isMovable() const override { return true; } bool isEditable() const override { return true; } - void endEdit(EditData&) override; void editDrag(EditData&) override; void clearBeamSegments() override; diff --git a/src/engraving/dom/types.h b/src/engraving/dom/types.h index 7a7a4471e2560..b84ccd5f444fc 100644 --- a/src/engraving/dom/types.h +++ b/src/engraving/dom/types.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_TYPES_OLD_H -#define MU_ENGRAVING_TYPES_OLD_H +#pragma once #include @@ -116,6 +115,9 @@ enum class CommandType { InsertTimeUnmanagedSpanner, ChangeStartEndSpanner, + // Ties + ChangeTieEndPointActive, + // Style ChangeStyle, ChangeStyleValues, @@ -538,5 +540,3 @@ Q_DECLARE_METATYPE(mu::engraving::NoteType) Q_DECLARE_METATYPE(mu::engraving::PlayEventType) Q_DECLARE_METATYPE(mu::engraving::AccidentalType) #endif - -#endif diff --git a/src/engraving/dom/undo.cpp b/src/engraving/dom/undo.cpp index fc6786c1fea32..7af1d4d2af31e 100644 --- a/src/engraving/dom/undo.cpp +++ b/src/engraving/dom/undo.cpp @@ -59,6 +59,7 @@ #include "noteevent.h" #include "page.h" #include "part.h" +#include "partialtie.h" #include "rest.h" #include "score.h" #include "segment.h" @@ -1008,11 +1009,17 @@ std::vector AddElement::objectItems() const static void removeNote(const Note* note) { Score* score = note->score(); - if (note->tieFor() && note->tieFor()->endNote()) { - score->doUndoRemoveElement(note->tieFor()); - } - if (note->tieBack()) { - score->doUndoRemoveElement(note->tieBack()); + Tie* tieFor = note->tieFor(); + Tie* tieBack = note->tieBack(); + if (tieFor && tieFor->endNote()) { + score->doUndoRemoveElement(tieFor); + } + if (tieBack) { + if (tieBack->tieJumpPoints() && tieBack->tieJumpPoints()->size() > 1) { + Tie::changeTieType(tieBack); + } else { + score->doUndoRemoveElement(tieBack); + } } for (Spanner* s : note->spannerBack()) { score->doUndoRemoveElement(s); @@ -3354,3 +3361,15 @@ std::vector RemoveSystemLock::objectItems() const { return { m_systemLock->startMB(), m_systemLock->endMB() }; } + +void ChangeTieJumpPointActive::flip(EditData*) +{ + TieJumpPoint* jumpPoint = m_jumpPointList->findJumpPoint(m_id); + if (!jumpPoint) { + return; + } + bool oldActive = jumpPoint->active(); + + jumpPoint->setActive(m_active); + m_active = oldActive; +} diff --git a/src/engraving/dom/undo.h b/src/engraving/dom/undo.h index e2ca09bf1924d..89b56cdafb313 100644 --- a/src/engraving/dom/undo.h +++ b/src/engraving/dom/undo.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_UNDO_H -#define MU_ENGRAVING_UNDO_H +#pragma once /** \file @@ -1733,5 +1732,22 @@ class ChangeSpanArpeggio : public UndoCommand UNDO_NAME("ChangeSpanArpeggio") UNDO_CHANGED_OBJECTS({ m_chord }) }; + +class ChangeTieJumpPointActive : public UndoCommand +{ + OBJECT_ALLOCATOR(engraving, ChangeTieJumpPointActive) + + TieJumpPointList* m_jumpPointList = nullptr; + String m_id; + bool m_active = false; + + void flip(EditData*) override; + +public: + ChangeTieJumpPointActive(TieJumpPointList* jumpPointList, String& id, bool active) + : m_jumpPointList(jumpPointList), m_id(id), m_active(active) {} + + UNDO_TYPE(CommandType::ChangeTieEndPointActive) + UNDO_NAME("ChangeTieEndPointActive") +}; } // namespace mu::engraving -#endif diff --git a/src/engraving/dom/utils.cpp b/src/engraving/dom/utils.cpp index 8ae34ed3541f5..8a0a8b3377088 100644 --- a/src/engraving/dom/utils.cpp +++ b/src/engraving/dom/utils.cpp @@ -769,24 +769,58 @@ int diatonicUpDown(Key k, int pitch, int steps) return pitch; } +Volta* findVolta(const Segment* seg, const Score* score) +{ + const Measure* measure = seg->measure(); + const Fraction tick = measure->tick() + Fraction::eps(); + auto spanners = score->spannerMap().findOverlapping(tick.ticks(), tick.ticks()); + for (auto& spanner : spanners) { + if (!spanner.value->isVolta()) { + continue; + } + return toVolta(spanner.value); + } + return nullptr; +} + //--------------------------------------------------------- // searchTieNote // search Note to tie to "note" //--------------------------------------------------------- -Note* searchTieNote(Note* note) +Note* searchTieNote(const Note* note, const Segment* nextSegment) { if (!note) { return nullptr; } - Note* note2 = 0; + Note* note2 = nullptr; Chord* chord = note->chord(); Segment* seg = chord->segment(); Part* part = chord->part(); + Score* score = chord->score(); track_idx_t strack = part->staves().front()->idx() * VOICES; track_idx_t etrack = strack + part->staves().size() * VOICES; + if (!nextSegment) { + const Fraction nextTick = chord->tick() + chord->actualTicks(); + nextSegment = seg->next1(SegmentType::ChordRest); + while (nextSegment && nextSegment->tick() < nextTick) { + nextSegment = nextSegment->next1(SegmentType::ChordRest); + } + } + + if (!nextSegment) { + return nullptr; + } + + Volta* startVolta = findVolta(seg, score); + Volta* endVolta = findVolta(nextSegment, score); + + if (startVolta && endVolta && startVolta != endVolta) { + return nullptr; + } + if (chord->isGraceBefore()) { chord = toChord(chord->explicitParent()); @@ -797,7 +831,6 @@ Note* searchTieNote(Note* note) if (c->graceIndex() == index + 1) { note2 = c->findNote(note->pitch()); if (note2) { -//printf("found grace-grace tie\n"); return note2; } } @@ -828,54 +861,40 @@ Note* searchTieNote(Note* note) // at this point, chord is a regular chord, not a grace chord // and we are looking for a note in the *next* chord (grace or regular) - // calculate end of current note duration - // but err on the safe side in case there is roundoff in tick count - Fraction endTick = chord->tick() + chord->actualTicks() - Fraction(1, 4 * 480); - int idx1 = note->unisonIndex(); - while ((seg = seg->next1(SegmentType::ChordRest))) { - // skip ahead to end of current note duration as calculated above - // but just in case, stop if we find element in current track - if (seg->tick() < endTick && !seg->element(chord->track())) { + for (track_idx_t track = strack; track < etrack; ++track) { + EngravingItem* e = nextSegment->element(track); + if (!e || !e->isChord()) { continue; } - for (track_idx_t track = strack; track < etrack; ++track) { - EngravingItem* e = seg->element(track); - if (e == 0 || !e->isChord()) { - continue; - } - Chord* c = toChord(e); - const staff_idx_t staffIdx = c->staffIdx() + c->staffMove(); - if (staffIdx != chord->staffIdx() + chord->staffMove()) { - // this check is needed as we are iterating over all staves to capture cross-staff chords - continue; - } - // if there are grace notes before, try to tie to first one - std::vector gnb = c->graceNotesBefore(); - if (!gnb.empty()) { - Chord* gc = gnb[0]; - Note* gn2 = gc->findNote(note->pitch()); - if (gn2) { - return gn2; - } + Chord* c = toChord(e); + const staff_idx_t staffIdx = c->staffIdx() + c->staffMove(); + if (staffIdx != chord->staffIdx() + chord->staffMove()) { + // this check is needed as we are iterating over all staves to capture cross-staff chords + continue; + } + // if there are grace notes before, try to tie to first one + std::vector gnb = c->graceNotesBefore(); + if (!gnb.empty()) { + Chord* gc = gnb[0]; + Note* gn2 = gc->findNote(note->pitch()); + if (gn2) { + return gn2; } - int idx2 = 0; - for (Note* n : c->notes()) { - if (n->pitch() == note->pitch()) { - if (idx1 == idx2) { - if (note2 == 0 || c->track() == chord->track()) { - note2 = n; - break; - } - } else { - ++idx2; + } + int idx2 = 0; + for (Note* n : c->notes()) { + if (n->pitch() == note->pitch()) { + if (idx1 == idx2) { + if (!note2 || c->track() == chord->track()) { + note2 = n; + break; } + } else { + ++idx2; } } } - if (note2) { - break; - } } return note2; } diff --git a/src/engraving/dom/utils.h b/src/engraving/dom/utils.h index f90de490d5a61..de96bcfa43db6 100644 --- a/src/engraving/dom/utils.h +++ b/src/engraving/dom/utils.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_UTILS_H -#define MU_ENGRAVING_UTILS_H +#pragma once #include "../types/types.h" @@ -35,9 +34,11 @@ class EngravingItem; class KeySig; class Note; class Rest; +class Score; class Segment; class System; class Tuplet; +class Volta; enum class Key; @@ -67,7 +68,8 @@ extern Note* prevChordNote(Note* note); extern Segment* nextSeg1(Segment* s); extern Segment* prevSeg1(Segment* seg); -extern Note* searchTieNote(Note* note); +extern Volta* findVolta(const Segment* seg, const Score* score); +extern Note* searchTieNote(const Note* note, const Segment* nextSegment = nullptr); extern Note* searchTieNote114(Note* note); extern int absStep(int pitch); @@ -103,4 +105,3 @@ extern String bendAmountToString(int fulls, int quarts); extern InstrumentTrackId makeInstrumentTrackId(const EngravingItem* item); } // namespace mu::engraving -#endif diff --git a/src/engraving/playback/renderers/arpeggiorenderer.cpp b/src/engraving/playback/renderers/arpeggiorenderer.cpp index 8ce569a1cbcc3..a1a6845a243cc 100644 --- a/src/engraving/playback/renderers/arpeggiorenderer.cpp +++ b/src/engraving/playback/renderers/arpeggiorenderer.cpp @@ -117,7 +117,7 @@ std::map ArpeggioRenderer::arpeggioNotes(const Ch } NominalNoteCtx noteCtx(note, ctx); - if (note->tieFor()) { + if (note->tieForNonPartial()) { noteCtx.duration = tiedNotesTotalDuration(ctx.score, note, noteCtx.duration, ctx.positionTickOffset); } diff --git a/src/engraving/rendering/score/chordlayout.cpp b/src/engraving/rendering/score/chordlayout.cpp index db707ad778844..ef662b3eb1a95 100644 --- a/src/engraving/rendering/score/chordlayout.cpp +++ b/src/engraving/rendering/score/chordlayout.cpp @@ -2763,17 +2763,26 @@ void ChordLayout::updateLineAttachPoints(Chord* chord, bool isFirstInMeasure, La if (isFirstInMeasure) { for (Note* note : chord->notes()) { Tie* tieBack = note->tieBack(); - if (tieBack && tieBack->startNote()->findMeasure() != note->findMeasure()) { - SlurTieLayout::tieLayoutBack(tieBack, note->findMeasure()->system(), ctx); + if (tieBack && (note->incomingPartialTie() || tieBack->startNote()->findMeasure() != note->findMeasure())) { + SlurTieLayout::layoutTieBack(tieBack, note->findMeasure()->system(), ctx); } } } for (Note* note : chord->notes()) { Tie* tie = note->tieFor(); if (tie) { + if (tie->isPartialTie()) { + SlurTieLayout::layoutTieFor(tie, note->findMeasure()->system()); // line attach points are updated here + } + Note* endNote = tie->endNote(); - if (endNote && endNote->findMeasure() == note->findMeasure()) { - SlurTieLayout::tieLayoutFor(tie, note->findMeasure()->system()); // line attach points are updated here + if (!endNote) { + continue; + } + const Measure* endNoteMeasure = endNote->findMeasure(); + const Measure* noteMeasure = note->findMeasure(); + if (endNoteMeasure == noteMeasure || endNoteMeasure->system() != noteMeasure->system()) { + SlurTieLayout::layoutTieFor(tie, note->findMeasure()->system()); // line attach points are updated here } } } @@ -3545,6 +3554,6 @@ void ChordLayout::addLineAttachPoints(Spanner* spanner) double endX = backSeg->pos2().x() + backSeg->ldata()->pos().x(); // because pos2 is relative to ipos // Here we don't pass y() because its value is unreliable during the first stages of layout. // The y() is irrelevant anyway for horizontal spacing. - startNote->addLineAttachPoint(PointF(startX, 0.0), spanner); - endNote->addLineAttachPoint(PointF(endX, 0.0), spanner); + startNote->addStartLineAttachPoint(PointF(startX, 0.0), spanner); + endNote->addEndLineAttachPoint(PointF(endX, 0.0), spanner); } diff --git a/src/engraving/rendering/score/guitarbendlayout.cpp b/src/engraving/rendering/score/guitarbendlayout.cpp index f1a9966b69764..47985578a1e9f 100644 --- a/src/engraving/rendering/score/guitarbendlayout.cpp +++ b/src/engraving/rendering/score/guitarbendlayout.cpp @@ -188,10 +188,10 @@ void GuitarBendLayout::layoutAngularBend(GuitarBendSegment* item, LayoutContext& item->mutldata()->setVertexPoint(vertex); if (item->isSingleBeginType()) { - startNote->addLineAttachPoint(item->pos(), bend); + startNote->addStartLineAttachPoint(item->pos(), bend); } if (item->isSingleEndType()) { - endNote->addLineAttachPoint(item->pos() + item->pos2(), bend); + endNote->addEndLineAttachPoint(item->pos() + item->pos2(), bend); } PainterPath path; diff --git a/src/engraving/rendering/score/horizontalspacing.cpp b/src/engraving/rendering/score/horizontalspacing.cpp index 196a4b734bb26..db7ee2b725625 100644 --- a/src/engraving/rendering/score/horizontalspacing.cpp +++ b/src/engraving/rendering/score/horizontalspacing.cpp @@ -26,7 +26,6 @@ #include "dom/chord.h" #include "dom/engravingitem.h" #include "dom/glissando.h" -#include "dom/laissezvib.h" #include "dom/lyrics.h" #include "dom/note.h" #include "dom/rest.h" @@ -1022,44 +1021,20 @@ double HorizontalSpacing::minHorizontalDistance(const Segment* f, const Segment* } } - // Allocate space to ensure minimum length of "dangling" ties or gliss at start of system - if ((systemHeaderGap || f->isStartRepeatBarLineType()) && ns && ns->isChordRestType()) { - for (EngravingItem* e : ns->elist()) { - if (!e || !e->isChord()) { - continue; - } - double headerTieMargin = systemHeaderGap ? f->style().styleMM(Sid::headerToLineStartDistance) - : f->style().styleMM(Sid::repeatBarlineDotSeparation); - for (Note* note : toChord(e)->notes()) { - bool tieOrGlissBack = note->spannerBack().size() || (note->tieBack() && !note->tieBack()->segmentsEmpty()); - if (!tieOrGlissBack || note->lineAttachPoints().empty()) { - continue; - } - const EngravingItem* attachedLine = note->lineAttachPoints().front().line(); - if (!attachedLine->addToSkyline()) { - continue; - } - double minLength = 0.0; - if (attachedLine->isTie()) { - minLength = f->style().styleMM(Sid::minTieLength); - } else if (attachedLine->isGlissando()) { - bool straight = toGlissando(attachedLine)->glissandoType() == GlissandoType::STRAIGHT; - minLength = straight ? f->style().styleMM(Sid::minStraightGlissandoLength) - : f->style().styleMM(Sid::minWigglyGlissandoLength); - } else if (attachedLine->isNoteLine()) { - minLength = f->style().styleMM(Sid::minStraightGlissandoLength); - } - double tieStartPointX = f->minRight() + headerTieMargin; - double notePosX = w + note->pos().x() + toChord(e)->pos().x() + note->headWidth() / 2; - double tieEndPointX = notePosX + note->lineAttachPoints().at(0).pos().x(); - double tieLength = tieEndPointX - tieStartPointX; - if (tieLength < minLength) { - w += minLength - tieLength; - } - } - } + // Allocate space to ensure minimum length of hanging ties or gliss at start of system + // These only occur when one segment is a ChordRest and the other isn't + // Allocate space to ensure minimum length of partial ties + if (f->isChordRestType() == ns->isChordRestType()) { + return w; } + const bool repeatSeg = f->isStartRepeatBarLineType() || ns->isStartRepeatBarLineType(); + const bool endOfSystem = f->isChordRestType() && !(ns->isChordRestType() || ns->isStartRepeatBarLineType()) + && (f->measure()->isLastInSystem() || f->measure()->next()->isHBox()); + const bool systemEnd = repeatSeg || endOfSystem; + + computeHangingLineWidth(f, ns, w, systemHeaderGap, systemEnd); + return w; } @@ -1403,3 +1378,75 @@ KerningType HorizontalSpacing::computeLyricsKerningType(const Lyrics* lyrics1, c return KerningType::ALLOW_COLLISION; } + +void HorizontalSpacing::computeHangingLineWidth(const Segment* firstSeg, const Segment* nextSeg, double& width, bool systemHeaderGap, + bool systemEnd) +{ + const MStyle& style = firstSeg->style(); + const Segment* crSeg = firstSeg->isChordRestType() ? firstSeg : nextSeg; + const Segment* otherSeg = firstSeg->isChordRestType() ? nextSeg : firstSeg; + const bool incoming = !firstSeg->isChordRestType(); + const Measure* crMeasure = crSeg->measure(); + const size_t ntracks = crSeg->score()->ntracks(); + + for (track_idx_t track = 0; track < ntracks; track++) { + // Hanging lines only occur at start or end of a measure + const ChordRest* cr = incoming ? crMeasure->firstChordRest(track) : crMeasure->lastChordRest(track); + if (!cr || !cr->isChord() || cr->segment() != crSeg) { + continue; + } + + const double headerLineMargin = systemHeaderGap ? otherSeg->style().styleMM(Sid::headerToLineStartDistance) + : otherSeg->style().styleMM(Sid::repeatBarlineDotSeparation); + const double endSystemMargin = style.styleMM(Sid::lineEndToSystemEndDistance); + + for (const Note* note : toChord(cr)->notes()) { + const bool lineBack = note->spannerBack().size() + || (note->tieBack() && !note->tieBack()->segmentsEmpty()); + const bool lineFor = note->spannerFor().size() + || (note->tieFor() && !note->tieFor()->segmentsEmpty()); + if (!(lineBack || lineFor) || note->lineAttachPoints().empty()) { + continue; + } + + for (const LineAttachPoint& lap : note->lineAttachPoints()) { + if (lap.start() == incoming) { + continue; + } + const Spanner* attachedLine = toSpanner(lap.line()); + if (!attachedLine->addToSkyline()) { + continue; + } + + // Partial ties are adjusted wherever they are in the system, other spanners are only adjusted over system breaks + if (!(systemEnd || systemHeaderGap) && !attachedLine->isPartialTie()) { + continue; + } + + double minLength = 0.0; + if (attachedLine->isTie()) { + minLength = style.styleMM(Sid::minHangingTieLength); + } else if (attachedLine->isGlissando()) { + bool straight = toGlissando(attachedLine)->glissandoType() == GlissandoType::STRAIGHT; + minLength = straight ? style.styleMM(Sid::minStraightGlissandoLength) + : style.styleMM(Sid::minWigglyGlissandoLength); + } else if (attachedLine->isNoteLine()) { + minLength = style.styleMM(Sid::minStraightGlissandoLength); + } + + const double notePosX = note->pos().x() + toChord(cr)->pos().x() + note->headWidth() / 2; + const double lineNoteEndPos = (incoming ? width : 0.0) + notePosX + lap.pos().x(); + const double lineSegEndPos + = (incoming ? otherSeg->minRight() + headerLineMargin : width + otherSeg->minLeft() - endSystemMargin); + + const double lineStartPointX = incoming ? lineSegEndPos : lineNoteEndPos; + const double lineEndPointX = incoming ? lineNoteEndPos : lineSegEndPos; + const double lineLength = lineEndPointX - lineStartPointX; + + if (lineLength < minLength) { + width += minLength - lineLength; + } + } + } + } +} diff --git a/src/engraving/rendering/score/horizontalspacing.h b/src/engraving/rendering/score/horizontalspacing.h index fa36a59aa516a..6995715c58e31 100644 --- a/src/engraving/rendering/score/horizontalspacing.h +++ b/src/engraving/rendering/score/horizontalspacing.h @@ -19,8 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_HORIZONTALSPACINGUTILS_DEV_H -#define MU_ENGRAVING_HORIZONTALSPACINGUTILS_DEV_H +#pragma once #include "types/fraction.h" @@ -141,6 +140,8 @@ class HorizontalSpacing static KerningType computeNoteKerningType(const Note* note, const EngravingItem* item2); static KerningType computeStemSlashKerningType(const StemSlash* stemSlash, const EngravingItem* item2); static KerningType computeLyricsKerningType(const Lyrics* lyrics1, const EngravingItem* item2); + + static void computeHangingLineWidth(const Segment* firstSeg, const Segment* nextSeg, double& width, bool systemHeaderGap, + bool systemEnd); }; } // namespace mu::engraving::layout -#endif // MU_ENGRAVING_HORIZONTALSPACINGUTILS_DEV_H diff --git a/src/engraving/rendering/score/measurelayout.cpp b/src/engraving/rendering/score/measurelayout.cpp index a5443d2c0f5bc..e9210cee398f7 100644 --- a/src/engraving/rendering/score/measurelayout.cpp +++ b/src/engraving/rendering/score/measurelayout.cpp @@ -128,10 +128,10 @@ void MeasureLayout::layout2(Measure* item, LayoutContext& ctx) SlurTieLayout::layoutLaissezVibChord(chord, ctx); } if (tieFor && !lv && tieFor->isCrossStaff()) { - SlurTieLayout::tieLayoutFor(tieFor, item->system()); + SlurTieLayout::layoutTieFor(tieFor, item->system()); } if (tieBack && tieBack->tick() < stick && tieBack->isCrossStaff()) { - SlurTieLayout::tieLayoutBack(tieBack, item->system(), ctx); + SlurTieLayout::layoutTieBack(tieBack, item->system(), ctx); } } } diff --git a/src/engraving/rendering/score/slurtielayout.cpp b/src/engraving/rendering/score/slurtielayout.cpp index 8f651858e3b68..c09c046b83324 100644 --- a/src/engraving/rendering/score/slurtielayout.cpp +++ b/src/engraving/rendering/score/slurtielayout.cpp @@ -42,6 +42,7 @@ #include "dom/measure.h" #include "dom/guitarbend.h" #include "dom/laissezvib.h" +#include "dom/partialtie.h" #include "tlayout.h" #include "chordlayout.h" @@ -1368,7 +1369,7 @@ static bool tieSegmentShouldBeSkipped(Tie* item) return showTiedFret == ShowTiedFret::NONE; } -TieSegment* SlurTieLayout::tieLayoutFor(Tie* item, System* system) +TieSegment* SlurTieLayout::layoutTieFor(Tie* item, System* system) { item->setPos(0, 0); @@ -1386,6 +1387,10 @@ TieSegment* SlurTieLayout::tieLayoutFor(Tie* item, System* system) return nullptr; } + if (item->isPartialTie()) { + return layoutPartialTie(toPartialTie(item)); + } + calculateDirection(item); calculateIsInside(item); @@ -1410,6 +1415,7 @@ TieSegment* SlurTieLayout::tieLayoutFor(Tie* item, System* system) segment->setSpannerSegmentType(sPos.system1 != sPos.system2 ? SpannerSegmentType::BEGIN : SpannerSegmentType::SINGLE); segment->setSystem(system); // Needed to populate System.spannerSegments segment->resetAdjustmentOffset(); + segment->mutldata()->allJumpPointsInactive = item->allJumpPointsInactive(); const Chord* startChord = item->startNote()->chord(); item->setTick(startChord->tick()); // Why is this here?? (M.S.) @@ -1436,7 +1442,7 @@ TieSegment* SlurTieLayout::tieLayoutFor(Tie* item, System* system) return segment; } -TieSegment* SlurTieLayout::tieLayoutBack(Tie* item, System* system, LayoutContext& ctx) +TieSegment* SlurTieLayout::layoutTieBack(Tie* item, System* system, LayoutContext& ctx) { Chord* chord = item->endNote() ? item->endNote()->chord() : nullptr; @@ -1453,6 +1459,10 @@ TieSegment* SlurTieLayout::tieLayoutBack(Tie* item, System* system, LayoutContex return nullptr; } + if (item->isPartialTie()) { + return layoutPartialTie(toPartialTie(item)); + } + SlurTiePos sPos; computeStartAndEndSystem(item, sPos); sPos.p2 = computeDefaultStartOrEndPoint(item, Grip::END); @@ -1495,18 +1505,22 @@ TieSegment* SlurTieLayout::tieLayoutBack(Tie* item, System* system, LayoutContex void SlurTieLayout::computeStartAndEndSystem(Tie* item, SlurTiePos& slurTiePos) { - Chord* startChord = item->startNote()->chord(); + Chord* startChord = item->startNote() ? item->startNote()->chord() : nullptr; Chord* endChord = item->endNote() ? item->endNote()->chord() : nullptr; - System* startSystem = startChord->measure()->system(); - - if (!startSystem) { - Measure* m = startChord->measure(); - LOGD("No system: measure is %d has %d count %d", m->isMMRest(), m->hasMMRest(), m->mmRestCount()); - } + System* startSystem = startChord ? startChord->measure()->system() : nullptr; System* endSystem = endChord ? endChord->measure()->system() : startSystem; + if (!startSystem && !endSystem) { + if (startChord) { + Measure* m = startChord->measure(); + LOGD("No system: measure is %d has %d count %d", m->isMMRest(), m->hasMMRest(), m->mmRestCount()); + } else { + LOGD("No start or end system for tie at ") << item->tick().toString(); + } + } + slurTiePos.system1 = startSystem; slurTiePos.system2 = endSystem; } @@ -1683,6 +1697,97 @@ void SlurTieLayout::calculateLaissezVibY(LaissezVibSegment* segment, SlurTiePos& } } +PartialTieSegment* SlurTieLayout::createPartialTieSegment(PartialTie* item) +{ + Chord* chord = item->isOutgoing() ? item->startNote()->chord() : item->endNote()->chord(); + item->setTick(chord->tick()); + + calculateDirection(item); + calculateIsInside(item); + + item->fixupSegments(1); + PartialTieSegment* segment = item->segmentAt(0); + segment->setSpannerSegmentType(SpannerSegmentType::SINGLE); + segment->setSystem(chord->segment()->measure()->system()); + segment->resetAdjustmentOffset(); + segment->mutldata()->allJumpPointsInactive = item->allJumpPointsInactive(); + + return segment; +} + +PartialTieSegment* SlurTieLayout::layoutPartialTie(PartialTie* item) +{ + const bool outgoing = item->isOutgoing(); + PartialTieSegment* segment = createPartialTieSegment(item); + SlurTiePos sPos; + + computeStartAndEndSystem(item, sPos); + if (outgoing) { + sPos.p1 = computeDefaultStartOrEndPoint(item, Grip::START); + } else { + sPos.p2 = computeDefaultStartOrEndPoint(item, Grip::END); + } + + if (segment->autoplace() && !segment->isEdited()) { + adjustX(segment, sPos, outgoing ? Grip::START : Grip::END); + } + + setPartialTieEndPos(item, sPos); + + correctForCrossStaff(item, sPos, SpannerSegmentType::SINGLE); + + adjustYforLedgerLines(segment, sPos); + + segment->ups(Grip::START).p = sPos.p1; + segment->ups(Grip::END).p = sPos.p2; + + if (segment->autoplace() && !segment->isEdited()) { + adjustY(segment); + } else { + computeBezier(segment); + } + + addLineAttachPoints(segment); + + return segment; +} + +void SlurTieLayout::setPartialTieEndPos(PartialTie* item, SlurTiePos& sPos) +{ + const bool outgoing = item->isOutgoing(); + + const Chord* chord = item->parentNote()->chord(); + const Segment* seg = chord->segment(); + const Measure* measure = seg->measure(); + const System* system = measure->system(); + double width = item->style().styleS(Sid::minHangingTieLength).val() * item->spatium(); + + if (seg->measure()->isFirstInSystem() && !outgoing) { + sPos.p1 = PointF((system ? system->firstNoteRestSegmentX(true) : 0), sPos.p2.y()); + return; + } + + const Segment* adjSeg = outgoing ? seg->next() : seg->prev(); + while (adjSeg && (!adjSeg->isActive() || !adjSeg->enabled())) { + adjSeg = outgoing ? seg->next() : seg->prev(); + } + + if (adjSeg) { + EngravingItem* element = adjSeg->element(staff2track(item->vStaffIdx())); + const double elementWidth = element ? element->width() : 0.0; + double widthToSegment = outgoing ? adjSeg->xPosInSystemCoords() - sPos.p1.x() : sPos.p2.x() + - (adjSeg->xPosInSystemCoords() + elementWidth); + widthToSegment -= 0.25 * item->spatium(); + width = std::max(widthToSegment, width); + } + + if (outgoing) { + sPos.p2 = PointF(sPos.p1.x() + width, sPos.p1.y()); + } else { + sPos.p1 = PointF(sPos.p2.x() - width, sPos.p2.y()); + } +} + void SlurTieLayout::layoutLaissezVibChord(Chord* chord, LayoutContext& ctx) { // Laissez vib ties should all end at the same rightmost point while honouring each tie's minimum length @@ -2589,15 +2694,40 @@ bool SlurTieLayout::shouldHideSlurSegment(SlurSegment* item, LayoutContext& ctx) void SlurTieLayout::addLineAttachPoints(TieSegment* segment) { - // Add tie attach point to start and end note + // Add tie attach point to start and end note of segment Tie* tie = segment->tie(); Note* startNote = tie->startNote(); Note* endNote = tie->endNote(); - if (startNote) { - startNote->addLineAttachPoint(segment->ups(Grip::START).pos(), tie); + + const bool singleOrBegin = segment->spannerSegmentType() == SpannerSegmentType::SINGLE + || segment->spannerSegmentType() == SpannerSegmentType::BEGIN; + + const bool singleOrEnd = segment->spannerSegmentType() == SpannerSegmentType::SINGLE + || segment->spannerSegmentType() == SpannerSegmentType::END; + + if (startNote && singleOrBegin) { + startNote->addStartLineAttachPoint(segment->ups(Grip::START).pos(), tie); + } + if (endNote && singleOrEnd) { + endNote->addEndLineAttachPoint(segment->ups(Grip::END).pos(), tie); } - if (endNote) { - endNote->addLineAttachPoint(segment->ups(Grip::END).pos(), tie); +} + +void SlurTieLayout::addLineAttachPoints(PartialTieSegment* segment) +{ + // Add tie attach point to parent note + PartialTie* tie = segment->partialTie(); + Note* note = tie ? tie->note() : nullptr; + if (!note) { + return; + } + + const bool isOutgoing = tie->isOutgoing(); + + if (isOutgoing) { + note->addStartLineAttachPoint(segment->ups(Grip::START).pos(), tie); + } else { + note->addEndLineAttachPoint(segment->ups(Grip::END).pos(), tie); } } diff --git a/src/engraving/rendering/score/slurtielayout.h b/src/engraving/rendering/score/slurtielayout.h index 123e6a45bbd71..b8fd0143268cd 100644 --- a/src/engraving/rendering/score/slurtielayout.h +++ b/src/engraving/rendering/score/slurtielayout.h @@ -38,6 +38,8 @@ class Tie; class TremoloTwoChord; enum class Grip; class Note; +class PartialTie; +class PartialTieSegment; } namespace muse::draw { @@ -50,8 +52,8 @@ class SlurTieLayout public: static SpannerSegment* layoutSystem(Slur* item, System* system, LayoutContext& ctx); - static TieSegment* tieLayoutFor(Tie* item, System* system); - static TieSegment* tieLayoutBack(Tie* item, System* system, LayoutContext& ctx); + static TieSegment* layoutTieFor(Tie* item, System* system); + static TieSegment* layoutTieBack(Tie* item, System* system, LayoutContext& ctx); static void resolveVerticalTieCollisions(const std::vector& stackedTies); static void computeUp(Slur* slur, LayoutContext& ctx); @@ -104,11 +106,17 @@ class SlurTieLayout static bool shouldHideSlurSegment(SlurSegment* item, LayoutContext& ctx); static void addLineAttachPoints(TieSegment* segment); + static void addLineAttachPoints(PartialTieSegment* segment); static void calculateIsInside(Tie* item); static LaissezVibSegment* createLaissezVibSegment(LaissezVib* item); static void calculateLaissezVibX(LaissezVibSegment* segment, SlurTiePos& sPos, bool smufl); static void calculateLaissezVibY(LaissezVibSegment* segment, SlurTiePos& sPos); + + static PartialTieSegment* createPartialTieSegment(PartialTie* item); + static PartialTieSegment* layoutPartialTie(PartialTie* item); + + static void setPartialTieEndPos(PartialTie* item, SlurTiePos& sPos); }; } diff --git a/src/engraving/rendering/score/systemlayout.cpp b/src/engraving/rendering/score/systemlayout.cpp index 2de8fc2385665..cf8b911289a5f 100644 --- a/src/engraving/rendering/score/systemlayout.cpp +++ b/src/engraving/rendering/score/systemlayout.cpp @@ -1539,7 +1539,7 @@ void SystemLayout::layoutTies(Chord* ch, System* system, const Fraction& stick, for (Note* note : ch->notes()) { Tie* t = note->tieFor(); if (t && !t->isLaissezVib()) { - TieSegment* ts = SlurTieLayout::tieLayoutFor(t, system); + TieSegment* ts = SlurTieLayout::layoutTieFor(t, system); if (ts && ts->addToSkyline()) { staff->skyline().add(ts->shape().translate(ts->pos())); stackedForwardTies.push_back(ts); @@ -1547,8 +1547,8 @@ void SystemLayout::layoutTies(Chord* ch, System* system, const Fraction& stick, } t = note->tieBack(); if (t) { - if (t->startNote()->tick() < stick) { - TieSegment* ts = SlurTieLayout::tieLayoutBack(t, system, ctx); + if (note->incomingPartialTie() || t->startNote()->tick() < stick) { + TieSegment* ts = SlurTieLayout::layoutTieBack(t, system, ctx); if (ts && ts->addToSkyline()) { staff->skyline().add(ts->shape().translate(ts->pos())); stackedBackwardTies.push_back(ts); diff --git a/src/engraving/rendering/score/tdraw.cpp b/src/engraving/rendering/score/tdraw.cpp index 435510dcc067d..8bab16b684cfb 100644 --- a/src/engraving/rendering/score/tdraw.cpp +++ b/src/engraving/rendering/score/tdraw.cpp @@ -98,6 +98,7 @@ #include "dom/ottava.h" #include "dom/page.h" +#include "dom/partialtie.h" #include "dom/palmmute.h" #include "dom/part.h" #include "dom/pedal.h" @@ -304,6 +305,8 @@ void TDraw::drawItem(const EngravingItem* item, Painter* painter) case ElementType::PAGE: draw(item_cast(item), painter); break; + case ElementType::PARTIAL_TIE_SEGMENT: draw(item_cast(item), painter); + break; case ElementType::PALM_MUTE_SEGMENT: draw(item_cast(item), painter); break; case ElementType::PEDAL_SEGMENT: draw(item_cast(item), painter); @@ -2444,6 +2447,11 @@ void TDraw::draw(const Page* item, Painter* painter) } } +void TDraw::draw(const PartialTieSegment* item, muse::draw::Painter* painter) +{ + draw(static_cast(item), painter); +} + void TDraw::draw(const PalmMuteSegment* item, Painter* painter) { TRACE_DRAW_ITEM; @@ -3047,9 +3055,13 @@ void TDraw::draw(const TieSegment* item, Painter* painter) return; } - Pen pen(item->curColor(item->getProperty(Pid::VISIBLE).toBool(), item->getProperty(Pid::COLOR).value())); - double mag = item->staff() ? item->staff()->staffMag(item->tie()->tick()) : 1.0; + Color penColor = item->curColor(item->getProperty(Pid::VISIBLE).toBool(), item->getProperty(Pid::COLOR).value()); + if (!item->score()->printing() && item->ldata()->allJumpPointsInactive) { + penColor.setAlpha(std::min(penColor.alpha(), 85)); + } + Pen pen(penColor); + double mag = item->staff() ? item->staff()->staffMag(item->tie()->tick()) : 1.0; //Replace generic Qt dash patterns with improved equivalents to show true dots (keep in sync with slur.cpp) std::vector dotted = { 0.01, 1.99 }; // tighter than Qt PenStyle::DotLine equivalent - would be { 0.01, 2.99 } std::vector dashed = { 3.00, 3.00 }; // Compensating for caps. Qt default PenStyle::DashLine is { 4.0, 2.0 } diff --git a/src/engraving/rendering/score/tdraw.h b/src/engraving/rendering/score/tdraw.h index 4fb55d85788b2..909193680c467 100644 --- a/src/engraving/rendering/score/tdraw.h +++ b/src/engraving/rendering/score/tdraw.h @@ -19,8 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_TDRAW_DEV_H -#define MU_ENGRAVING_TDRAW_DEV_H +#pragma once #include "dom/engravingitem.h" @@ -261,6 +260,7 @@ class TDraw static void draw(const OttavaSegment* item, muse::draw::Painter* painter); static void draw(const Page* item, muse::draw::Painter* painter); + static void draw(const PartialTieSegment* item, muse::draw::Painter* painter); static void draw(const PalmMuteSegment* item, muse::draw::Painter* painter); static void draw(const PedalSegment* item, muse::draw::Painter* painter); static void draw(const PickScrapeSegment* item, muse::draw::Painter* painter); @@ -318,5 +318,3 @@ class TDraw static void draw(const Chord* item, muse::draw::Painter* painter); }; } - -#endif // MU_ENGRAVING_TDRAW_DEV_H diff --git a/src/engraving/rendering/single/singledraw.cpp b/src/engraving/rendering/single/singledraw.cpp index 07ef937ae84fb..455925a9fdd9d 100644 --- a/src/engraving/rendering/single/singledraw.cpp +++ b/src/engraving/rendering/single/singledraw.cpp @@ -482,7 +482,7 @@ void SingleDraw::draw(const Note* item, Painter* painter) } const Staff* st = item->staff(); const StaffType* tab = st->staffTypeForElement(item); - if (item->tieBack() && !tab->showBackTied()) { + if (item->tieBackNonPartial() && !tab->showBackTied()) { if (item->chord()->measure()->system() == item->tieBack()->startNote()->chord()->measure()->system() && item->el().empty()) { // fret should be hidden, so return without drawing it return; diff --git a/src/engraving/rw/read410/connectorinforeader.cpp b/src/engraving/rw/read410/connectorinforeader.cpp index dd2862d3557b1..f23dcce241bb7 100644 --- a/src/engraving/rw/read410/connectorinforeader.cpp +++ b/src/engraving/rw/read410/connectorinforeader.cpp @@ -363,7 +363,6 @@ void ConnectorInfoReader::readAddConnector(Note* item, ConnectorInfoReader* info case ElementType::GLISSANDO: case ElementType::GUITAR_BEND: case ElementType::NOTELINE: - case ElementType::LAISSEZ_VIB: { Spanner* sp = toSpanner(info->connector()); if (info->isStart()) { @@ -389,7 +388,11 @@ void ConnectorInfoReader::readAddConnector(Note* item, ConnectorInfoReader* info sp->setTick2(item->tick()); sp->setEndElement(item); if (sp->isTie()) { - item->setTieBack(toTie(sp)); + Tie* tie = toTie(sp); + item->setTieBack(tie); + if (pasteMode) { + tie->updatePossibleJumpPoints(); + } } else { bool isNoteAnchoredTextLine = sp->isNoteLine() && toNoteLine(sp)->enforceMinLength(); if ((sp->isGlissando() || sp->isGuitarBend() || isNoteAnchoredTextLine) && item->explicitParent() diff --git a/src/engraving/rw/read410/tread.cpp b/src/engraving/rw/read410/tread.cpp index a48f0ac198f8b..757ba540eeff4 100644 --- a/src/engraving/rw/read410/tread.cpp +++ b/src/engraving/rw/read410/tread.cpp @@ -136,6 +136,7 @@ #include "../../dom/palmmute.h" #include "../../dom/segment.h" #include "../../dom/part.h" +#include "../../dom/partialtie.h" #include "../../dom/whammybar.h" #include "../xmlreader.h" @@ -262,6 +263,8 @@ void TRead::readItem(EngravingItem* item, XmlReader& xml, ReadContext& ctx) break; case ElementType::PALM_MUTE: read(item_cast(item), xml, ctx); break; + case ElementType::PARTIAL_TIE: read(item_cast(item), xml, ctx); + break; case ElementType::PEDAL: read(item_cast(item), xml, ctx); break; case ElementType::PLAYTECH_ANNOTATION: read(item_cast(item), xml, ctx); @@ -448,6 +451,8 @@ PropertyValue TRead::readPropertyValue(Pid id, XmlReader& e, ReadContext& ctx) return PropertyValue(TConv::fromXml(e.readAsciiText(), VoiceAssignment::ALL_VOICE_IN_INSTRUMENT)); case P_TYPE::AUTO_ON_OFF: return PropertyValue(TConv::fromXml(e.readAsciiText(), AutoOnOff::AUTO)); + case P_TYPE::PARTIAL_SPANNER_DIRECTION: + return PropertyValue(TConv::fromXml(e.readAsciiText(), PartialSpannerDirection::OUTGOING)); default: ASSERT_X("unhandled PID type"); break; @@ -3616,6 +3621,15 @@ bool TRead::readProperties(Note* n, XmlReader& e, ReadContext& ctx) TRead::read(lv, e, ctx); lv->setParent(n); n->add(lv); + } else if (tag == "PartialTie") { + PartialTie* pt = Factory::createPartialTie(n); + TRead::read(pt, e, ctx); + if (pt->isOutgoing()) { + pt->setStartNote(n); + } else { + pt->setEndNote(n); + } + n->add(pt); } else if (readItemProperties(n, e, ctx)) { } else { return false; @@ -3753,6 +3767,18 @@ void TRead::read(Part* p, XmlReader& e, ReadContext& ctx) } } +void TRead::read(PartialTie* p, XmlReader& xml, ReadContext& ctx) +{ + while (xml.readNextStartElement()) { + const AsciiStringView tag(xml.name()); + if (TRead::readProperty(p, tag, xml, ctx, Pid::TIE_PLACEMENT)) { + } else if (TRead::readProperty(p, tag, xml, ctx, Pid::PARTIAL_SPANNER_DIRECTION)) { + } else if (!readProperties(static_cast(p), xml, ctx)) { + xml.unknown(); + } + } +} + bool TRead::readProperties(Part* p, XmlReader& e, ReadContext& ctx) { const AsciiStringView tag(e.name()); diff --git a/src/engraving/rw/read410/tread.h b/src/engraving/rw/read410/tread.h index ed38e9b41e9eb..f25f58fad53da 100644 --- a/src/engraving/rw/read410/tread.h +++ b/src/engraving/rw/read410/tread.h @@ -19,8 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_READ410_TREAD_H -#define MU_ENGRAVING_READ410_TREAD_H +#pragma once #include "global/types/string.h" @@ -271,6 +270,7 @@ class TRead static void read(Page* p, XmlReader& xml, ReadContext& ctx); static void read(PalmMute* p, XmlReader& xml, ReadContext& ctx); static void read(Part* p, XmlReader& xml, ReadContext& ctx); + static void read(PartialTie* p, XmlReader& xml, ReadContext& ctx); static void read(Pedal* p, XmlReader& xml, ReadContext& ctx); static void read(PlayTechAnnotation* a, XmlReader& xml, ReadContext& ctx); @@ -384,5 +384,3 @@ class TRead static void readSystemLock(Score* score, XmlReader& e); }; } - -#endif // MU_ENGRAVING_READ410_TREAD_H diff --git a/src/engraving/rw/write/twrite.cpp b/src/engraving/rw/write/twrite.cpp index db2ffb2b96ccd..2194f600369f5 100644 --- a/src/engraving/rw/write/twrite.cpp +++ b/src/engraving/rw/write/twrite.cpp @@ -111,6 +111,7 @@ #include "dom/page.h" #include "dom/palmmute.h" #include "dom/part.h" +#include "dom/partialtie.h" #include "dom/pedal.h" #include "dom/pickscrape.h" #include "dom/playtechannotation.h" @@ -282,6 +283,8 @@ void TWrite::writeItem(const EngravingItem* item, XmlWriter& xml, WriteContext& break; case ElementType::PALM_MUTE: write(item_cast(item), xml, ctx); break; + case ElementType::PARTIAL_TIE: write(item_cast(item), xml, ctx); + break; case ElementType::PEDAL: write(item_cast(item), xml, ctx); break; case ElementType::PICK_SCRAPE: write(item_cast(item), xml, ctx); @@ -2274,11 +2277,19 @@ void TWrite::write(const Note* item, XmlWriter& xml, WriteContext& ctx) write(item->laissezVib(), xml, ctx); } - if (item->tieFor() && !item->laissezVib()) { + if (item->incomingPartialTie() && !ctx.clipboardmode()) { + write(item->incomingPartialTie(), xml, ctx); + } + + if (item->outgoingPartialTie() && !ctx.clipboardmode()) { + write(item->outgoingPartialTie(), xml, ctx); + } + + if (item->tieForNonPartial()) { writeSpannerStart(item->tieFor(), xml, ctx, item, item->track()); } - if (item->tieBack()) { + if (item->tieBackNonPartial()) { writeSpannerEnd(item->tieBack(), xml, ctx, item, item->track()); } @@ -2421,6 +2432,15 @@ void TWrite::write(const Part* item, XmlWriter& xml, WriteContext& ctx) xml.endElement(); } +void TWrite::write(const PartialTie* item, XmlWriter& xml, WriteContext& ctx) +{ + xml.startElement(item); + writeProperty(item, xml, Pid::TIE_PLACEMENT); + writeProperty(item, xml, Pid::PARTIAL_SPANNER_DIRECTION); + writeProperties(static_cast(item), xml, ctx); + xml.endElement(); +} + void TWrite::write(const Pedal* item, XmlWriter& xml, WriteContext& ctx) { if (!ctx.canWrite(item)) { diff --git a/src/engraving/rw/write/twrite.h b/src/engraving/rw/write/twrite.h index 57a8bd9fc3ca2..7189e1720f5c7 100644 --- a/src/engraving/rw/write/twrite.h +++ b/src/engraving/rw/write/twrite.h @@ -19,8 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_TWRITE_H -#define MU_ENGRAVING_TWRITE_H +#pragma once #include "../xmlwriter.h" #include "writecontext.h" @@ -258,6 +257,7 @@ class TWrite static void write(const Page* item, XmlWriter& xml, WriteContext& ctx); static void write(const PalmMute* item, XmlWriter& xml, WriteContext& ctx); static void write(const Part* item, XmlWriter& xml, WriteContext& ctx); + static void write(const PartialTie* item, XmlWriter& xml, WriteContext& ctx); static void write(const Pedal* item, XmlWriter& xml, WriteContext& ctx); static void write(const PickScrape* item, XmlWriter& xml, WriteContext& ctx); static void write(const PlayTechAnnotation* item, XmlWriter& xml, WriteContext& ctx); @@ -350,5 +350,3 @@ class TWrite static void writeSystemLock(const SystemLock* systemLock, XmlWriter& xml); }; } - -#endif // MU_ENGRAVING_TWRITE_H diff --git a/src/engraving/rw/xmlwriter.cpp b/src/engraving/rw/xmlwriter.cpp index 8f35ffc00e23c..e1dc321bd4bad 100644 --- a/src/engraving/rw/xmlwriter.cpp +++ b/src/engraving/rw/xmlwriter.cpp @@ -303,6 +303,9 @@ void XmlWriter::tagProperty(const AsciiStringView& name, P_TYPE type, const Prop case P_TYPE::INT_VEC: { element(name, TConv::toXml(data.value >())); } break; + case P_TYPE::PARTIAL_SPANNER_DIRECTION: { + element(name, TConv::toXml(data.value())); + } break; default: { UNREACHABLE; //! TODO } diff --git a/src/engraving/style/styledef.cpp b/src/engraving/style/styledef.cpp index 1895af18402cf..10cf36f5721f5 100644 --- a/src/engraving/style/styledef.cpp +++ b/src/engraving/style/styledef.cpp @@ -515,12 +515,14 @@ const std::array StyleDef::styleValue styleDef(tieMidWidth, Spatium(.21)), styleDef(tieDottedWidth, Spatium(.10)), styleDef(minTieLength, Spatium(1.0)), + styleDef(minHangingTieLength, Spatium(1.0)), styleDef(minStraightGlissandoLength, Spatium(1.2)), styleDef(minWigglyGlissandoLength, Spatium(2.0)), styleDef(slurMinDistance, Spatium(0.5)), styleDef(tieMinDistance, Spatium(0.5)), styleDef(laissezVibMinDistance, Spatium(0.5)), styleDef(headerToLineStartDistance, Spatium(1.0)), + styleDef(lineEndToSystemEndDistance, Spatium(0.25)), styleDef(tiePlacementSingleNote, TiePlacement::OUTSIDE), styleDef(tiePlacementChord, TiePlacement::OUTSIDE), diff --git a/src/engraving/style/styledef.h b/src/engraving/style/styledef.h index e9d9fd46c504a..fde510934ba17 100644 --- a/src/engraving/style/styledef.h +++ b/src/engraving/style/styledef.h @@ -527,12 +527,14 @@ enum class Sid { tieMidWidth, tieDottedWidth, minTieLength, + minHangingTieLength, minStraightGlissandoLength, minWigglyGlissandoLength, slurMinDistance, tieMinDistance, laissezVibMinDistance, - headerToLineStartDistance, // determines start point of "dangling" lines (ties, gliss, lyrics...) at start of system + headerToLineStartDistance, // determines start point of "dangling" lines (ties, gliss, lyrics...) at start of system + lineEndToSystemEndDistance, // determines end point of "dangling" lines (ties, gliss, lyrics...) at end of system tiePlacementSingleNote, tiePlacementChord, diff --git a/src/engraving/tests/CMakeLists.txt b/src/engraving/tests/CMakeLists.txt index 907c82d580320..eedde0793b17c 100644 --- a/src/engraving/tests/CMakeLists.txt +++ b/src/engraving/tests/CMakeLists.txt @@ -66,6 +66,7 @@ set(MODULE_TEST_SRC #${CMAKE_CURRENT_LIST_DIR}/midimapping_tests.cpp doesn't compile and needs actualization ${CMAKE_CURRENT_LIST_DIR}/note_tests.cpp ${CMAKE_CURRENT_LIST_DIR}/parts_tests.cpp + ${CMAKE_CURRENT_LIST_DIR}/partialtie_tests.cpp ${CMAKE_CURRENT_LIST_DIR}/pitchwheelrender_tests.cpp ${CMAKE_CURRENT_LIST_DIR}/playback/playbackeventsrendering_tests.cpp ${CMAKE_CURRENT_LIST_DIR}/playback/playbackmodel_tests.cpp diff --git a/src/engraving/tests/partialtie_data/coda-ref.mscx b/src/engraving/tests/partialtie_data/coda-ref.mscx new file mode 100644 index 0000000000000..210503bdc90e9 --- /dev/null +++ b/src/engraving/tests/partialtie_data/coda-ref.mscx @@ -0,0 +1,235 @@ + + + + 480 + + 1 + 1 + 1 + 0 + 1 + + + Composer / arranger + + + + + + + Subtitle + + + Untitled score + + Orchestra + + Flutes + +
+ flutes + oboes + clarinets + saxophones + bassoons + +
+
+ horns + trumpets + cornets + flugelhorns + trombones + tubas + +
+
+ timpani +
+
+ keyboard-percussion + + drums + unpitched-metal-percussion + unpitched-wooden-percussion + other-percussion + +
+ keyboards + harps + organs + synths + + +
+ voices + voice-groups +
+
+ orchestral-strings +
+
+ + + + stdNormal + + + Flute + + Flute + Fl. + Flute + 59 + 98 + 60 + 93 + wind.flutes.flute + + + + + + + + 10 + + + Untitled score + + + + Subtitle + + + + Composer / arranger + + + + + + To Coda + + + + -10 + line + + + + 0 + + + 4 + 4 + + + half + + + quarter + + + quarter + + 65 + 13 + + + + + + + + 1 + -3/4 + 1 + + + + 69 + 17 + + + + + + + + D.C. al Coda + start + coda + codab + + + -10 + line + + + + quarter + + 62 + 16 + + + 65 + 13 + + + + + + -1 + 3/4 + -1 + + + + 69 + 17 + + + + quarter + + + half + + + + + + + coda + + + + + quarter + + 62 + 16 + + + + incoming + + 69 + 17 + + + + quarter + + + half + + + + +
+
diff --git a/src/engraving/tests/partialtie_data/coda.mscx b/src/engraving/tests/partialtie_data/coda.mscx new file mode 100644 index 0000000000000..801107eec1b86 --- /dev/null +++ b/src/engraving/tests/partialtie_data/coda.mscx @@ -0,0 +1,214 @@ + + + 4.5.0 + + + 480 + 1 + 1 + 1 + 0 + 1 + + + Composer / arranger + + 2024-11-14 + + + + Apple Macintosh + + + Subtitle + + + Untitled score + + Orchestra + + Flutes + +
+ flutes + oboes + clarinets + saxophones + bassoons + +
+
+ horns + trumpets + cornets + flugelhorns + trombones + tubas + +
+
+ timpani +
+
+ keyboard-percussion + + drums + unpitched-metal-percussion + unpitched-wooden-percussion + other-percussion + +
+ keyboards + harps + organs + synths + + +
+ voices + voice-groups +
+
+ orchestral-strings +
+
+ + + + stdNormal + + + Flute + + Flute + Fl. + Flute + 59 + 98 + 60 + 93 + wind.flutes.flute + + + Fluid + + + + + + 10 + + + Untitled score + + + + Subtitle + + + + Composer / arranger + + + + + + To Coda + + + + -10 + line + + + + 0 + + + 4 + 4 + + + half + + + quarter + + + quarter + + 65 + 13 + + + 69 + 17 + + + + + + + + D.C. al Coda + start + coda + codab + + + -10 + line + + + + quarter + + 62 + 16 + + + 65 + 13 + + + 69 + 17 + + + + quarter + + + half + + + + + + + coda + + + + + quarter + + 62 + 16 + + + 69 + 17 + + + + quarter + + + half + + + + +
+
diff --git a/src/engraving/tests/partialtie_data/repeat_barlines-ref.mscx b/src/engraving/tests/partialtie_data/repeat_barlines-ref.mscx new file mode 100644 index 0000000000000..ecdf22f37690e --- /dev/null +++ b/src/engraving/tests/partialtie_data/repeat_barlines-ref.mscx @@ -0,0 +1,179 @@ + + + + 480 + + 1 + 1 + 1 + 0 + 1 + + + Composer / arranger + + + + + + + Subtitle + + + Untitled score + + Orchestra + + Flutes + +
+ flutes + oboes + clarinets + saxophones + bassoons + +
+
+ horns + trumpets + cornets + flugelhorns + trombones + tubas + +
+
+ timpani +
+
+ keyboard-percussion + + drums + unpitched-metal-percussion + unpitched-wooden-percussion + other-percussion + +
+ keyboards + harps + organs + synths + + +
+ voices + voice-groups +
+
+ orchestral-strings +
+
+ + + + stdNormal + + + Flute + + Flute + Fl. + Flute + 59 + 98 + 60 + 93 + wind.flutes.flute + + + + + + + + + + + 0 + + + 4 + 4 + + + quarter + + + incoming + + 74 + 16 + + + + quarter + + + half + + + + + 2 + + + half + + + quarter + + + quarter + + + + + + + 1 + -3/4 + + + + 74 + 16 + + + + + + + + quarter + + + + + -1 + 3/4 + + + + 74 + 16 + + + + quarter + + + half + + + + +
+
diff --git a/src/engraving/tests/partialtie_data/repeat_barlines.mscx b/src/engraving/tests/partialtie_data/repeat_barlines.mscx new file mode 100755 index 0000000000000..c3bbd58d0307c --- /dev/null +++ b/src/engraving/tests/partialtie_data/repeat_barlines.mscx @@ -0,0 +1,160 @@ + + + 4.5.0 + + + 480 + 1 + 1 + 1 + 0 + 1 + + + Composer / arranger + + 2024-11-13 + + + + Apple Macintosh + + + Subtitle + + + Untitled score + + Orchestra + + Flutes + +
+ flutes + oboes + clarinets + saxophones + bassoons + +
+
+ horns + trumpets + cornets + flugelhorns + trombones + tubas + +
+
+ timpani +
+
+ keyboard-percussion + + drums + unpitched-metal-percussion + unpitched-wooden-percussion + other-percussion + +
+ keyboards + harps + organs + synths + + +
+ voices + voice-groups +
+
+ orchestral-strings +
+
+ + + + stdNormal + + + Flute + + Flute + Fl. + Flute + 59 + 98 + 60 + 93 + wind.flutes.flute + + + Fluid + + + + + + + + + 0 + + + 4 + 4 + + + quarter + + 74 + 16 + + + + quarter + + + half + + + + + 2 + + + half + + + quarter + + + quarter + + 74 + 16 + + + + + + + + quarter + + 74 + 16 + + + + quarter + + + half + + + + +
+
diff --git a/src/engraving/tests/partialtie_data/segno.mscx b/src/engraving/tests/partialtie_data/segno.mscx new file mode 100755 index 0000000000000..0a224ab65eed3 --- /dev/null +++ b/src/engraving/tests/partialtie_data/segno.mscx @@ -0,0 +1,202 @@ + + + 4.5.0 + + + 480 + 1 + 1 + 1 + 0 + 1 + + + Composer / arranger + + 2024-11-29 + + + + Apple Macintosh + + + Subtitle + + + Untitled score + + Orchestra + + Oboes + +
+ flutes + oboes + clarinets + saxophones + bassoons + +
+
+ horns + trumpets + cornets + flugelhorns + trombones + tubas + +
+
+ timpani +
+
+ keyboard-percussion + + drums + unpitched-metal-percussion + unpitched-wooden-percussion + other-percussion + +
+ keyboards + harps + organs + synths + + +
+ voices + voice-groups +
+
+ orchestral-strings +
+
+ + + + stdNormal + + + Oboe + + Oboe + Ob. + Oboe + 58 + 96 + 58 + 87 + wind.reed.oboe + + + Fluid + + + + + + 10 + + + Untitled score + + + + Subtitle + + + + Composer / arranger + + + + + + 0 + + + 4 + 4 + + + half + + + quarter + + + quarter + + 74 + 16 + + + + + + + + segno + + + + + quarter + + 74 + 16 + + + + quarter + + + half + + + + + + -10 + line + + + + D.S. al Coda + segno + coda + codab + + + + half + + + quarter + + + quarter + + 74 + 16 + + + + + + + + coda + + + + + measure + 4/4 + + + + +
+
diff --git a/src/engraving/tests/partialtie_data/volta_coda-ref.mscx b/src/engraving/tests/partialtie_data/volta_coda-ref.mscx new file mode 100644 index 0000000000000..224dbef6f5393 --- /dev/null +++ b/src/engraving/tests/partialtie_data/volta_coda-ref.mscx @@ -0,0 +1,300 @@ + + + + 480 + + 1 + 1 + 1 + 0 + 1 + + + Composer / arranger + + + + + + + Subtitle + + + Untitled score + + Orchestra + + Flutes + +
+ flutes + oboes + clarinets + saxophones + bassoons + +
+
+ horns + trumpets + cornets + flugelhorns + trombones + tubas + +
+
+ timpani +
+
+ keyboard-percussion + + drums + unpitched-metal-percussion + unpitched-wooden-percussion + other-percussion + +
+ keyboards + harps + organs + synths + + +
+ voices + voice-groups +
+
+ orchestral-strings +
+
+ + + + stdNormal + + + Flute + + Flute + Fl. + Flute + 59 + 98 + 60 + 93 + wind.flutes.flute + + + + + + + + + + To Coda + + + + -10 + line + + + + 0 + + + 4 + 4 + + + half + + + quarter + + + quarter + + + + + + + 1 + -3/4 + + + + 74 + 16 + + + + + + 2 + + + + 1 + 1. + 1 + + + + 1 + + + + + quarter + + + + + -1 + 3/4 + + + + 74 + 16 + + + + quarter + + + half + + + + + 2 + + + + + -1 + + + + + + 2. + 2 + + + + 1 + + + + + quarter + + + incoming + + 74 + 16 + + + + quarter + + + half + + + + + + + D.C. al Coda + start + coda + codab + + + -10 + line + + + + + + -1 + + + + + + 1 + 3. + 3 + + + + 1 + + + + + quarter + + + incoming + + 74 + 16 + + + + quarter + + + half + + + + + + + coda + + + + + + + -1 + + + + + quarter + + + incoming + + 74 + 16 + + + + quarter + + + half + + + + +
+
diff --git a/src/engraving/tests/partialtie_data/volta_coda.mscx b/src/engraving/tests/partialtie_data/volta_coda.mscx new file mode 100755 index 0000000000000..fc88d45bcea16 --- /dev/null +++ b/src/engraving/tests/partialtie_data/volta_coda.mscx @@ -0,0 +1,275 @@ + + + 4.5.0 + + + 480 + 1 + 1 + 1 + 0 + 1 + + + Composer / arranger + + 2024-11-14 + + + + Apple Macintosh + + + Subtitle + + + Untitled score + + Orchestra + + Flutes + +
+ flutes + oboes + clarinets + saxophones + bassoons + +
+
+ horns + trumpets + cornets + flugelhorns + trombones + tubas + +
+
+ timpani +
+
+ keyboard-percussion + + drums + unpitched-metal-percussion + unpitched-wooden-percussion + other-percussion + +
+ keyboards + harps + organs + synths + + +
+ voices + voice-groups +
+
+ orchestral-strings +
+
+ + + + stdNormal + + + Flute + + Flute + Fl. + Flute + 59 + 98 + 60 + 93 + wind.flutes.flute + + + Fluid + + + + + + + + To Coda + + + + -10 + line + + + + 0 + + + 4 + 4 + + + half + + + quarter + + + quarter + + 74 + 16 + + + + + + 2 + + + + 1 + 1. + 1 + + + + 1 + + + + + quarter + + 74 + 16 + + + + quarter + + + half + + + + + 2 + + + + + -1 + + + + + + 2. + 2 + + + + 1 + + + + + quarter + + 74 + 16 + + + + quarter + + + half + + + + + + + D.C. al Coda + start + coda + codab + + + -10 + line + + + + + + -1 + + + + + + 1 + 3. + 3 + + + + 1 + + + + + quarter + + 74 + 16 + + + + quarter + + + half + + + + + + + coda + + + + + + + -1 + + + + + quarter + + 74 + 16 + + + + quarter + + + half + + + + +
+
diff --git a/src/engraving/tests/partialtie_tests.cpp b/src/engraving/tests/partialtie_tests.cpp new file mode 100644 index 0000000000000..73fe18bbaf114 --- /dev/null +++ b/src/engraving/tests/partialtie_tests.cpp @@ -0,0 +1,417 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2024 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "dom/note.h" +#include "dom/chord.h" + +#include "utils/scorerw.h" +#include "utils/scorecomp.h" + +using namespace mu::engraving; +static const String PARTIALTIE_DATA_DIR(u"partialtie_data/"); + +class Engraving_PartialTieTests : public ::testing::Test +{ +protected: + void SetUp() override + { + m_useRead302 = MScore::useRead302InTestMode; + MScore::useRead302InTestMode = false; + } + + void TearDown() override + { + MScore::useRead302InTestMode = m_useRead302; + delete m_masterScore; + } + + Note* getNoteAtTick(const Fraction& tick) + { + Segment* seg = m_masterScore->tick2segment(tick, false, SegmentType::ChordRest); + EXPECT_TRUE(seg); + EXPECT_TRUE(seg->element(0) && seg->element(0)->isChord()); + + Chord* chord = toChord(seg->element(0)); + Note* note = chord->upNote(); + EXPECT_TRUE(note); + + return note; + } + + void testPartialTies(const String& score, const Fraction& startPointLocation, const std::vector& jumpPointLocations) + { + openScore(score, startPointLocation, jumpPointLocations); + + addTie(); + + saveAndLoad(score, startPointLocation, jumpPointLocations); + + toggleJumpPoint(); + + deleteJumpTie(); + + deleteJumpNote(); + + toggleFirstJumpPoint(); + + deleteStartTie(); + } + + void openScore(const String& score, const Fraction& startPointLocation, const std::vector& jumpPointLocations) + { + m_masterScore = ScoreRW::readScore(PARTIALTIE_DATA_DIR + score + u".mscx"); + + EXPECT_TRUE(m_masterScore); + + // Find start note + m_startNote = getNoteAtTick(startPointLocation); + // Find jump points + for (const Fraction& jumpPointTick : jumpPointLocations) { + m_jumpPoints.push_back(getNoteAtTick(jumpPointTick)); + } + } + + Tie* addTie() + { + // Add tie to start note + // Expect tie to be added successfully and all jump points to have an incoming tie + m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); + m_masterScore->select(m_startNote); + Tie* t = m_masterScore->cmdToggleTie(); + EXPECT_TRUE(t); + m_masterScore->endCmd(); + + for (const Note* note : m_jumpPoints) { + EXPECT_TRUE(note->tieBack()); + } + + return t; + } + + void toggleJumpPoint() + { + // Toggle the second jump point + // Expect the second jump point to not have an incoming tie and all other jump points to have incoming ties + TieJumpPointList* jumpPointList = m_startNote->tieJumpPoints(); + EXPECT_TRUE(jumpPointList->size() > 1); + + m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); + jumpPointList->toggleJumpPoint(u"jumpPoint1"); + m_masterScore->endCmd(); + + for (TieJumpPoint* jumpPoint : *jumpPointList) { + if (jumpPoint->id() == u"jumpPoint1") { + EXPECT_FALSE(jumpPoint->endTie()); + } else { + EXPECT_TRUE(jumpPoint->endTie()); + } + } + + m_masterScore->undoRedo(true, 0); + + // Expect all jump points to have incoming ties + + for (TieJumpPoint* jumpPoint : *jumpPointList) { + EXPECT_TRUE(jumpPoint->endTie()); + } + } + + void deleteJumpTie() + { + // Delete the second jump tie + // Expect the second jump point to not have an incoming tie and all other jump points to have incoming ties + TieJumpPointList* jumpPointList = m_startNote->tieJumpPoints(); + EXPECT_TRUE(jumpPointList->size() > 1); + EXPECT_TRUE(m_jumpPoints.at(1)->tieBack()->frontSegment()); + + m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); + m_masterScore->deleteItem(m_jumpPoints.at(1)->tieBack()->frontSegment()); + m_masterScore->endCmd(); + + for (TieJumpPoint* jumpPoint : *jumpPointList) { + if (jumpPoint->id() == u"jumpPoint1") { + EXPECT_FALSE(jumpPoint->endTie()); + } else { + EXPECT_TRUE(jumpPoint->endTie()); + } + } + + m_masterScore->undoRedo(true, 0); + + // Expect all jumpPoints to have incoming ties + + for (TieJumpPoint* jumpPoint : *jumpPointList) { + EXPECT_TRUE(jumpPoint->endTie()); + } + } + + void deleteJumpNote() + { + // Delete the second jump point note + // Expect the second jump point to note have in incoming tie and all other jump points to have incoming ties + TieJumpPointList* jumpPointList = m_startNote->tieJumpPoints(); + EXPECT_TRUE(jumpPointList->size() > 1); + EXPECT_TRUE(m_jumpPoints.at(1)->chord()); + + m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); + m_masterScore->deleteItem(m_jumpPoints.at(1)->chord()); + m_masterScore->endCmd(); + + for (TieJumpPoint* jumpPoint : *jumpPointList) { + if (jumpPoint->id() == u"jumpPoint1") { + EXPECT_FALSE(jumpPoint->endTie()); + } else { + EXPECT_TRUE(jumpPoint->endTie()); + } + } + + m_masterScore->undoRedo(true, 0); + + // Expect all jump points to have incoming ties + + for (TieJumpPoint* jumpPoint : *jumpPointList) { + EXPECT_TRUE(jumpPoint->endTie()); + } + } + + void toggleFirstJumpPoint() + { + // Toggle the first jump point + // Expect the first jump point to not have an incoming tie + TieJumpPointList* jumpPointList = m_startNote->tieJumpPoints(); + + Tie* startTie = jumpPointList->startTie(); + + m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); + jumpPointList->toggleJumpPoint(u"jumpPoint0"); + m_masterScore->endCmd(); + + for (TieJumpPoint* jumpPoint : *jumpPointList) { + if (jumpPoint->id() == u"jumpPoint0") { + EXPECT_FALSE(jumpPoint->endTie()); + } else { + EXPECT_TRUE(jumpPoint->endTie()); + } + } + + // Expect the start (full) tie to be replaced with a partial tie + Tie* newStartTie = jumpPointList->startTie(); + + EXPECT_NE(startTie, newStartTie); + EXPECT_TRUE(newStartTie->isPartialTie()); + + // Expect all jump points to have incoming ties + + m_masterScore->undoRedo(true, 0); + + for (TieJumpPoint* jumpPoint : *jumpPointList) { + EXPECT_TRUE(jumpPoint->endTie()); + } + + // Expect the start (partial) tie to be replaced with a full tie + + startTie = jumpPointList->startTie(); + EXPECT_NE(startTie, newStartTie); + EXPECT_FALSE(startTie->isPartialTie()); + } + + void deleteStartTie() + { + // Delete the start tie + // Expect no jump points to have incoming ties + TieJumpPointList* jumpPointList = m_startNote->tieJumpPoints(); + EXPECT_TRUE(m_startNote->tieFor()->frontSegment()); + + m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); + m_masterScore->deleteItem(m_startNote->tieFor()->frontSegment()); + m_masterScore->endCmd(); + + for (TieJumpPoint* jumpPoint : *jumpPointList) { + EXPECT_FALSE(jumpPoint->endTie()); + EXPECT_FALSE(jumpPoint->active()); + } + } + + void testSegnoPartialTieFirst(Fraction tickBeforeSegno, Fraction tickAfterSegno) + { + // Add a partial tie to the note following a segno, then add a full tie to the note preceding the segno + addTie(); + + TieJumpPointList* jumpPointList = m_startNote->tieJumpPoints(); + + Note* noteAfterSegno = getNoteAtTick(tickAfterSegno); + Tie* initialTie = noteAfterSegno->tieBack(); + EXPECT_TRUE(initialTie && initialTie->isPartialTie()); + + Note* noteBeforeSegno = getNoteAtTick(tickBeforeSegno); + m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); + m_masterScore->select(noteBeforeSegno); + Tie* tieBeforeSegno = m_masterScore->cmdToggleTie(); + m_masterScore->endCmd(); + + bool newTieFound = false; + for (TieJumpPoint* jumpPoint : *jumpPointList) { + EXPECT_NE(jumpPoint->endTie(), initialTie); + newTieFound |= jumpPoint->endTie() == tieBeforeSegno; + } + + EXPECT_TRUE(newTieFound); + + EXPECT_TRUE(tieBeforeSegno); + EXPECT_FALSE(tieBeforeSegno->isPartialTie()); + EXPECT_NE(tieBeforeSegno, initialTie); + + EXPECT_EQ(tieBeforeSegno->tieJumpPoints()->size(), 0); + EXPECT_EQ(jumpPointList->size(), 1); + + // Delete the start tie + // Expect segno tie to still have a tie but no jump point + EXPECT_TRUE(m_startNote->tieFor()->frontSegment()); + + m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); + m_masterScore->deleteItem(m_startNote->tieFor()->frontSegment()); + m_masterScore->endCmd(); + + EXPECT_TRUE(tieBeforeSegno); + EXPECT_FALSE(tieBeforeSegno->jumpPoint()); + } + + void testSegnoPartialTieAfter(Fraction tickBeforeSegno) + { + // Add a full tie to the note preceding a segno, then add a tie to the D.S which should add the previous tie to the list of jump points + Note* noteBeforeSegno = getNoteAtTick(tickBeforeSegno); + m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); + m_masterScore->select(noteBeforeSegno); + Tie* tieBeforeSegno = m_masterScore->cmdToggleTie(); + EXPECT_TRUE(tieBeforeSegno); + m_masterScore->endCmd(); + + Tie* startTie = addTie(); + + EXPECT_EQ(startTie->tieJumpPoints()->size(), 1); + + // Delete the start tie + // Expect segno tie to still have a tie but no jump point + EXPECT_TRUE(m_startNote->tieFor()->frontSegment()); + + m_masterScore->startCmd(TranslatableString::untranslatable("Partial tie tests")); + m_masterScore->deleteItem(m_startNote->tieFor()->frontSegment()); + m_masterScore->endCmd(); + + EXPECT_TRUE(tieBeforeSegno); + EXPECT_FALSE(tieBeforeSegno->jumpPoint()); + } + + void saveAndLoad(const String& score, const Fraction& startPointLocation, const std::vector& jumpPointLocations) + { + // Save score + const String savePath = score + u".mscx"; + EXPECT_TRUE(ScoreComp::saveCompareScore(m_masterScore, savePath, PARTIALTIE_DATA_DIR + score + u"-ref.mscx")); + delete m_masterScore; + m_masterScore = nullptr; + m_startNote = nullptr; + m_jumpPoints.clear(); + + // Load + openScore(score + u"-ref", startPointLocation, jumpPointLocations); + + // Expect start tie has jumpPoints + // Expect each jumpPoint to have an incoming tie + TieJumpPointList* jumpPointList = m_startNote->tieJumpPoints(); + EXPECT_TRUE(jumpPointList); + + EXPECT_EQ(jumpPointLocations.size(), jumpPointList->size()); + + for (TieJumpPoint* jumpPoint : *jumpPointList) { + EXPECT_EQ(jumpPoint->endTie()->startTie(), m_startNote->tieFor()); + } + + for (const Note* note : m_jumpPoints) { + EXPECT_TRUE(note->tieBack()); + } + } + +private: + bool m_useRead302 = false; + + MasterScore* m_masterScore = nullptr; + Note* m_startNote = nullptr; + std::vector m_jumpPoints; +}; + +TEST_F(Engraving_PartialTieTests, repeatBarlines) +{ + const String test = u"repeat_barlines"; + + const Fraction startPointTick = Fraction(7, 4); + const std::vector jumpPoints = { Fraction(8, 4), Fraction(0, 4) }; + + testPartialTies(test, startPointTick, jumpPoints); +} + +TEST_F(Engraving_PartialTieTests, voltaCoda) +{ + const String test = u"volta_coda"; + + const Fraction startPointTick = Fraction(3, 4); + const std::vector jumpPoints = { Fraction(4, 4), Fraction(8, 4), Fraction(12, 4), Fraction(16, 4) }; + + testPartialTies(test, startPointTick, jumpPoints); +} + +TEST_F(Engraving_PartialTieTests, coda) +{ + const String test = u"coda"; + + const Fraction startPointTick = Fraction(3, 4); + const std::vector jumpPoints = { Fraction(4, 4), Fraction(8, 4) }; + + testPartialTies(test, startPointTick, jumpPoints); +} + +TEST_F(Engraving_PartialTieTests, segnoBefore) +{ + const String test = u"segno"; + + const Fraction startPointTick = Fraction(11, 4); + const std::vector jumpPoints = { Fraction(4, 4) }; + + openScore(test, startPointTick, jumpPoints); + + // Add tie to 3,4. Replace incoming partial tie at 4,4 with full tie + testSegnoPartialTieFirst(Fraction(3, 4), Fraction(4, 4)); +} + +TEST_F(Engraving_PartialTieTests, segnoAfter) +{ + const String test = u"segno"; + + const Fraction startPointTick = Fraction(11, 4); + const std::vector jumpPoints = { Fraction(4, 4) }; + + openScore(test, startPointTick, jumpPoints); + + testSegnoPartialTieAfter(Fraction(3, 4)); +} diff --git a/src/engraving/tests/parts_data/part-image-parts.mscx b/src/engraving/tests/parts_data/part-image-parts.mscx index 02c6a6fddbe08..8cbc18b049309 100644 --- a/src/engraving/tests/parts_data/part-image-parts.mscx +++ b/src/engraving/tests/parts_data/part-image-parts.mscx @@ -130,7 +130,7 @@ - 8999 + 9199 71b2e9f575296b78c22ba721cd71f6e5.png schnee.png @@ -874,7 +874,7 @@ - 8999 + 9199 71b2e9f575296b78c22ba721cd71f6e5.png schnee.png diff --git a/src/engraving/tests/parts_data/part-image-udel.mscx b/src/engraving/tests/parts_data/part-image-udel.mscx index d0e22de22be1c..e8973969edfd0 100644 --- a/src/engraving/tests/parts_data/part-image-udel.mscx +++ b/src/engraving/tests/parts_data/part-image-udel.mscx @@ -130,7 +130,7 @@ - 8999 + 9199 71b2e9f575296b78c22ba721cd71f6e5.png schnee.png @@ -876,7 +876,7 @@ - 8999 + 9199 71b2e9f575296b78c22ba721cd71f6e5.png schnee.png diff --git a/src/engraving/tests/parts_data/part-image.mscx b/src/engraving/tests/parts_data/part-image.mscx index de8040cad029c..06258664a80dc 100644 --- a/src/engraving/tests/parts_data/part-image.mscx +++ b/src/engraving/tests/parts_data/part-image.mscx @@ -124,7 +124,7 @@ - 8999 + 9199 71b2e9f575296b78c22ba721cd71f6e5.png schnee.png diff --git a/src/engraving/types/propertyvalue.cpp b/src/engraving/types/propertyvalue.cpp index b06c99e968e62..107a2291f859f 100644 --- a/src/engraving/types/propertyvalue.cpp +++ b/src/engraving/types/propertyvalue.cpp @@ -168,6 +168,7 @@ QVariant PropertyValue::toQVariant() const case P_TYPE::TIE_PLACEMENT: return static_cast(value()); case P_TYPE::TIE_DOTS_PLACEMENT: return static_cast(value()); case P_TYPE::LYRICS_DASH_SYSTEM_START_TYPE: return static_cast(value()); + case P_TYPE::PARTIAL_SPANNER_DIRECTION: return static_cast(value()); case P_TYPE::VOICE_ASSIGNMENT: return static_cast(value()); case P_TYPE::AUTO_ON_OFF: return static_cast(value()); @@ -275,6 +276,7 @@ PropertyValue PropertyValue::fromQVariant(const QVariant& v, P_TYPE type) case P_TYPE::TIE_PLACEMENT: return PropertyValue(TiePlacement(v.toInt())); case P_TYPE::TIE_DOTS_PLACEMENT: return PropertyValue(TieDotsPlacement(v.toInt())); case P_TYPE::LYRICS_DASH_SYSTEM_START_TYPE: return PropertyValue(LyricsDashSystemStart(v.toInt())); + case P_TYPE::PARTIAL_SPANNER_DIRECTION: return PropertyValue(PartialSpannerDirection(v.toInt())); case P_TYPE::VOICE_ASSIGNMENT: return PropertyValue(VoiceAssignment(v.toInt())); case P_TYPE::AUTO_ON_OFF: return PropertyValue(AutoOnOff(v.toInt())); diff --git a/src/engraving/types/propertyvalue.h b/src/engraving/types/propertyvalue.h index 1875439f85880..c3b0d53508007 100644 --- a/src/engraving/types/propertyvalue.h +++ b/src/engraving/types/propertyvalue.h @@ -19,8 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_PROPERTYVALUE_H -#define MU_ENGRAVING_PROPERTYVALUE_H +#pragma once #include #include @@ -107,6 +106,7 @@ enum class P_TYPE { SLUR_STYLE_TYPE, NOTELINE_PLACEMENT_TYPE, LYRICS_DASH_SYSTEM_START_TYPE, + PARTIAL_SPANNER_DIRECTION, VOICE_ASSIGNMENT, AUTO_ON_OFF, @@ -293,6 +293,9 @@ class PropertyValue PropertyValue(const LyricsDashSystemStart& v) : m_type(P_TYPE::LYRICS_DASH_SYSTEM_START_TYPE), m_data(make_data(v)) {} + PropertyValue(const PartialSpannerDirection& v) + : m_type(P_TYPE::PARTIAL_SPANNER_DIRECTION), m_data(make_data(v)) {} + PropertyValue(const VoiceAssignment& v) : m_type(P_TYPE::VOICE_ASSIGNMENT), m_data(make_data(v)) {} @@ -491,5 +494,3 @@ inline muse::logger::Stream& operator<<(muse::logger::Stream& s, const mu::engra s << "property(not implemented log output)"; return s; } - -#endif // MU_ENGRAVING_PROPERTYVALUE_H diff --git a/src/engraving/types/types.h b/src/engraving/types/types.h index 4e053a4f3d731..3f086d883a187 100644 --- a/src/engraving/types/types.h +++ b/src/engraving/types/types.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_TYPES_H -#define MU_ENGRAVING_TYPES_H +#pragma once #include #include @@ -78,7 +77,7 @@ enum class ElementType { SLUR_SEGMENT, TIE_SEGMENT, LAISSEZ_VIB_SEGMENT, - LAISSEZ_VIB, + PARTIAL_TIE_SEGMENT, BAR_LINE, STAFF_LINES, SYSTEM_DIVIDER, @@ -100,6 +99,8 @@ enum class ElementType { BREATH, MEASURE_REPEAT, TIE, + LAISSEZ_VIB, + PARTIAL_TIE, ARTICULATION, ORNAMENT, FERMATA, @@ -742,6 +743,12 @@ enum class OrnamentShowAccidental { ALWAYS, }; +enum class PartialSpannerDirection : char { + NONE, + INCOMING, + OUTGOING +}; + //------------------------------------------------------------------- // Tid /// Enumerates the list of built-in text substyles @@ -1184,5 +1191,3 @@ Q_DECLARE_METATYPE(mu::engraving::MarkerType) Q_DECLARE_METATYPE(mu::engraving::TrillType) Q_DECLARE_METATYPE(mu::engraving::VibratoType) #endif - -#endif // MU_ENGRAVING_TYPES_H diff --git a/src/engraving/types/typesconv.cpp b/src/engraving/types/typesconv.cpp index c24626d9a0539..844fabddadef5 100644 --- a/src/engraving/types/typesconv.cpp +++ b/src/engraving/types/typesconv.cpp @@ -176,6 +176,7 @@ static const std::vector > ELEMENT_TYPES = { { ElementType::SLUR_SEGMENT, "SlurSegment", muse::TranslatableString("engraving", "Slur segment") }, { ElementType::TIE_SEGMENT, "TieSegment", muse::TranslatableString("engraving", "Tie segment") }, { ElementType::LAISSEZ_VIB_SEGMENT, "LaissezVibSegment", muse::TranslatableString("engraving", "Laissez vibrer segment") }, + { ElementType::PARTIAL_TIE_SEGMENT, "PartialTieSegment", muse::TranslatableString("engraving", "Partial tie segment") }, { ElementType::BAR_LINE, "BarLine", muse::TranslatableString("engraving", "Barline") }, { ElementType::STAFF_LINES, "StaffLines", muse::TranslatableString("engraving", "Staff lines") }, { ElementType::SYSTEM_DIVIDER, "SystemDivider", muse::TranslatableString("engraving", "System divider") }, @@ -196,6 +197,8 @@ static const std::vector > ELEMENT_TYPES = { { ElementType::BREATH, "Breath", muse::TranslatableString("engraving", "Breath") }, { ElementType::MEASURE_REPEAT, "MeasureRepeat", muse::TranslatableString("engraving", "Measure repeat") }, { ElementType::TIE, "Tie", muse::TranslatableString("engraving", "Tie") }, + { ElementType::LAISSEZ_VIB, "LaissezVib", muse::TranslatableString("engraving", "Laissez vibrer") }, + { ElementType::PARTIAL_TIE, "PartialTie", muse::TranslatableString("engraving", "Partial tie") }, { ElementType::ARTICULATION, "Articulation", muse::TranslatableString("engraving", "Articulation") }, { ElementType::ORNAMENT, "Ornament", muse::TranslatableString("engraving", "Ornament") }, { ElementType::FERMATA, "Fermata", muse::TranslatableString("engraving", "Fermata") }, @@ -247,7 +250,6 @@ static const std::vector > ELEMENT_TYPES = { { ElementType::LYRICSLINE_SEGMENT, "LyricsLineSegment", muse::TranslatableString("engraving", "Extension line segment") }, { ElementType::GLISSANDO_SEGMENT, "GlissandoSegment", muse::TranslatableString("engraving", "Glissando segment") }, { ElementType::NOTELINE_SEGMENT, "NoteLineSegment", muse::TranslatableString("engraving", "Note-anchored line segment") }, - { ElementType::LAISSEZ_VIB, "LaissezVib", muse::TranslatableString("engraving", "Laissez vibrer") }, { ElementType::LAYOUT_BREAK, "LayoutBreak", muse::TranslatableString("engraving", "Layout break") }, { ElementType::SYSTEM_LOCK_INDICATOR, "systemLockIndicator", muse::TranslatableString("engraving", "System lock") }, { ElementType::SPACER, "Spacer", muse::TranslatableString("engraving", "Spacer") }, @@ -488,6 +490,22 @@ AutoOnOff TConv::fromXml(const AsciiStringView& str, AutoOnOff def) return findTypeByXmlTag(AUTO_ON_OFF, str, def); } +static const std::vector > PARTIAL_SPANNER_DIRECTION = { + { PartialSpannerDirection::NONE, "none" }, + { PartialSpannerDirection::OUTGOING, "outgoing" }, + { PartialSpannerDirection::INCOMING, "incoming" } +}; + +AsciiStringView TConv::toXml(PartialSpannerDirection v) +{ + return findXmlTagByType(PARTIAL_SPANNER_DIRECTION, v); +} + +PartialSpannerDirection TConv::fromXml(const AsciiStringView& str, PartialSpannerDirection def) +{ + return findTypeByXmlTag(PARTIAL_SPANNER_DIRECTION, str, def); +} + String TConv::translatedUserName(SymId v) { return SymNames::translatedUserNameForSymId(v); diff --git a/src/engraving/types/typesconv.h b/src/engraving/types/typesconv.h index 600f529744801..9c8770cdfdd56 100644 --- a/src/engraving/types/typesconv.h +++ b/src/engraving/types/typesconv.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_ENGRAVING_TYPESCONV_H -#define MU_ENGRAVING_TYPESCONV_H +#pragma once #include "types/string.h" #include "types.h" @@ -244,7 +243,8 @@ class TConv static AsciiStringView toXml(AutoOnOff autoOnOff); static AutoOnOff fromXml(const AsciiStringView& str, AutoOnOff def); + + static AsciiStringView toXml(PartialSpannerDirection v); + static PartialSpannerDirection fromXml(const AsciiStringView& str, PartialSpannerDirection def); }; } - -#endif // MU_ENGRAVING_TYPESCONV_H diff --git a/src/framework/global/containers.h b/src/framework/global/containers.h index 03b5b3bbf35dd..404dac5414371 100644 --- a/src/framework/global/containers.h +++ b/src/framework/global/containers.h @@ -19,8 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef MUSE_GLOBAL_CONTAINERS_H -#define MUSE_GLOBAL_CONTAINERS_H +#pragma once #include #include @@ -226,6 +225,15 @@ inline bool contains(const std::unordered_set& s, const T& v) return s.find(v) != s.cend(); } +// =========================== +// Array +// =========================== +template +inline bool contains(const std::array& s, const T& v) +{ + return std::find(s.begin(), s.end(), v) != s.cend(); +} + // =========================== // General // =========================== @@ -449,5 +457,3 @@ inline std::set& operator<<(std::set& s, const T& v) s.insert(v); return s; } - -#endif // MUSE_GLOBAL_CONTAINERS_H diff --git a/src/framework/uicomponents/qml/Muse/UiComponents/ContextMenuLoader.qml b/src/framework/uicomponents/qml/Muse/UiComponents/ContextMenuLoader.qml index 6633c1b116c60..ef07f20408681 100644 --- a/src/framework/uicomponents/qml/Muse/UiComponents/ContextMenuLoader.qml +++ b/src/framework/uicomponents/qml/Muse/UiComponents/ContextMenuLoader.qml @@ -21,12 +21,23 @@ */ import QtQuick 2.15 +import Muse.Ui 1.0 + Item { id: container // Useful for static context menus property var items: [] + + property NavigationSection notationViewNavigationSection: null + property int navigationOrderStart: 0 + + property alias closeMenuOnSelection: contextMenuLoader.closeMenuOnSelection + property alias opensUpward: contextMenuLoader.opensUpward + property alias focusOnOpened: contextMenuLoader.focusOnOpened + property alias item: contextMenuLoader.item + signal handleMenuItem(string itemId) signal opened() signal closed() @@ -36,6 +47,13 @@ Item { width: 0 height: 0 + onNotationViewNavigationSectionChanged: function() { + contextMenuLoader.item.notationViewNavigationSection = container.notationViewNavigationSection + } + onNavigationOrderStartChanged: function() { + contextMenuLoader.item.navigationOrderStart = container.navigationOrderStart + } + function show(position: point, items) { if (!items) { items = container.items diff --git a/src/framework/uicomponents/qml/Muse/UiComponents/StyledMenuLoader.qml b/src/framework/uicomponents/qml/Muse/UiComponents/StyledMenuLoader.qml index 1622e320e80bf..3425a5a3f84ef 100644 --- a/src/framework/uicomponents/qml/Muse/UiComponents/StyledMenuLoader.qml +++ b/src/framework/uicomponents/qml/Muse/UiComponents/StyledMenuLoader.qml @@ -21,6 +21,7 @@ */ import QtQuick 2.15 +import Muse.Ui 1.0 import Muse.UiComponents 1.0 import "internal" @@ -37,6 +38,13 @@ Loader { property StyledMenu menu: loader.item as StyledMenu property Item menuAnchorItem: null property bool hasSiblingMenus: false + property bool closeMenuOnSelection: true + property bool focusOnOpened: true + + property NavigationSection notationViewNavigationSection: null + property int navigationOrderStart: 0 + + property bool opensUpward: false property alias isMenuOpened: loader.active @@ -65,7 +73,9 @@ Loader { accessibleName: loader.accessibleName onHandleMenuItem: function(itemId) { - itemMenu.close() + if (loader.closeMenuOnSelections) { + itemMenu.close() + } Qt.callLater(loader.handleMenuItem, itemId) } @@ -82,7 +92,9 @@ Loader { } onOpened: { - focusOnOpenedMenuTimer.start() + if (focusOnOpened) { + focusOnOpenedMenuTimer.start() + } } } @@ -133,6 +145,8 @@ Loader { menu.closeSubMenu() + menu.setOpensUpward(loader.opensUpward) + if (x !== -1) { menu.x = x } diff --git a/src/framework/uicomponents/qml/Muse/UiComponents/internal/StyledMenu.qml b/src/framework/uicomponents/qml/Muse/UiComponents/internal/StyledMenu.qml index 6262704ad7365..5cc67be2fa735 100644 --- a/src/framework/uicomponents/qml/Muse/UiComponents/internal/StyledMenu.qml +++ b/src/framework/uicomponents/qml/Muse/UiComponents/internal/StyledMenu.qml @@ -32,6 +32,9 @@ MenuView { property alias model: view.model + property NavigationSection notationViewNavigationSection: null + property int navigationOrderStart: 0 + property int preferredAlign: Qt.AlignRight // Left, HCenter, Right property bool hasSiblingMenus: loader.hasSiblingMenus @@ -46,6 +49,13 @@ MenuView { signal loaded() + onNotationViewNavigationSectionChanged: function() { + menuNavPanel.section = root.notationViewNavigationSection + } + onNavigationOrderStartChanged: function() { + menuNavPanel.order = root.navigationOrderStart + } + function requestFocus() { var focused = prv.focusOnSelected() if (!focused) { @@ -133,6 +143,7 @@ MenuView { } property NavigationPanel navigationPanel: NavigationPanel { + id: menuNavPanel name: "StyledMenu" section: content.navigationSection direction: NavigationPanel.Vertical diff --git a/src/importexport/musicxml/internal/musicxml/export/exportmusicxml.cpp b/src/importexport/musicxml/internal/musicxml/export/exportmusicxml.cpp index ad654e1b7649d..8cb6594713c25 100644 --- a/src/importexport/musicxml/internal/musicxml/export/exportmusicxml.cpp +++ b/src/importexport/musicxml/internal/musicxml/export/exportmusicxml.cpp @@ -4261,10 +4261,10 @@ void ExportMusicXml::chord(Chord* chord, staff_idx_t staff, const std::vectortieBack()) { + if (note->tieBackNonPartial()) { m_xml.tag("tie", { { "type", "stop" } }); } - if (note->tieFor() && !note->tieFor()->isLaissezVib()) { + if (note->tieForNonPartial()) { m_xml.tag("tie", { { "type", "start" } }); } } diff --git a/src/inspector/internal/services/elementrepositoryservice.cpp b/src/inspector/internal/services/elementrepositoryservice.cpp index 18bcf23a1ec2c..b0eda7c592c30 100644 --- a/src/inspector/internal/services/elementrepositoryservice.cpp +++ b/src/inspector/internal/services/elementrepositoryservice.cpp @@ -104,6 +104,7 @@ QList ElementRepositoryService::findElementsByTyp case mu::engraving::ElementType::SLUR: case mu::engraving::ElementType::TIE: case mu::engraving::ElementType::LAISSEZ_VIB: + case mu::engraving::ElementType::PARTIAL_TIE: case mu::engraving::ElementType::GRADUAL_TEMPO_CHANGE: case mu::engraving::ElementType::PALM_MUTE: return findLines(elementType); default: @@ -322,6 +323,7 @@ QList ElementRepositoryService::findLines(mu::eng { mu::engraving::ElementType::SLUR, mu::engraving::ElementType::SLUR_SEGMENT }, { mu::engraving::ElementType::TIE, mu::engraving::ElementType::TIE_SEGMENT }, { mu::engraving::ElementType::LAISSEZ_VIB, mu::engraving::ElementType::LAISSEZ_VIB_SEGMENT }, + { mu::engraving::ElementType::PARTIAL_TIE, mu::engraving::ElementType::PARTIAL_TIE_SEGMENT }, { mu::engraving::ElementType::GRADUAL_TEMPO_CHANGE, mu::engraving::ElementType::GRADUAL_TEMPO_CHANGE_SEGMENT } }; diff --git a/src/inspector/models/abstractinspectormodel.cpp b/src/inspector/models/abstractinspectormodel.cpp index 8aeec7087d5c7..ed78f9f8e2302 100644 --- a/src/inspector/models/abstractinspectormodel.cpp +++ b/src/inspector/models/abstractinspectormodel.cpp @@ -53,6 +53,8 @@ static const QMap NOTATION_ELEME { mu::engraving::ElementType::TIE_SEGMENT, InspectorModelType::TYPE_TIE }, { mu::engraving::ElementType::LAISSEZ_VIB, InspectorModelType::TYPE_LAISSEZ_VIB }, { mu::engraving::ElementType::LAISSEZ_VIB_SEGMENT, InspectorModelType::TYPE_LAISSEZ_VIB }, + { mu::engraving::ElementType::PARTIAL_TIE, InspectorModelType::TYPE_PARTIAL_TIE }, + { mu::engraving::ElementType::PARTIAL_TIE_SEGMENT, InspectorModelType::TYPE_PARTIAL_TIE }, { mu::engraving::ElementType::TEMPO_TEXT, InspectorModelType::TYPE_TEMPO }, { mu::engraving::ElementType::FERMATA, InspectorModelType::TYPE_FERMATA }, { mu::engraving::ElementType::LAYOUT_BREAK, InspectorModelType::TYPE_SECTIONBREAK }, diff --git a/src/inspector/models/abstractinspectormodel.h b/src/inspector/models/abstractinspectormodel.h index 51243c9fc13cb..c08e611db5ebf 100644 --- a/src/inspector/models/abstractinspectormodel.h +++ b/src/inspector/models/abstractinspectormodel.h @@ -19,8 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef MU_INSPECTOR_ABSTRACTINSPECTORMODEL_H -#define MU_INSPECTOR_ABSTRACTINSPECTORMODEL_H +#pragma once #include #include @@ -107,6 +106,7 @@ class AbstractInspectorModel : public QObject, public muse::async::Asyncable TYPE_SLUR, TYPE_TIE, TYPE_LAISSEZ_VIB, + TYPE_PARTIAL_TIE, TYPE_CRESCENDO, TYPE_DIMINUENDO, TYPE_STAFF_TYPE_CHANGES, @@ -255,5 +255,3 @@ using InspectorSectionType = AbstractInspectorModel::InspectorSectionType; using InspectorModelTypeSet = std::set; using InspectorSectionTypeSet = std::set; } - -#endif // MU_INSPECTOR_ABSTRACTINSPECTORMODEL_H diff --git a/src/inspector/models/inspectormodelcreator.cpp b/src/inspector/models/inspectormodelcreator.cpp index 346d43466f029..274a251d26d81 100644 --- a/src/inspector/models/inspectormodelcreator.cpp +++ b/src/inspector/models/inspectormodelcreator.cpp @@ -153,6 +153,8 @@ AbstractInspectorModel* InspectorModelCreator::newInspectorModel(InspectorModelT return new SlurAndTieSettingsModel(parent, repository, SlurAndTieSettingsModel::Tie); case InspectorModelType::TYPE_LAISSEZ_VIB: return new SlurAndTieSettingsModel(parent, repository, SlurAndTieSettingsModel::LaissezVib); + case InspectorModelType::TYPE_PARTIAL_TIE: + return new SlurAndTieSettingsModel(parent, repository, SlurAndTieSettingsModel::PartialTie); case InspectorModelType::TYPE_STAFF_TYPE_CHANGES: return new StaffTypeSettingsModel(parent, repository); case InspectorModelType::TYPE_TEXT_FRAME: diff --git a/src/inspector/models/notation/lines/slurandtiesettingsmodel.cpp b/src/inspector/models/notation/lines/slurandtiesettingsmodel.cpp index 94abef1ae3d94..22f69a33808b4 100644 --- a/src/inspector/models/notation/lines/slurandtiesettingsmodel.cpp +++ b/src/inspector/models/notation/lines/slurandtiesettingsmodel.cpp @@ -47,12 +47,17 @@ SlurAndTieSettingsModel::SlurAndTieSettingsModel(QObject* parent, IElementReposi setElementType(mu::engraving::ElementType::TIE); setTitle(muse::qtrc("inspector", "Tie")); setIcon(IconCode::NOTE_TIE); - } else { + } else if (elementType == ElementType::LaissezVib) { setModelType(InspectorModelType::TYPE_LAISSEZ_VIB); setElementType(mu::engraving::ElementType::LAISSEZ_VIB); setTitle(muse::qtrc("inspector", "Laissez vibrer")); setIcon(IconCode::NOTE_LV); m_isLaissezVib = true; + } else if (elementType == ElementType::PartialTie) { + setModelType(InspectorModelType::TYPE_PARTIAL_TIE); + setElementType(mu::engraving::ElementType::PARTIAL_TIE); + setTitle(muse::qtrc("inspector", "Tie (partial)")); + setIcon(IconCode::NOTE_TIE); } createProperties(); @@ -144,7 +149,7 @@ void SlurAndTieSettingsModel::updateIsTiePlacementAvailable() { bool available = false; for (EngravingItem* item : m_elementList) { - if (item->isTie() || item->isLaissezVib()) { + if (item->isTie()) { available = true; break; } diff --git a/src/inspector/models/notation/lines/slurandtiesettingsmodel.h b/src/inspector/models/notation/lines/slurandtiesettingsmodel.h index 7b889be3d8090..d8be1cd2de911 100644 --- a/src/inspector/models/notation/lines/slurandtiesettingsmodel.h +++ b/src/inspector/models/notation/lines/slurandtiesettingsmodel.h @@ -19,8 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef MU_INSPECTOR_SLURANDTIESETTINGSMODEL_H -#define MU_INSPECTOR_SLURANDTIESETTINGSMODEL_H +#pragma once #include "models/abstractinspectormodel.h" @@ -42,7 +41,8 @@ class SlurAndTieSettingsModel : public AbstractInspectorModel enum ElementType { Slur, Tie, - LaissezVib + LaissezVib, + PartialTie }; explicit SlurAndTieSettingsModel(QObject* parent, IElementRepositoryService* repository, ElementType elementType); @@ -86,5 +86,3 @@ class SlurAndTieSettingsModel : public AbstractInspectorModel bool m_isLaissezVib = false; }; } - -#endif // MU_INSPECTOR_SLURANDTIESETTINGSMODEL_H diff --git a/src/inspector/view/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml b/src/inspector/view/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml index cbc5e7a8ca62b..ab13aad1b7983 100644 --- a/src/inspector/view/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml +++ b/src/inspector/view/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml @@ -92,7 +92,8 @@ Loader { case Inspector.TYPE_VIBRATO: return vibratoComp case Inspector.TYPE_SLUR: case Inspector.TYPE_TIE: - case Inspector.TYPE_LAISSEZ_VIB: return slurAndTieComp + case Inspector.TYPE_LAISSEZ_VIB: + case Inspector.TYPE_PARTIAL_TIE: return slurAndTieComp case Inspector.TYPE_TEMPO: return tempoComp case Inspector.TYPE_A_TEMPO: return aTempoComp case Inspector.TYPE_TEMPO_PRIMO: return tempoPrimoComp diff --git a/src/notation/CMakeLists.txt b/src/notation/CMakeLists.txt index cacaabfaaf781..7ea63107bef28 100644 --- a/src/notation/CMakeLists.txt +++ b/src/notation/CMakeLists.txt @@ -158,6 +158,8 @@ set(MODULE_SRC ${CMAKE_CURRENT_LIST_DIR}/view/internal/caposettingsmodel.h ${CMAKE_CURRENT_LIST_DIR}/view/internal/stringtuningssettingsmodel.cpp ${CMAKE_CURRENT_LIST_DIR}/view/internal/stringtuningssettingsmodel.h + ${CMAKE_CURRENT_LIST_DIR}/view/internal/partialtiepopupmodel.cpp + ${CMAKE_CURRENT_LIST_DIR}/view/internal/partialtiepopupmodel.h ${CMAKE_CURRENT_LIST_DIR}/view/selectionfiltermodel.cpp ${CMAKE_CURRENT_LIST_DIR}/view/selectionfiltermodel.h ${CMAKE_CURRENT_LIST_DIR}/view/editgridsizedialogmodel.cpp diff --git a/src/notation/inotationinteraction.h b/src/notation/inotationinteraction.h index a3295246f6a4a..34caca2c2a2d1 100644 --- a/src/notation/inotationinteraction.h +++ b/src/notation/inotationinteraction.h @@ -130,6 +130,7 @@ class INotationInteraction // Move/nudge elements virtual void movePitch(MoveDirection d, PitchMode mode) = 0; virtual void nudge(MoveDirection d, bool quickly) = 0; + virtual void nudgeAnchors(MoveDirection d) = 0; virtual void moveChordRestToStaff(MoveDirection d) = 0; virtual void swapChordRest(MoveDirection d) = 0; virtual void toggleSnapToPrevious() = 0; diff --git a/src/notation/internal/inotationundostack.h b/src/notation/internal/inotationundostack.h index 87dcb4bc53b07..c0754d9c9d931 100644 --- a/src/notation/internal/inotationundostack.h +++ b/src/notation/internal/inotationundostack.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_SCENE_NOTATION_INOTATIONUNDOSTACK_H -#define MU_SCENE_NOTATION_INOTATIONUNDOSTACK_H +#pragma once #include "async/notification.h" #include "async/channel.h" @@ -51,6 +50,8 @@ class INotationUndoStack virtual void rollbackChanges() = 0; virtual void commitChanges() = 0; + virtual void mergeCommands(const size_t startIdx) = 0; + virtual bool isStackClean() const = 0; virtual void lock() = 0; @@ -70,5 +71,3 @@ class INotationUndoStack using INotationUndoStackPtr = std::shared_ptr; } - -#endif // MU_SCENE_NOTATION_INOTATIONUNDOSTACK_H diff --git a/src/notation/internal/notationactioncontroller.cpp b/src/notation/internal/notationactioncontroller.cpp index 1c6ccc6cd3d76..dd93da105fe9f 100644 --- a/src/notation/internal/notationactioncontroller.cpp +++ b/src/notation/internal/notationactioncontroller.cpp @@ -29,6 +29,7 @@ #include "engraving/dom/note.h" #include "engraving/dom/text.h" #include "engraving/dom/sig.h" +#include "view/abstractelementpopupmodel.h" #include "translation.h" #include "log.h" @@ -192,14 +193,14 @@ void NotationActionController::init() registerMoveSelectionAction("next-system", MoveSelectionType::System, MoveDirection::Right); registerMoveSelectionAction("prev-system", MoveSelectionType::System, MoveDirection::Left); - registerAction("notation-move-right", &Controller::move, MoveDirection::Right, false); - registerAction("notation-move-left", &Controller::move, MoveDirection::Left, false); + registerAction("notation-move-right", &Controller::move, MoveDirection::Right, false, &Controller::isNotEditingOrHasPopup); + registerAction("notation-move-left", &Controller::move, MoveDirection::Left, false, &Controller::isNotEditingOrHasPopup); registerAction("notation-move-right-quickly", &Controller::move, MoveDirection::Right, true, &Controller::measureNavigationAvailable); registerAction("notation-move-left-quickly", &Controller::move, MoveDirection::Left, true, &Controller::measureNavigationAvailable); - registerAction("pitch-up", &Controller::move, MoveDirection::Up, false); - registerAction("pitch-down", &Controller::move, MoveDirection::Down, false); - registerAction("pitch-up-octave", &Controller::move, MoveDirection::Up, true); - registerAction("pitch-down-octave", &Controller::move, MoveDirection::Down, true); + registerAction("pitch-up", &Controller::move, MoveDirection::Up, false, &Controller::isNotEditingOrHasPopup); + registerAction("pitch-down", &Controller::move, MoveDirection::Down, false, &Controller::isNotEditingOrHasPopup); + registerAction("pitch-up-octave", &Controller::move, MoveDirection::Up, true, &Controller::isNotEditingOrHasPopup); + registerAction("pitch-down-octave", &Controller::move, MoveDirection::Down, true, &Controller::isNotEditingOrHasPopup); registerAction("up-chord", [this]() { moveWithinChord(MoveDirection::Up); }, &Controller::hasSelection); registerAction("down-chord", [this]() { moveWithinChord(MoveDirection::Down); }, &Controller::hasSelection); @@ -1014,6 +1015,8 @@ void NotationActionController::move(MoveDirection direction, bool quickly) interaction->moveLyrics(direction); } else if (selectedElement && (selectedElement->isTextBase() || selectedElement->isArticulationFamily())) { interaction->nudge(direction, quickly); + } else if (selectedElement && selectedElement->hasGrips()) { + interaction->nudgeAnchors(direction); } else if (interaction->noteInput()->isNoteInputMode() && interaction->noteInput()->state().staffGroup == mu::engraving::StaffGroup::TAB) { if (quickly) { @@ -1069,6 +1072,8 @@ void NotationActionController::move(MoveDirection direction, bool quickly) if (selectedElement && selectedElement->isTextBase()) { interaction->nudge(direction, quickly); + } else if (selectedElement && selectedElement->hasGrips()) { + interaction->nudgeAnchors(direction); } else { if (interaction->selection()->isNone()) { interaction->selectFirstElement(false); @@ -1524,7 +1529,7 @@ void NotationActionController::startEditSelectedElement(const ActionData& args) } if (elementHasPopup(element)) { - dispatcher()->dispatch("notation-popup-menu"); + dispatcher()->dispatch("notation-popup-menu", ActionData::make_arg1(element)); return; } @@ -1776,7 +1781,7 @@ FilterElementsOptions NotationActionController::elementsFilterOptions(const Engr bool NotationActionController::measureNavigationAvailable() const { - return isNotEditingElement() || textNavigationAvailable(); + return isNotEditingOrHasPopup() || textNavigationAvailable(); } bool NotationActionController::toggleLayoutBreakAvailable() const @@ -2121,13 +2126,9 @@ const mu::engraving::Harmony* NotationActionController::editedChordSymbol() cons return toHarmony(text); } -bool NotationActionController::elementHasPopup(EngravingItem* e) +bool NotationActionController::elementHasPopup(const EngravingItem* e) const { - if (e->isHarpPedalDiagram()) { - return true; - } - - return false; + return AbstractElementPopupModel::supportsPopup(e->type()); } bool NotationActionController::canUndo() const @@ -2169,6 +2170,17 @@ bool NotationActionController::isNotEditingElement() const return !isEditingElement(); } +bool NotationActionController::isNotEditingOrHasPopup() const +{ + const EngravingItem* element = selectedElement(); + + if (!element) { + return isNotEditingElement(); + } + + return elementHasPopup(element) || isNotEditingElement(); +} + bool NotationActionController::isToggleVisibleAllowed() const { auto interaction = currentNotationInteraction(); diff --git a/src/notation/internal/notationactioncontroller.h b/src/notation/internal/notationactioncontroller.h index af9dd473f171d..bbec0dc6fc842 100644 --- a/src/notation/internal/notationactioncontroller.h +++ b/src/notation/internal/notationactioncontroller.h @@ -170,6 +170,7 @@ class NotationActionController : public muse::actions::Actionable, public muse:: bool isNoteInputMode() const; bool isEditingElement() const; bool isNotEditingElement() const; + bool isNotEditingOrHasPopup() const; bool isNotNoteInputMode() const; bool isToggleVisibleAllowed() const; @@ -210,7 +211,7 @@ class NotationActionController : public muse::actions::Actionable, public muse:: const mu::engraving::Harmony* editedChordSymbol() const; - bool elementHasPopup(EngravingItem* e); + bool elementHasPopup(const EngravingItem* e) const; bool canUndo() const; bool canRedo() const; diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index fc78a1d3e7b95..9718b073f5e46 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -3228,6 +3228,45 @@ void NotationInteraction::nudge(MoveDirection d, bool quickly) notifyAboutDragChanged(); } +void NotationInteraction::nudgeAnchors(MoveDirection d) +{ + startEdit(TranslatableString("undoableAction", "Nudge")); + qreal vRaster = mu::engraving::MScore::vRaster(); + qreal hRaster = mu::engraving::MScore::hRaster(); + + switch (d) { + case MoveDirection::Left: + m_editData.delta = QPointF(-nudgeDistance(m_editData, hRaster), 0); + break; + case MoveDirection::Right: + m_editData.delta = QPointF(nudgeDistance(m_editData, hRaster), 0); + break; + case MoveDirection::Up: + m_editData.delta = QPointF(0, -nudgeDistance(m_editData, vRaster)); + break; + case MoveDirection::Down: + m_editData.delta = QPointF(0, nudgeDistance(m_editData, vRaster)); + break; + default: + rollback(); + return; + } + + m_editData.evtDelta = m_editData.moveDelta = m_editData.delta; + m_editData.hRaster = hRaster; + m_editData.vRaster = vRaster; + + if (m_editData.curGrip != mu::engraving::Grip::NO_GRIP && int(m_editData.curGrip) < m_editData.grips) { + m_editData.pos = m_editData.grip[int(m_editData.curGrip)].center() + m_editData.delta; + } + + m_editData.element->startEditDrag(m_editData); + m_editData.element->editDrag(m_editData); + m_editData.element->endEditDrag(m_editData); + + apply(); +} + bool NotationInteraction::isTextSelected() const { EngravingItem* selectedElement = m_selection->element(); @@ -4222,9 +4261,13 @@ void NotationInteraction::flipSelection() void NotationInteraction::addTieToSelection() { // Calls `startEdit` internally - score()->cmdToggleTie(); + Tie* newTie = score()->cmdToggleTie(); notifyAboutNotationChanged(); + + if (newTie && newTie->tieJumpPoints() && newTie->tieJumpPoints()->size() > 1) { + selectAndStartEditIfNeeded(newTie); + } } void NotationInteraction::addLaissezVibToSelection() diff --git a/src/notation/internal/notationinteraction.h b/src/notation/internal/notationinteraction.h index 688e5f95aeee9..18d31cc31aa40 100644 --- a/src/notation/internal/notationinteraction.h +++ b/src/notation/internal/notationinteraction.h @@ -134,6 +134,7 @@ class NotationInteraction : public INotationInteraction, public muse::Injectable // Move void movePitch(MoveDirection d, PitchMode mode) override; void nudge(MoveDirection d, bool quickly) override; + void nudgeAnchors(MoveDirection) override; void moveChordRestToStaff(MoveDirection d) override; void moveLyrics(MoveDirection d) override; void swapChordRest(MoveDirection d) override; diff --git a/src/notation/internal/notationundostack.cpp b/src/notation/internal/notationundostack.cpp index dfca501cda624..7c69e6b2d2fd7 100644 --- a/src/notation/internal/notationundostack.cpp +++ b/src/notation/internal/notationundostack.cpp @@ -153,6 +153,15 @@ bool NotationUndoStack::isStackClean() const return undoStack()->isClean(); } +void NotationUndoStack::mergeCommands(size_t startIdx) +{ + IF_ASSERT_FAILED(undoStack()) { + return; + } + + undoStack()->mergeCommands(startIdx); +} + void NotationUndoStack::lock() { IF_ASSERT_FAILED(undoStack()) { diff --git a/src/notation/internal/notationundostack.h b/src/notation/internal/notationundostack.h index 3b12b12931cbc..77bac4857211d 100644 --- a/src/notation/internal/notationundostack.h +++ b/src/notation/internal/notationundostack.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_NOTATION_UNDOSTACK -#define MU_NOTATION_UNDOSTACK +#pragma once #include "inotationundostack.h" #include "igetscore.h" @@ -53,6 +52,8 @@ class NotationUndoStack : public INotationUndoStack bool isStackClean() const override; + void mergeCommands(size_t startIdx) override; + void lock() override; void unlock() override; bool isLocked() const override; @@ -83,5 +84,3 @@ class NotationUndoStack : public INotationUndoStack muse::async::Notification m_undoRedoNotification; }; } - -#endif // MU_NOTATION_UNDOSTACK diff --git a/src/notation/notationmodule.cpp b/src/notation/notationmodule.cpp index 8986d0b96cb66..c89e760650340 100644 --- a/src/notation/notationmodule.cpp +++ b/src/notation/notationmodule.cpp @@ -74,6 +74,7 @@ #include "view/internal/harppedalpopupmodel.h" #include "view/internal/caposettingsmodel.h" #include "view/internal/stringtuningssettingsmodel.h" +#include "view/internal/partialtiepopupmodel.h" #include "view/percussionpanel/percussionpanelmodel.h" @@ -186,6 +187,7 @@ void NotationModule::registerUiTypes() qmlRegisterType("MuseScore.NotationScene", 1, 0, "HarpPedalPopupModel"); qmlRegisterType("MuseScore.NotationScene", 1, 0, "CapoSettingsModel"); qmlRegisterType("MuseScore.NotationScene", 1, 0, "StringTuningsSettingsModel"); + qmlRegisterType("MuseScore.NotationScene", 1, 0, "PartialTiePopupModel"); qmlRegisterType("MuseScore.NotationScene", 1, 0, "PaintedEngravingItem"); qmlRegisterType("MuseScore.NotationScene", 1, 0, "PercussionPanelModel"); diff --git a/src/notation/notationscene.qrc b/src/notation/notationscene.qrc index 4b6f5e9730be1..486e8294bab0e 100644 --- a/src/notation/notationscene.qrc +++ b/src/notation/notationscene.qrc @@ -65,5 +65,6 @@ qml/MuseScore/NotationScene/internal/EditStyle/StyleControlRowWithReset.qml qml/MuseScore/NotationScene/internal/EditStyle/TextFieldWithReset.qml qml/MuseScore/NotationScene/internal/EditStyle/IconAndTextButtonSelector.qml + qml/MuseScore/NotationScene/internal/PartialTiePopup.qml diff --git a/src/notation/qml/MuseScore/NotationScene/internal/CapoPopup.qml b/src/notation/qml/MuseScore/NotationScene/internal/CapoPopup.qml index 259d74a670f2d..bb724f4820042 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/CapoPopup.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/CapoPopup.qml @@ -33,6 +33,9 @@ StyledPopupView { property int navigationOrderStart: 0 property int navigationOrderEnd: capoSettingsNavPanel.order + property QtObject model: capoModel + + contentWidth: content.width contentHeight: content.height diff --git a/src/notation/qml/MuseScore/NotationScene/internal/ElementPopupLoader.qml b/src/notation/qml/MuseScore/NotationScene/internal/ElementPopupLoader.qml index 9c9fbf3b93d3b..e9aafdb23c6f1 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/ElementPopupLoader.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/ElementPopupLoader.qml @@ -53,6 +53,7 @@ Item { case Notation.TYPE_CAPO: return capoComp case Notation.TYPE_STRING_TUNINGS: return stringTuningsComp case Notation.TYPE_SOUND_FLAG: return soundFlagComp + case Notation.TYPE_PARTIAL_TIE: return partialTieComp } return null @@ -71,7 +72,13 @@ Item { function show(elementType, elementRect) { close() - var popup = loader.loadPopup(prv.componentByType(elementType), elementRect) + var component = prv.componentByType(elementType) + + var popup = loader.loadPopup(component, elementRect) + if (!popup.model.canOpen) { + loader.unloadPopup() + return + } popup.open() } @@ -150,4 +157,10 @@ Item { SoundFlagPopup { } } + + Component { + id: partialTieComp + PartialTiePopup { + } + } } diff --git a/src/notation/qml/MuseScore/NotationScene/internal/PartialTiePopup.qml b/src/notation/qml/MuseScore/NotationScene/internal/PartialTiePopup.qml new file mode 100644 index 0000000000000..057dc3c3c9dae --- /dev/null +++ b/src/notation/qml/MuseScore/NotationScene/internal/PartialTiePopup.qml @@ -0,0 +1,115 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2022 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 + +import Muse.Ui 1.0 +import Muse.UiComponents 1.0 +import MuseScore.NotationScene 1.0 + +StyledPopupView { + id: root + margins: 0 + + property NavigationSection notationViewNavigationSection: null + property int navigationOrderStart: 0 + property int navigationOrderEnd: partialTieNavPanel.order + + property QtObject model: partialTiePopupModel + + showArrow: false + + onOpened: { + tieMenuLoader.show(Qt.point(0, 0)) + } + + onClosed: { + tieMenuLoader.close() + partialTiePopupModel.onClosed() + } + + signal elementRectChanged(var elementRect) + + function updatePosition() { + const opensUp = partialTiePopupModel.tieDirection + const popupHeight = tieMenuLoader.item.height + root.margins * 2 + root.padding * 2 + root.x = partialTiePopupModel.dialogPosition.x - root.parent.x + root.y = partialTiePopupModel.dialogPosition.y - root.parent.y - (opensUp ? popupHeight : 0) + root.setOpensUpward(opensUp) + } + + contentWidth: content.width + contentHeight: content.childrenRect.height + + ColumnLayout { + id: content + + PartialTiePopupModel { + id: partialTiePopupModel + + onItemRectChanged: function(rect) { + root.elementRectChanged(rect) + } + + onItemsChanged: function() { + tieMenuLoader.items = partialTiePopupModel.items + tieMenuLoader.show(Qt.point(0, 0)) + } + } + + Component.onCompleted: { + partialTiePopupModel.init() + } + + ContextMenuLoader { + id: tieMenuLoader + closeMenuOnSelection: false + focusOnOpened: false + opensUpward: root.opensUpward + + items: partialTiePopupModel.items + + onHandleMenuItem: function(itemId) { + partialTiePopupModel.toggleItemChecked(itemId) + } + } + + NavigationPanel { + id: partialTieNavPanel + name: "PartialTieMenu" + direction: NavigationPanel.Vertical + section: root.notationViewNavigationSection + order: root.navigationOrderStart + accessible.name: qsTrc("notation", "Partial tie menu items") + + onSectionChanged: function() { + tieMenuLoader.notationViewNavigationSection = section + } + + onOrderChanged: function() { + tieMenuLoader.navigationOrderStart = order + } + } + } +} diff --git a/src/notation/qml/MuseScore/NotationScene/internal/StringTuningsPopup.qml b/src/notation/qml/MuseScore/NotationScene/internal/StringTuningsPopup.qml index 20384da8721d5..adf36f863dffa 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/StringTuningsPopup.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/StringTuningsPopup.qml @@ -33,6 +33,9 @@ StyledPopupView { property int navigationOrderStart: 0 property int navigationOrderEnd: navPanel.order + property QtObject model: stringTuningsModel + + contentWidth: content.width contentHeight: content.height diff --git a/src/notation/tests/mocks/notationinteractionmock.h b/src/notation/tests/mocks/notationinteractionmock.h index 91f37f276d721..c65190aed7a9c 100644 --- a/src/notation/tests/mocks/notationinteractionmock.h +++ b/src/notation/tests/mocks/notationinteractionmock.h @@ -96,6 +96,7 @@ class NotationInteractionMock : public INotationInteraction MOCK_METHOD(void, movePitch, (MoveDirection, PitchMode), (override)); MOCK_METHOD(void, nudge, (MoveDirection, bool), (override)); + MOCK_METHOD(void, nudgeAnchors, (MoveDirection), (override)); MOCK_METHOD(void, moveChordRestToStaff, (MoveDirection), (override)); MOCK_METHOD(void, swapChordRest, (MoveDirection), (override)); MOCK_METHOD(void, toggleSnapToPrevious, (), (override)); diff --git a/src/notation/view/abstractelementpopupmodel.cpp b/src/notation/view/abstractelementpopupmodel.cpp index 7baa54da27337..5d78618a84afd 100644 --- a/src/notation/view/abstractelementpopupmodel.cpp +++ b/src/notation/view/abstractelementpopupmodel.cpp @@ -30,6 +30,8 @@ static const QMap ELEMENT_POPUP_TYPE { mu::engraving::ElementType::CAPO, PopupModelType::TYPE_CAPO }, { mu::engraving::ElementType::STRING_TUNINGS, PopupModelType::TYPE_STRING_TUNINGS }, { mu::engraving::ElementType::SOUND_FLAG, PopupModelType::TYPE_SOUND_FLAG }, + { mu::engraving::ElementType::TIE_SEGMENT, PopupModelType::TYPE_PARTIAL_TIE }, + { mu::engraving::ElementType::PARTIAL_TIE_SEGMENT, PopupModelType::TYPE_PARTIAL_TIE } }; static const QHash POPUP_DEPENDENT_ELEMENT_TYPES = { @@ -37,6 +39,7 @@ static const QHash POPUP_DEPENDEN { PopupModelType::TYPE_CAPO, { mu::engraving::ElementType::CAPO } }, { PopupModelType::TYPE_STRING_TUNINGS, { mu::engraving::ElementType::STRING_TUNINGS } }, { PopupModelType::TYPE_SOUND_FLAG, { mu::engraving::ElementType::SOUND_FLAG, mu::engraving::ElementType::STAFF_TEXT } }, + { PopupModelType::TYPE_PARTIAL_TIE, { mu::engraving::ElementType::PARTIAL_TIE_SEGMENT, mu::engraving::ElementType::TIE_SEGMENT } }, }; AbstractElementPopupModel::AbstractElementPopupModel(PopupModelType modelType, QObject* parent) @@ -182,6 +185,10 @@ void AbstractElementPopupModel::init() m_item = selection->element(); + if (!canOpen()) { + return; + } + undoStack->changesChannel().onReceive(this, [this] (const ChangesRange& range) { for (ElementType type : dependentElementTypes()) { if (muse::contains(range.changedTypes, type)) { diff --git a/src/notation/view/abstractelementpopupmodel.h b/src/notation/view/abstractelementpopupmodel.h index 6dedce4798160..d26b5a2b9a7b9 100644 --- a/src/notation/view/abstractelementpopupmodel.h +++ b/src/notation/view/abstractelementpopupmodel.h @@ -19,8 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef MU_NOTATION_ABSTRACTELEMENTPOPUPMODEL_H -#define MU_NOTATION_ABSTRACTELEMENTPOPUPMODEL_H +#pragma once #include @@ -39,6 +38,7 @@ class AbstractElementPopupModel : public QObject, public muse::Injectable, publi Q_PROPERTY(PopupModelType modelType READ modelType CONSTANT) Q_PROPERTY(QRect itemRect READ itemRect NOTIFY itemRectChanged) + Q_PROPERTY(bool canOpen READ canOpen CONSTANT) public: muse::Inject dispatcher = { this }; @@ -50,7 +50,8 @@ class AbstractElementPopupModel : public QObject, public muse::Injectable, publi TYPE_HARP_DIAGRAM, TYPE_CAPO, TYPE_STRING_TUNINGS, - TYPE_SOUND_FLAG + TYPE_SOUND_FLAG, + TYPE_PARTIAL_TIE }; Q_ENUM(PopupModelType) @@ -59,6 +60,8 @@ class AbstractElementPopupModel : public QObject, public muse::Injectable, publi PopupModelType modelType() const; QRect itemRect() const; + virtual bool canOpen() const { return true; } + static bool supportsPopup(const mu::engraving::ElementType& elementType); static PopupModelType modelTypeFromElement(const mu::engraving::ElementType& elementType); @@ -79,6 +82,7 @@ class AbstractElementPopupModel : public QObject, public muse::Injectable, publi void endMultiCommands(); void updateNotation(); notation::INotationPtr currentNotation() const; + INotationInteractionPtr interaction() const; void changeItemProperty(mu::engraving::Pid id, const PropertyValue& value); void changeItemProperty(mu::engraving::Pid id, const PropertyValue& value, engraving::PropertyFlags flags); @@ -86,7 +90,6 @@ class AbstractElementPopupModel : public QObject, public muse::Injectable, publi EngravingItem* m_item = nullptr; private: - INotationInteractionPtr interaction() const; INotationSelectionPtr selection() const; engraving::ElementType elementType() const; @@ -111,5 +114,3 @@ inline size_t qHash(mu::notation::PopupModelType key) #ifndef NO_QT_SUPPORT Q_DECLARE_METATYPE(mu::notation::PopupModelType) #endif - -#endif // MU_NOTATION_ABSTRACTELEMENTPOPUPMODEL_H diff --git a/src/notation/view/internal/partialtiepopupmodel.cpp b/src/notation/view/internal/partialtiepopupmodel.cpp new file mode 100644 index 0000000000000..b2eb607323e13 --- /dev/null +++ b/src/notation/view/internal/partialtiepopupmodel.cpp @@ -0,0 +1,187 @@ +#include "partialtiepopupmodel.h" +#include "dom/partialtie.h" +#include "dom/tie.h" +#include "dom/undo.h" + +using namespace mu::notation; +using namespace mu::engraving; +using namespace muse::uicomponents; +using namespace muse::ui; + +PartialTiePopupModel::PartialTiePopupModel(QObject* parent) + : AbstractElementPopupModel(PopupModelType::TYPE_PARTIAL_TIE, parent) +{ +} + +bool PartialTiePopupModel::tieDirection() const +{ + if (!m_item) { + return false; + } + const Tie* tieItem = tie(); + return tieItem->up(); +} + +bool PartialTiePopupModel::canOpen() const +{ + Tie* tieItem = tie(); + if (!tieItem || !tieItem->tieJumpPoints()) { + return false; + } + + if (tieItem->tieJumpPoints()->size() < 2) { + return false; + } + + return tieItem->isPartialTie() ? toPartialTie(tieItem)->isOutgoing() : true; +} + +QPointF PartialTiePopupModel::dialogPosition() const +{ + const Tie* tieItem = tie(); + const Note* startNote = tieItem ? tieItem->startNote() : nullptr; + const TieSegment* seg = toTieSegment(m_item); + if (!seg || !startNote) { + return QPointF(); + } + const RectF segRect = seg->canvasBoundingRect(); + const double x = startNote->canvasBoundingRect().x(); + const int up = tieItem->up() ? -1 : 1; + const double y = (tieItem->up() ? segRect.top() + segRect.height() * 2 + / 3 : segRect.bottom() - segRect.height() / 3) + tieItem->spatium() * up; + + return fromLogical(PointF(x, y)).toQPointF(); +} + +QVariantList PartialTiePopupModel::items() const +{ + QVariantList items; + + for (MenuItem* item: m_items) { + items << QVariant::fromValue(item); + } + + return items; +} + +void PartialTiePopupModel::init() +{ + AbstractElementPopupModel::init(); + + connect(this, &AbstractElementPopupModel::dataChanged, [this]() { + load(); + }); + + load(); +} + +void PartialTiePopupModel::toggleItemChecked(QString& id) +{ + Tie* tieItem = tie(); + if (!tieItem || !tieItem->tieJumpPoints()) { + return; + } + + for (MenuItem* item : m_items) { + if (item->id() != id) { + continue; + } + UiActionState state = item->state(); + state.checked = !state.checked; + item->setState(state); + break; + } + + TieJumpPointList* jumpList = tieItem->tieJumpPoints(); + jumpList->toggleJumpPoint(id); + Tie* newTie = jumpList->startTie(); + + // Update popup item if it has changed + if (newTie && newTie != tieItem) { + m_item = newTie->segmentsEmpty() ? nullptr : newTie->frontSegment(); + + interaction()->endEditGrip(); + interaction()->endEditElement(); + interaction()->startEditGrip(m_item, Grip::DRAG); + } + + updateNotation(); + emit itemsChanged(); +} + +void PartialTiePopupModel::load() +{ + Tie* tieItem = tie(); + if (!tieItem) { + return; + } + + tieItem->updatePossibleJumpPoints(); + + m_items = makeMenuItems(); + + // load items + emit tieDirectionChanged(tieDirection()); + emit itemsChanged(); +} + +MenuItemList PartialTiePopupModel::makeMenuItems() +{ + Tie* tieItem = tie(); + if (!tieItem || !tieItem->tieJumpPoints()) { + return MenuItemList{}; + } + + MenuItemList itemList; + + for (const TieJumpPoint* jumpPoint : *tieItem->tieJumpPoints()) { + itemList << makeMenuItem(jumpPoint); + } + + return itemList; +} + +muse::uicomponents::MenuItem* PartialTiePopupModel::makeMenuItem(const engraving::TieJumpPoint* jumpPoint) +{ + MenuItem* item = new MenuItem(this); + item->setId(jumpPoint->id()); + TranslatableString title = TranslatableString("notation", jumpPoint->menuTitle()); + item->setTitle(title); + + UiAction action; + action.title = title; + action.checkable = Checkable::Yes; + item->setAction(action); + + UiActionState state; + state.enabled = true; + state.checked = jumpPoint->active(); + item->setState(state); + + return item; +} + +Tie* PartialTiePopupModel::tie() const +{ + const TieSegment* tieSeg = m_item && m_item->isTieSegment() ? toTieSegment(m_item) : nullptr; + + return tieSeg ? tieSeg->tie() : nullptr; +} + +void mu::notation::PartialTiePopupModel::onClosed() +{ + Tie* tieItem = tie(); + if (!tieItem) { + return; + } + + if (tieItem->allJumpPointsInactive()) { + Score* score = tieItem->score(); + beginCommand(TranslatableString("engraving", "Remove partial tie")); + score->undoRemoveElement(tieItem); + endCommand(); + + // Combine this with the last undoable action (which will be to remove a tie) so the user cannot undo to get a translucent tie + undoStack()->mergeCommands(undoStack()->currentStateIndex() - 2); + } +} diff --git a/src/notation/view/internal/partialtiepopupmodel.h b/src/notation/view/internal/partialtiepopupmodel.h new file mode 100644 index 0000000000000..ebee372a49f1d --- /dev/null +++ b/src/notation/view/internal/partialtiepopupmodel.h @@ -0,0 +1,41 @@ +#pragma once + +#include "view/abstractelementpopupmodel.h" +#include "framework/uicomponents/view/menuitem.h" +#include "dom/tie.h" + +namespace mu::notation { +class PartialTiePopupModel : public AbstractElementPopupModel +{ + Q_OBJECT + + Q_PROPERTY(QVariantList items READ items NOTIFY itemsChanged) + Q_PROPERTY(bool tieDirection READ tieDirection NOTIFY tieDirectionChanged) + Q_PROPERTY(QPointF dialogPosition READ dialogPosition CONSTANT) + +public: + explicit PartialTiePopupModel(QObject* parent = nullptr); + + QVariantList items() const; + bool tieDirection() const; + bool canOpen() const override; + QPointF dialogPosition() const; + + Q_INVOKABLE void init() override; + Q_INVOKABLE void toggleItemChecked(QString& id); + + Q_INVOKABLE void onClosed(); + +signals: + void tieDirectionChanged(bool direction); + void itemsChanged(); + +private: + void load(); + muse::uicomponents::MenuItemList makeMenuItems(); + muse::uicomponents::MenuItem* makeMenuItem(const engraving::TieJumpPoint* jumpPoint); + mu::engraving::Tie* tie() const; + + muse::uicomponents::MenuItemList m_items; +}; +} diff --git a/src/notation/view/notationviewinputcontroller.cpp b/src/notation/view/notationviewinputcontroller.cpp index b01788683b4fa..0249b300db1f9 100644 --- a/src/notation/view/notationviewinputcontroller.cpp +++ b/src/notation/view/notationviewinputcontroller.cpp @@ -103,9 +103,9 @@ void NotationViewInputController::init() m_view->showContextMenu(selectionType(), m_view->fromLogical(selectionElementPos()).toQPointF()); }); - dispatcher()->reg(this, "notation-popup-menu", [this]() { - if (auto selection = viewInteraction()->selection()) { - togglePopupForItemIfSupports(selection->element()); + dispatcher()->reg(this, "notation-popup-menu", [this](const ActionData& args) { + if (EngravingItem* el = args.arg()) { + togglePopupForItemIfSupports(el); } }); diff --git a/src/notation/view/widgets/editstyle.cpp b/src/notation/view/widgets/editstyle.cpp index aa3b2e83b0888..c45bac9a7d442 100644 --- a/src/notation/view/widgets/editstyle.cpp +++ b/src/notation/view/widgets/editstyle.cpp @@ -495,6 +495,7 @@ EditStyle::EditStyle(QWidget* parent) { StyleId::tieDottedWidth, false, tieDottedLineWidth, resetTieDottedLineWidth }, { StyleId::tieMinDistance, false, tieMinDistance, resetTieMinDistance }, { StyleId::minTieLength, false, minTieLength, resetMinTieLength }, + { StyleId::minHangingTieLength, false, minHangingTieLength, resetMinHangingTieLength }, { StyleId::minLaissezVibLength, false, minLaissezVibLength, resetMinLaissezVibLength }, { StyleId::laissezVibUseSmuflSym, false, laissezVibUseSmufl, 0 }, @@ -1577,6 +1578,8 @@ QString EditStyle::pageCodeForElement(const EngravingItem* element) case ElementType::TIE_SEGMENT: case ElementType::LAISSEZ_VIB: case ElementType::LAISSEZ_VIB_SEGMENT: + case ElementType::PARTIAL_TIE: + case ElementType::PARTIAL_TIE_SEGMENT: return "slurs-and-ties"; case ElementType::HAIRPIN: diff --git a/src/notation/view/widgets/editstyle.ui b/src/notation/view/widgets/editstyle.ui index 1a9a77ce89c40..3a6234408a9f8 100644 --- a/src/notation/view/widgets/editstyle.ui +++ b/src/notation/view/widgets/editstyle.ui @@ -8106,6 +8106,77 @@ + + + + + + + + 0 + 0 + + + + Minimum hanging tie length: + + + minHangingTieLength + + + + + + + + 0 + 0 + + + + false + + + sp + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + Reset to default + + + Reset 'Minimum hanging tie length' value + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + diff --git a/src/playback/qml/MuseScore/Playback/SoundFlagPopup.qml b/src/playback/qml/MuseScore/Playback/SoundFlagPopup.qml index 2edd0dc97a47d..a27116cdd472e 100644 --- a/src/playback/qml/MuseScore/Playback/SoundFlagPopup.qml +++ b/src/playback/qml/MuseScore/Playback/SoundFlagPopup.qml @@ -36,6 +36,8 @@ StyledPopupView { property int navigationOrderStart: 0 property int navigationOrderEnd: museSoundsParams.navigationPanelOrderEnd + property QtObject model: soundFlagModel + contentWidth: content.width contentHeight: content.childrenRect.height onContentHeightChanged: { diff --git a/vtest/scores/tie-min-length.mscz b/vtest/scores/tie-min-length.mscz new file mode 100644 index 0000000000000..8d073dadc7393 Binary files /dev/null and b/vtest/scores/tie-min-length.mscz differ