diff --git a/src/appshell/qml/Preferences/internal/NoteInput/NoteColorsSection.qml b/src/appshell/qml/Preferences/internal/NoteInput/NoteColorsSection.qml index 4178caa2766d4..de330eed55bcb 100644 --- a/src/appshell/qml/Preferences/internal/NoteInput/NoteColorsSection.qml +++ b/src/appshell/qml/Preferences/internal/NoteInput/NoteColorsSection.qml @@ -55,7 +55,7 @@ BaseSection { id: warnBendsBox width: parent.width - text: qsTrc("appshell/preferences", "Color guitar bends outside of playable range") + text: qsTrc("appshell/preferences", "Color guitar bends and hammer-ons/pull-offs outside of playable range") navigation.name: "WarnBendBox" navigation.panel: root.navigation diff --git a/src/engraving/dom/chord.cpp b/src/engraving/dom/chord.cpp index b370b56090439..37eb33ec05052 100644 --- a/src/engraving/dom/chord.cpp +++ b/src/engraving/dom/chord.cpp @@ -909,6 +909,33 @@ double Chord::centerX() const return x; } +bool Chord::allNotesTiedToNext() const +{ + Chord* tiedChord = nullptr; + for (Note* note : m_notes) { + if (!note->tieFor()) { + return false; + } + + Note* endNote = note->tieFor()->endNote(); + Chord* endChord = endNote ? endNote->chord() : nullptr; + if (!endChord) { + return false; + } + + if (!tiedChord) { + tiedChord = endChord; + continue; + } + + if (endChord != tiedChord) { + return false; + } + } + + return true; +} + //--------------------------------------------------------- // processSiblings //--------------------------------------------------------- diff --git a/src/engraving/dom/chord.h b/src/engraving/dom/chord.h index 48c6b3a58e703..1720c680bbc81 100644 --- a/src/engraving/dom/chord.h +++ b/src/engraving/dom/chord.h @@ -330,6 +330,8 @@ class Chord final : public ChordRest StartEndSlurs& startEndSlurs() { return m_startEndSlurs; } + bool allNotesTiedToNext() const; + private: friend class Factory; diff --git a/src/engraving/dom/dom.cmake b/src/engraving/dom/dom.cmake index 95561e8f79b2c..f4ed44fabe0b5 100644 --- a/src/engraving/dom/dom.cmake +++ b/src/engraving/dom/dom.cmake @@ -131,6 +131,8 @@ set(DOM_SRC ${CMAKE_CURRENT_LIST_DIR}/harmonicmark.h ${CMAKE_CURRENT_LIST_DIR}/harmony.cpp ${CMAKE_CURRENT_LIST_DIR}/harmony.h + ${CMAKE_CURRENT_LIST_DIR}/hammeronpulloff.cpp + ${CMAKE_CURRENT_LIST_DIR}/hammeronpulloff.h ${CMAKE_CURRENT_LIST_DIR}/harppedaldiagram.cpp ${CMAKE_CURRENT_LIST_DIR}/harppedaldiagram.h ${CMAKE_CURRENT_LIST_DIR}/hook.cpp diff --git a/src/engraving/dom/edit.cpp b/src/engraving/dom/edit.cpp index aa09a089be1fd..7446d54c58fa2 100644 --- a/src/engraving/dom/edit.cpp +++ b/src/engraving/dom/edit.cpp @@ -44,6 +44,7 @@ #include "glissando.h" #include "guitarbend.h" #include "hairpin.h" +#include "hammeronpulloff.h" #include "harmony.h" #include "harppedaldiagram.h" #include "hook.h" @@ -667,8 +668,32 @@ Slur* Score::addSlur(ChordRest* firstChordRest, ChordRest* secondChordRest, cons options.disableOverRepeats = true; secondChordRest = nextChordRest(firstChordRest, options); - if (!secondChordRest) { - secondChordRest = firstChordRest; + if (!secondChordRest || !secondChordRest->isChord()) { + if (slurTemplate && slurTemplate->isHammerOnPullOff() && firstChordRest->isChord()) { + Note* endNote = GuitarBend::createEndNote(toChord(firstChordRest)->upNote()); + if (endNote) { + secondChordRest = endNote->chord(); + } + } + if (!secondChordRest) { + secondChordRest = firstChordRest; + } + } else if (secondChordRest->isChord()) { + bool firstChordRestIsTiedToSecond = firstChordRest->isChord() && toChord(firstChordRest)->allNotesTiedToNext() + && toChord(firstChordRest)->upNote()->tieFor()->endNote()->parent() == secondChordRest; + + // Follow chain of tied notes and slur until the last + while (toChord(secondChordRest)->allNotesTiedToNext()) { + secondChordRest = toChord(secondChordRest)->upNote()->tieFor()->endNote()->chord(); + } + + // If the first chord rest is also tied to this chain, slur to the next non-tied note + if (firstChordRestIsTiedToSecond) { + ChordRest* nextCandidate = nextChordRest(secondChordRest, options); + if (nextCandidate) { + secondChordRest = nextCandidate; + } + } } } @@ -687,7 +712,7 @@ Slur* Score::addSlur(ChordRest* firstChordRest, ChordRest* secondChordRest, cons slur->setEndElement(secondChordRest); firstChordRest->score()->undoAddElement(slur); - SlurSegment* ss = new SlurSegment(firstChordRest->score()->dummy()->system()); + SlurTieSegment* ss = slur->newSlurTieSegment(firstChordRest->score()->dummy()->system()); ss->setSpannerSegmentType(SpannerSegmentType::SINGLE); if (firstChordRest == secondChordRest && !(slur->isOutgoing() || slur->isIncoming())) { ss->setSlurOffset(Grip::END, PointF(3.0 * firstChordRest->style().spatium(), 0.0)); @@ -2507,7 +2532,8 @@ void Score::cmdFlip() || e->isPedalSegment() || e->isLyrics() || e->isBreath() - || e->isFermata()) { + || e->isFermata() + || e->isHammerOnPullOffText()) { e->undoChangeProperty(Pid::AUTOPLACE, true); // TODO: undoChangeProperty() should probably do this directly // see https://musescore.org/en/node/281432 @@ -2603,6 +2629,7 @@ void Score::deleteItem(EngravingItem* el) case ElementType::KEYSIG: case ElementType::MEASURE_NUMBER: case ElementType::SYSTEM_LOCK_INDICATOR: + case ElementType::HAMMER_ON_PULL_OFF_TEXT: break; // All other types cannot be removed if generated default: @@ -3046,6 +3073,7 @@ void Score::deleteItem(EngravingItem* el) case ElementType::HARMONIC_MARK_SEGMENT: case ElementType::PICK_SCRAPE_SEGMENT: case ElementType::GUITAR_BEND_SEGMENT: + case ElementType::HAMMER_ON_PULL_OFF_SEGMENT: { el = toSpannerSegment(el)->spanner(); if (el->isTie()) { @@ -3061,6 +3089,10 @@ void Score::deleteItem(EngravingItem* el) } break; + case ElementType::HAMMER_ON_PULL_OFF_TEXT: + undoRemoveHopoText(toHammerOnPullOffText(el)); + break; + case ElementType::STEM_SLASH: // cannot delete this elements case ElementType::HOOK: case ElementType::GUITAR_BEND_TEXT: @@ -5138,7 +5170,7 @@ void Score::cloneVoice(track_idx_t strack, track_idx_t dtrack, Segment* sf, cons if (spanner) { // Find and add corresponding slurs and hairpins - static const std::set SPANNERS_TO_COPY { ElementType::SLUR, ElementType::HAIRPIN }; + static const std::set SPANNERS_TO_COPY { ElementType::SLUR, ElementType::HAMMER_ON_PULL_OFF, ElementType::HAIRPIN }; auto spanners = score->spannerMap().findOverlapping(start.ticks(), lTick.ticks()); for (auto i = spanners.begin(); i < spanners.end(); i++) { Spanner* sp = i->value; @@ -5996,6 +6028,7 @@ static void undoChangeNoteVisibility(Note* note, bool visible) ElementType::NOTE, ElementType::LYRICS, ElementType::SLUR, + ElementType::HAMMER_ON_PULL_OFF, ElementType::CHORD, // grace notes ElementType::LEDGER_LINE, // temporary objects, impossible to change visibility }; @@ -6284,6 +6317,7 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool && et != ElementType::CHORDLINE && et != ElementType::LYRICS && et != ElementType::SLUR + && et != ElementType::HAMMER_ON_PULL_OFF && et != ElementType::TIE && et != ElementType::NOTE && et != ElementType::INSTRUMENT_CHANGE @@ -6355,6 +6389,7 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool ElementType::OTTAVA, ElementType::TRILL, ElementType::SLUR, + ElementType::HAMMER_ON_PULL_OFF, ElementType::VIBRATO, ElementType::TEXTLINE, ElementType::PEDAL, @@ -7066,6 +7101,72 @@ void Score::undoRemoveElement(EngravingItem* element, bool removeLinked) } } +void Score::undoRemoveHopoText(HammerOnPullOffText* hopoText) +{ + Chord* startChord = hopoText->startChord(); + Chord* endChord = hopoText->endChord(); + IF_ASSERT_FAILED(startChord && endChord) { + return; + } + + HammerOnPullOffSegment* hopoSegment = toHammerOnPullOffSegment(hopoText->parentItem()); + HammerOnPullOff* hopo = hopoSegment ? hopoSegment->hammerOnPullOff() : nullptr; + IF_ASSERT_FAILED(hopo) { + return; + } + + Chord* hopoStartChord = toChord(hopo->startElement()); + Chord* hopoEndChord = toChord(hopo->endElement()); + IF_ASSERT_FAILED(hopoStartChord && hopoEndChord) { + return; + } + + if (startChord == hopoStartChord && endChord == hopoEndChord) { + undoRemoveElement(hopo); + return; + } + + Fraction hopoStartTick = hopo->tick(); + Fraction hopoEndTick = hopo->tick2(); + Fraction hopoTextStartTick = startChord->tick(); + Fraction hopoTextEndTick = endChord->tick(); + + bool shortenFromStart = (hopoTextStartTick - hopoStartTick) < (hopoEndTick - hopoTextEndTick); + EditData editData; + editData.curGrip = shortenFromStart ? Grip::START : Grip::END; + + if (shortenFromStart) { + Fraction newStartTick = hopoTextEndTick; + Fraction newTicks = hopoEndTick - newStartTick; + hopo->undoChangeProperty(Pid::SPANNER_TICK, newStartTick); + hopo->undoChangeProperty(Pid::SPANNER_TICKS, newTicks); + hopo->undoChangeStartEndElements(endChord, hopoEndChord); + if (startChord != hopoStartChord) { + HammerOnPullOff* newHopo = Factory::createHammerOnPullOff(score()->dummy()); + newHopo->setTrack(hopo->track()); + newHopo->setTick(hopoStartTick); + newHopo->setTick2(hopoTextStartTick); + newHopo->setStartElement(hopoStartChord); + newHopo->setEndElement(startChord); + score()->undoAddElement(newHopo); + } + } else { + Fraction newEndTick = hopoTextStartTick; + Fraction newTicks = newEndTick - hopoStartTick; + hopo->undoChangeProperty(Pid::SPANNER_TICKS, newTicks); + hopo->undoChangeStartEndElements(hopoStartChord, startChord); + if (endChord != hopoEndChord) { + HammerOnPullOff* newHopo = new HammerOnPullOff(score()->dummy()); + newHopo->setTrack(hopo->track()); + newHopo->setTick(hopoTextEndTick); + newHopo->setTick2(hopoEndTick); + newHopo->setStartElement(endChord); + newHopo->setEndElement(hopoEndChord); + score()->undoAddElement(newHopo); + } + } +} + //--------------------------------------------------------- // undoChangeSpannerElements //--------------------------------------------------------- diff --git a/src/engraving/dom/engravingobject.cpp b/src/engraving/dom/engravingobject.cpp index 4c38812778b29..baf721ad7d84b 100644 --- a/src/engraving/dom/engravingobject.cpp +++ b/src/engraving/dom/engravingobject.cpp @@ -757,7 +757,8 @@ bool EngravingObject::isTextBase() const || type() == ElementType::MMREST_RANGE || type() == ElementType::STICKING || type() == ElementType::HARP_DIAGRAM - || type() == ElementType::GUITAR_BEND_TEXT; + || type() == ElementType::GUITAR_BEND_TEXT + || type() == ElementType::HAMMER_ON_PULL_OFF_TEXT; } //--------------------------------------------------------- diff --git a/src/engraving/dom/engravingobject.h b/src/engraving/dom/engravingobject.h index 5cfe6d035beec..d7d6513aa84b8 100644 --- a/src/engraving/dom/engravingobject.h +++ b/src/engraving/dom/engravingobject.h @@ -84,6 +84,9 @@ class GuitarBendText; class HBox; class Hairpin; class HairpinSegment; +class HammerOnPullOff; +class HammerOnPullOffSegment; +class HammerOnPullOffText; class HarmonicMark; class HarmonicMarkSegment; class Harmony; @@ -346,7 +349,6 @@ class EngravingObject CONVERT(VBox, VBOX) CONVERT(TBox, TBOX) CONVERT(FBox, FBOX) - CONVERT(Slur, SLUR) CONVERT(Glissando, GLISSANDO) CONVERT(GlissandoSegment, GLISSANDO_SEGMENT) CONVERT(GuitarBend, GUITAR_BEND) @@ -370,7 +372,6 @@ class EngravingObject CONVERT(Beam, BEAM) CONVERT(Hook, HOOK) CONVERT(StemSlash, STEM_SLASH) - CONVERT(SlurSegment, SLUR_SEGMENT) CONVERT(LaissezVibSegment, LAISSEZ_VIB_SEGMENT) CONVERT(LaissezVib, LAISSEZ_VIB) CONVERT(PartialTieSegment, PARTIAL_TIE_SEGMENT) @@ -457,6 +458,9 @@ class EngravingObject CONVERT(TimeTickAnchor, TIME_TICK_ANCHOR) CONVERT(Parenthesis, PARENTHESIS) CONVERT(ShadowNote, SHADOW_NOTE) + CONVERT(HammerOnPullOff, HAMMER_ON_PULL_OFF) + CONVERT(HammerOnPullOffSegment, HAMMER_ON_PULL_OFF_SEGMENT) + CONVERT(HammerOnPullOffText, HAMMER_ON_PULL_OFF_TEXT) #undef CONVERT virtual bool isEngravingItem() const { return false; } // overridden in element.h @@ -486,6 +490,16 @@ class EngravingObject || isNoteLineSegment(); } + bool isSlur() const + { + return type() == ElementType::SLUR || type() == ElementType::HAMMER_ON_PULL_OFF; + } + + bool isSlurSegment() const + { + return type() == ElementType::SLUR_SEGMENT || type() == ElementType::HAMMER_ON_PULL_OFF_SEGMENT; + } + bool isLineSegment() const { return isGlissandoSegment() @@ -626,7 +640,8 @@ 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::PARTIAL_TIE_SEGMENT); + || e->type() == ElementType::LAISSEZ_VIB_SEGMENT || e->type() == ElementType::PARTIAL_TIE_SEGMENT + || e->type() == ElementType::HAMMER_ON_PULL_OFF_SEGMENT); return (SlurTieSegment*)e; } @@ -634,7 +649,8 @@ 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::PARTIAL_TIE_SEGMENT); + || e->type() == ElementType::LAISSEZ_VIB_SEGMENT || e->type() == ElementType::PARTIAL_TIE_SEGMENT + || e->type() == ElementType::HAMMER_ON_PULL_OFF_SEGMENT); return (const SlurTieSegment*)e; } @@ -877,5 +893,8 @@ CONVERT(PartialLyricsLine) CONVERT(PartialLyricsLineSegment) CONVERT(Parenthesis) CONVERT(ShadowNote) +CONVERT(HammerOnPullOff) +CONVERT(HammerOnPullOffSegment) +CONVERT(HammerOnPullOffText) #undef CONVERT } diff --git a/src/engraving/dom/factory.cpp b/src/engraving/dom/factory.cpp index f77ba6c8fde33..a46bd1e16d537 100644 --- a/src/engraving/dom/factory.cpp +++ b/src/engraving/dom/factory.cpp @@ -51,6 +51,7 @@ #include "gradualtempochange.h" #include "guitarbend.h" #include "hairpin.h" +#include "hammeronpulloff.h" #include "harmonicmark.h" #include "harmony.h" #include "harppedaldiagram.h" @@ -220,6 +221,7 @@ EngravingItem* Factory::doCreateItem(ElementType type, EngravingItem* parent) case ElementType::FIGURED_BASS: return new FiguredBass(parent->isSegment() ? toSegment(parent) : dummy->segment()); case ElementType::STEM: return new Stem(parent->isChord() ? toChord(parent) : dummy->chord()); case ElementType::SLUR: return new Slur(parent); + case ElementType::HAMMER_ON_PULL_OFF: return new HammerOnPullOff(parent); case ElementType::TIE: return new Tie(parent); case ElementType::TUPLET: return new Tuplet(parent->isMeasure() ? toMeasure(parent) : dummy->measure()); case ElementType::FINGERING: return new Fingering(parent->isNote() ? toNote(parent) : dummy->note()); @@ -661,6 +663,9 @@ COPY_ITEM_IMPL(Tuplet) CREATE_ITEM_IMPL(Hairpin, ElementType::HAIRPIN, EngravingItem, isAccessibleEnabled) MAKE_ITEM_IMPL(Hairpin, EngravingItem) +CREATE_ITEM_IMPL(HammerOnPullOff, ElementType::HAMMER_ON_PULL_OFF, EngravingItem, isAccessibleEnabled) +MAKE_ITEM_IMPL(HammerOnPullOff, EngravingItem) + CREATE_ITEM_IMPL(Glissando, ElementType::GLISSANDO, EngravingItem, isAccessibleEnabled) MAKE_ITEM_IMPL(Glissando, EngravingItem) diff --git a/src/engraving/dom/factory.h b/src/engraving/dom/factory.h index 7ddacc8ec7d4a..07660f8037182 100644 --- a/src/engraving/dom/factory.h +++ b/src/engraving/dom/factory.h @@ -241,6 +241,9 @@ class Factory static Hairpin* createHairpin(EngravingItem* parent, bool isAccessibleEnabled = true); static std::shared_ptr makeHairpin(EngravingItem* parent); + static HammerOnPullOff* createHammerOnPullOff(EngravingItem* parent, bool isAccessibleEnabled = true); + static std::shared_ptr makeHammerOnPullOff(EngravingItem* parent); + static Glissando* createGlissando(EngravingItem* parent, bool isAccessibleEnabled = true); static std::shared_ptr makeGlissando(EngravingItem* parent); diff --git a/src/engraving/dom/hammeronpulloff.cpp b/src/engraving/dom/hammeronpulloff.cpp new file mode 100644 index 0000000000000..edb9591cafb98 --- /dev/null +++ b/src/engraving/dom/hammeronpulloff.cpp @@ -0,0 +1,296 @@ +/* + * 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 . + */ + +#include "hammeronpulloff.h" +#include "note.h" +#include "score.h" +#include "stafftype.h" +#include "style/textstyle.h" +#include "system.h" + +namespace mu::engraving { +HammerOnPullOff::HammerOnPullOff(EngravingItem* parent) + : Slur(parent, ElementType::HAMMER_ON_PULL_OFF) +{ +} + +HammerOnPullOff::HammerOnPullOff(const HammerOnPullOff& other) + : Slur(other) +{ +} + +HammerOnPullOffSegment::HammerOnPullOffSegment(System* parent) + : SlurSegment(parent, ElementType::HAMMER_ON_PULL_OFF_SEGMENT) +{ +} + +HammerOnPullOffSegment::HammerOnPullOffSegment(const HammerOnPullOffSegment& other) + : SlurSegment(other) +{ +} + +Color HammerOnPullOffSegment::curColor() const +{ + if (score()->printing() || !MScore::warnGuitarBends || isValid()) { + return SlurSegment::curColor(); + } + + auto engravingConf = configuration(); + return selected() ? engravingConf->criticalSelectedColor() : engravingConf->criticalColor(); +} + +void HammerOnPullOffSegment::scanElements(void* data, void (*func)(void*, EngravingItem*), bool all) +{ + for (EngravingObject* child : scanChildren()) { + child->scanElements(data, func, all); + } + + func(data, this); +} + +EngravingObjectList HammerOnPullOffSegment::scanChildren() const +{ + EngravingObjectList children; + for (HammerOnPullOffText* hopo : m_hopoText) { + children.push_back(hopo); + } + + return children; +} + +void HammerOnPullOffSegment::setTrack(track_idx_t idx) +{ + m_track = idx; + for (HammerOnPullOffText* hopo : m_hopoText) { + hopo->setTrack(idx); + } +} + +void HammerOnPullOffSegment::updateHopoText() +{ + Chord* startChord = nullptr; + if (isSingleBeginType()) { + EngravingItem* startEl = hammerOnPullOff()->startElement(); + startChord = startEl && startEl->isChord() ? toChord(startEl) : nullptr; + } else { + ChordRest* firstCR = system()->firstChordRest(track()); + startChord = firstCR && firstCR->isChord() ? toChord(firstCR) : nullptr; + } + + Chord* endChord = nullptr; + if (isSingleEndType()) { + EngravingItem* endEl = hammerOnPullOff()->endElement(); + endChord = endEl && endEl->isChord() ? toChord(endEl) : nullptr; + } else { + // If the segment doesn't end in this system, the endChord is the first chord of next system + ChordRest* lastCR = system()->lastChordRest(track()); + if (lastCR) { + lastCR = toChordRest(lastCR->segment()->next1WithElemsOnTrack(track())->element(track())); + } + endChord = lastCR && lastCR->isChord() ? toChord(lastCR) : nullptr; + } + + if (!startChord || !endChord) { + muse::DeleteAll(m_hopoText); + m_hopoText.clear(); + return; + } + + std::vector hopoTextRegions = computeHopoTextRegions(startChord, endChord); + size_t regionCount = hopoTextRegions.size(); + + size_t curRegionIdx = 0; + for (; curRegionIdx < regionCount; ++curRegionIdx) { + HopoTextRegion curRegion = hopoTextRegions[curRegionIdx]; + + HammerOnPullOffText* curHopoText; + if (curRegionIdx < m_hopoText.size()) { + // Reuse existing, if available + curHopoText = m_hopoText[curRegionIdx]; + } else { + // Create new + curHopoText = new HammerOnPullOffText(this); + m_hopoText.push_back(curHopoText); + } + + curHopoText->setParent(this); + curHopoText->setTrack(track()); + curHopoText->setIsValid(curRegion.isValid); + curHopoText->setIsHammerOn(curRegion.isHammerOn); + curHopoText->setXmlText(style().styleB(Sid::hopoUpperCase) ? (curRegion.isHammerOn ? "H" : "P") : (curRegion.isHammerOn ? "h" : "p")); + curHopoText->setStartChord(curRegion.startChord); + curHopoText->setEndChord(curRegion.endChord); + } + + // Delete unused + if (curRegionIdx < m_hopoText.size()) { + size_t unusedHopoCount = m_hopoText.size() - curRegionIdx; + for (size_t i = 0; i < unusedHopoCount; ++i) { + delete m_hopoText.back(); + m_hopoText.pop_back(); + } + } +} + +std::vector HammerOnPullOffSegment::computeHopoTextRegions(Chord* startChord, Chord* endChord) +{ + std::vector result; + bool isTabStaff = staffType()->isTabStaff(); + if ((isTabStaff && !style().styleB(Sid::hopoShowOnTabStaves)) || (!isTabStaff && !style().styleB(Sid::hopoShowOnStandardStaves))) { + return result; + } + + for (Chord* curChord = startChord; curChord != endChord;) { + IF_ASSERT_FAILED(curChord->tick() <= endChord->tick()) { + break; + } + + Chord* nextChord = curChord->next(); + if (!nextChord) { + break; + } + + Note* curNote = curChord->upNote(); + Note* nextNote = nextChord->upNote(); + bool isValid = isTabStaff ? nextNote->string() == curNote->string() : nextNote->pitch() != curNote->pitch(); + bool isHammerOn = isTabStaff ? nextNote->fret() > curNote->fret() : nextNote->pitch() > curNote->pitch(); + + bool startNewRegion = result.empty() || style().styleB(Sid::hopoShowAll) || result.back().isHammerOn != isHammerOn; + + if (startNewRegion) { + HopoTextRegion region; + region.startChord = curChord; + region.endChord = nextChord; + region.isValid = isValid; + region.isHammerOn = isHammerOn; + result.push_back(region); + } else { + HopoTextRegion& curRegion = result.back(); + curRegion.endChord = nextChord; + curRegion.isValid = curRegion.isValid && isValid; + } + + curChord = nextChord; + } + + return result; +} + +bool HammerOnPullOffSegment::isUserModified() const +{ + for (const HammerOnPullOffText* hopoText : m_hopoText) { + if (hopoText->isUserModified()) { + return true; + } + } + + return SlurSegment::isUserModified(); +} + +bool HammerOnPullOffSegment::isValid() const +{ + for (const HammerOnPullOffText* hopoText : m_hopoText) { + if (!hopoText->isValid()) { + return false; + } + } + + return true; +} + +void HammerOnPullOffSegment::reset() +{ + for (HammerOnPullOffText* hopoText : m_hopoText) { + hopoText->reset(); + } + + SlurTieSegment::reset(); +} + +static ElementStyle hopoStyle; + +HammerOnPullOffText::HammerOnPullOffText(HammerOnPullOffSegment* parent) + : TextBase(ElementType::HAMMER_ON_PULL_OFF_TEXT, parent, TextStyleType::HAMMER_ON_PULL_OFF, + ElementFlag::MOVABLE | ElementFlag::GENERATED) +{ + resetProperty(Pid::PLACEMENT); + initElementStyle(&hopoStyle); +} + +HammerOnPullOffText::HammerOnPullOffText(const HammerOnPullOffText& h) + : TextBase(h) +{ +} + +std::vector HammerOnPullOffText::dragAnchorLines() const +{ + std::vector result; + + PointF p1 = canvasPos(); + + HammerOnPullOffSegment* hopoSeg = toHammerOnPullOffSegment(parent()); + const Shape& hopoSegShape = hopoSeg->ldata()->shape(); + double x = ldata()->pos().x();// + hopoSegShape.bbox().x(); + double y = hopoSeg->hammerOnPullOff()->up() ? hopoSegShape.topAtX(x) : hopoSegShape.bottomAtX(x); + + PointF p2 = PointF(x, y) + hopoSeg->canvasPos(); + + result.push_back(LineF(p1, p2)); + + return result; +} + +bool HammerOnPullOffText::isUserModified() const +{ + for (const TextStyleProperty& p : *textStyle(textStyleType())) { + if (getProperty(p.pid) != propertyDefault(p.pid)) { + return true; + } + } + + return TextBase::isUserModified(); +} + +Color HammerOnPullOffText::curColor() const +{ + if (score()->printing()) { + return TextBase::curColor(); + } + + auto engravingConf = configuration(); + if (isValid() || !MScore::warnGuitarBends) { + return selected() || (parentItem() && parentItem()->selected()) ? engravingConf->selectionColor() : TextBase::curColor(); + } + + return selected() || parentItem()->selected() ? engravingConf->criticalSelectedColor() : engravingConf->criticalColor(); +} + +PropertyValue HammerOnPullOffText::propertyDefault(Pid id) const +{ + switch (id) { + case Pid::PLACEMENT: + return PlacementV::ABOVE; + default: + return TextBase::propertyDefault(id); + } +} +} // namespace mu::engraving diff --git a/src/engraving/dom/hammeronpulloff.h b/src/engraving/dom/hammeronpulloff.h new file mode 100644 index 0000000000000..651d561f3a783 --- /dev/null +++ b/src/engraving/dom/hammeronpulloff.h @@ -0,0 +1,120 @@ +/* + * 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 "slur.h" +#include "textbase.h" + +namespace mu::engraving { +class HammerOnPullOffText final : public TextBase +{ + OBJECT_ALLOCATOR(engraving, HammerOnPullOffText) + DECLARE_CLASSOF(ElementType::HAMMER_ON_PULL_OFF_TEXT) + +public: + HammerOnPullOffText(HammerOnPullOffSegment* parent = nullptr); + HammerOnPullOffText(const HammerOnPullOffText& h); + HammerOnPullOffText* clone() const override { return new HammerOnPullOffText(*this); } + + bool isEditAllowed(EditData&) const override { return false; } + + Chord* startChord() const { return m_startChord; } + Chord* endChord() const { return m_endChord; } + void setStartChord(Chord* c) { m_startChord = c; } + void setEndChord(Chord* c) { m_endChord = c; } + + std::vector dragAnchorLines() const override; + + bool isUserModified() const override; + bool isValid() const { return m_isValid; } + void setIsValid(bool v) { m_isValid = v; } + bool isHammerOn() const { return m_isHammerOn; } + void setIsHammerOn(bool v) { m_isHammerOn = v; } + + Color curColor() const override; + + PropertyValue propertyDefault(Pid id) const override; + +private: + Chord* m_startChord = nullptr; + Chord* m_endChord = nullptr; + bool m_isValid = true; + bool m_isHammerOn = true; +}; + +class HammerOnPullOffSegment final : public SlurSegment +{ + OBJECT_ALLOCATOR(engraving, HammerOnPullOffSegment) + DECLARE_CLASSOF(ElementType::HAMMER_ON_PULL_OFF_SEGMENT) + +public: + HammerOnPullOffSegment(System* parent); + HammerOnPullOffSegment(const HammerOnPullOffSegment& other); + + HammerOnPullOffSegment* clone() const override { return new HammerOnPullOffSegment(*this); } + + HammerOnPullOff* hammerOnPullOff() const { return toHammerOnPullOff(spanner()); } + + Color curColor() const override; + + void scanElements(void* data, void (* func)(void*, EngravingItem*), bool all=true) override; + EngravingObjectList scanChildren() const override; + + void setTrack(track_idx_t idx) override; + + void updateHopoText(); + void addHopoText(HammerOnPullOffText* t) { m_hopoText.push_back(t); } + const std::vector& hopoText() const { return m_hopoText; } + + bool isUserModified() const override; + bool isValid() const; + + void reset() override; + +private: + struct HopoTextRegion { + Chord* startChord = nullptr; + Chord* endChord = nullptr; + bool isHammerOn = true; + bool isValid = true; + }; + + std::vector computeHopoTextRegions(Chord* startChord, Chord* endChord); + + std::vector m_hopoText; +}; + +class HammerOnPullOff final : public Slur +{ + OBJECT_ALLOCATOR(engraving, HammerOnPullOff) + DECLARE_CLASSOF(ElementType::HAMMER_ON_PULL_OFF) + +public: + HammerOnPullOff* clone() const override { return new HammerOnPullOff(*this); } + SlurTieSegment* newSlurTieSegment(System* parent) override { return new HammerOnPullOffSegment(parent); } + + friend class Factory; + + HammerOnPullOff(EngravingItem* parent); + HammerOnPullOff(const HammerOnPullOff&); +}; +} // namespace mu::engraving diff --git a/src/engraving/dom/navigate.cpp b/src/engraving/dom/navigate.cpp index b09b67758642b..233b4a96ae020 100644 --- a/src/engraving/dom/navigate.cpp +++ b/src/engraving/dom/navigate.cpp @@ -25,6 +25,7 @@ #include "chord.h" #include "engravingitem.h" #include "lyrics.h" +#include "hammeronpulloff.h" #include "measure.h" #include "measurerepeat.h" #include "marker.h" @@ -750,6 +751,15 @@ EngravingItem* Score::nextElement() return score()->firstElement(); } } + case ElementType::HAMMER_ON_PULL_OFF_TEXT: + return toHammerOnPullOffText(e)->endChord()->upNote(); + case ElementType::HAMMER_ON_PULL_OFF_SEGMENT: + { + HammerOnPullOffSegment* hopoSeg = toHammerOnPullOffSegment(e); + if (!hopoSeg->hopoText().empty()) { + return hopoSeg->hopoText().front(); + } // else fallthrough: + } case ElementType::VOLTA_SEGMENT: case ElementType::SLUR_SEGMENT: case ElementType::TEXTLINE_SEGMENT: @@ -941,8 +951,20 @@ EngravingItem* Score::prevElement() return previousElement; } + case ElementType::HAMMER_ON_PULL_OFF_TEXT: + { + HammerOnPullOffText* hopoText = toHammerOnPullOffText(e); + HammerOnPullOffSegment* hopoSegment = toHammerOnPullOffSegment(hopoText->parent()); + DO_ASSERT(hopoSegment); + if (hopoSegment->hopoText().size() > 0 && hopoSegment->hopoText().front() == hopoText) { + return hopoSegment; + } else { + return hopoText->startChord()->downNote(); + } + } case ElementType::VOLTA_SEGMENT: case ElementType::SLUR_SEGMENT: + case ElementType::HAMMER_ON_PULL_OFF_SEGMENT: case ElementType::TEXTLINE_SEGMENT: case ElementType::HAIRPIN_SEGMENT: case ElementType::OTTAVA_SEGMENT: diff --git a/src/engraving/dom/rest.cpp b/src/engraving/dom/rest.cpp index 70035e061fd3e..0a456c15b272e 100644 --- a/src/engraving/dom/rest.cpp +++ b/src/engraving/dom/rest.cpp @@ -219,6 +219,7 @@ bool Rest::acceptDrop(EditData& data) const // prevent 'hanging' slurs, avoid crash on tie static const std::set ignoredTypes { ElementType::SLUR, + ElementType::HAMMER_ON_PULL_OFF, ElementType::TIE, ElementType::GLISSANDO }; diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index ff2fd62c6d64b..b1c815c6ef0e0 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -1528,6 +1528,7 @@ void Score::addElement(EngravingItem* element) break; case ElementType::SLUR: + case ElementType::HAMMER_ON_PULL_OFF: addLayoutFlags(LayoutFlag::PLAY_EVENTS); // fall through @@ -1722,6 +1723,7 @@ void Score::removeElement(EngravingItem* element) break; case ElementType::SLUR: + case ElementType::HAMMER_ON_PULL_OFF: addLayoutFlags(LayoutFlag::PLAY_EVENTS); // fall through diff --git a/src/engraving/dom/score.h b/src/engraving/dom/score.h index cdc3c765ea35b..5e129912047a6 100644 --- a/src/engraving/dom/score.h +++ b/src/engraving/dom/score.h @@ -457,6 +457,7 @@ class Score : public EngravingObject, public muse::Injectable EngravingItem* elementToRelink = nullptr); void undoAddCR(ChordRest* element, Measure*, const Fraction& tick); void undoRemoveElement(EngravingItem* element, bool removeLinked = true); + void undoRemoveHopoText(HammerOnPullOffText* hopoText); void undoChangeSpannerElements(Spanner* spanner, EngravingItem* startElement, EngravingItem* endElement); void undoChangeElement(EngravingItem* oldElement, EngravingItem* newElement); void undoChangePitch(Note* note, int pitch, int tpc1, int tpc2); diff --git a/src/engraving/dom/segment.cpp b/src/engraving/dom/segment.cpp index 6aebca8352f7b..5bd9d5cfe402c 100644 --- a/src/engraving/dom/segment.cpp +++ b/src/engraving/dom/segment.cpp @@ -37,6 +37,7 @@ #include "chordrest.h" #include "clef.h" #include "engravingitem.h" +#include "hammeronpulloff.h" #include "harppedaldiagram.h" #include "hook.h" #include "instrchange.h" @@ -358,6 +359,17 @@ Segment* Segment::next1WithElemsOnStaff(staff_idx_t staffIdx, SegmentType segTyp return next; } +Segment* Segment::next1WithElemsOnTrack(track_idx_t trackIdx, SegmentType segType) const +{ + Segment* next = next1(segType); + + while (next && !next->hasElements(trackIdx, trackIdx)) { + next = next->next1(segType); + } + + return next; +} + Segment* Segment::next1MM(SegmentType types) const { for (Segment* s = next1MM(); s; s = s->next1MM()) { @@ -2102,6 +2114,17 @@ EngravingItem* Segment::nextElement(staff_idx_t activeStaff) if (s) { return s->spannerSegments().front(); } + + for (SpannerSegment* spannerSeg : system()->spannerSegments()) { + if (spannerSeg->staffIdx() == activeStaff && spannerSeg->isHammerOnPullOffSegment()) { + for (HammerOnPullOffText* hopoText : toHammerOnPullOffSegment(spannerSeg)->hopoText()) { + if (hopoText->startChord() && hopoText->startChord()->segment() == this) { + return hopoText; + } + } + } + } + Segment* nextSegment = seg->next1MMenabled(); for (; nextSegment && nextSegment->isTimeTickType(); nextSegment = nextSegment->next1MMenabled()) { if (EngravingItem* annotation = nextSegment->firstAnnotation(activeStaff)) { @@ -2288,7 +2311,19 @@ EngravingItem* Segment::prevElement(staff_idx_t activeStaff) } else { return prev; } + } else { + System* system = seg->system(); + for (SpannerSegment* spannerSeg : system->spannerSegments()) { + if (spannerSeg->staffIdx() == activeStaff && spannerSeg->isHammerOnPullOffSegment()) { + for (HammerOnPullOffText* hopoText : toHammerOnPullOffSegment(spannerSeg)->hopoText()) { + if (hopoText->endChord() && hopoText->endChord()->segment() == seg) { + return hopoText; + } + } + } + } } + Segment* prevSeg = seg->prev1MMenabled(); for (; prevSeg && prevSeg->isTimeTickType(); prevSeg = prevSeg->prev1MMenabled()) { if (Spanner* spanner = prevSeg->lastSpanner(activeStaff)) { diff --git a/src/engraving/dom/segment.h b/src/engraving/dom/segment.h index 3a316c1cf3672..0b0f818845dbe 100644 --- a/src/engraving/dom/segment.h +++ b/src/engraving/dom/segment.h @@ -101,6 +101,7 @@ class Segment final : public EngravingItem Segment* next1(SegmentType) const; Segment* next1ChordRestOrTimeTick() const; Segment* next1WithElemsOnStaff(staff_idx_t staffIdx, SegmentType segType = SegmentType::ChordRest) const; + Segment* next1WithElemsOnTrack(track_idx_t trackIdx, SegmentType segType = SegmentType::ChordRest) const; Segment* next1MM(SegmentType) const; Segment* prev1() const; diff --git a/src/engraving/dom/select.cpp b/src/engraving/dom/select.cpp index 97b2879b48b57..34bf704486f44 100644 --- a/src/engraving/dom/select.cpp +++ b/src/engraving/dom/select.cpp @@ -1075,6 +1075,7 @@ muse::ByteArray Selection::symbolListMimeData() const e = toSpannerSegment(e)->spanner(); [[fallthrough]]; case ElementType::SLUR: + case ElementType::HAMMER_ON_PULL_OFF: case ElementType::HAIRPIN: case ElementType::OTTAVA: case ElementType::TRILL: diff --git a/src/engraving/dom/selectionfilter.cpp b/src/engraving/dom/selectionfilter.cpp index b9ba6ab335d95..ff26d41556928 100644 --- a/src/engraving/dom/selectionfilter.cpp +++ b/src/engraving/dom/selectionfilter.cpp @@ -134,6 +134,8 @@ bool SelectionFilter::canSelect(const EngravingItem* e) const return isFiltered(ElementsSelectionFilterTypes::CHORD_SYMBOL); case ElementType::SLUR: case ElementType::SLUR_SEGMENT: + case ElementType::HAMMER_ON_PULL_OFF: + case ElementType::HAMMER_ON_PULL_OFF_SEGMENT: return isFiltered(ElementsSelectionFilterTypes::SLUR); case ElementType::FIGURED_BASS: return isFiltered(ElementsSelectionFilterTypes::FIGURED_BASS); diff --git a/src/engraving/dom/slur.cpp b/src/engraving/dom/slur.cpp index cb67cd407ef75..42215bb9fad02 100644 --- a/src/engraving/dom/slur.cpp +++ b/src/engraving/dom/slur.cpp @@ -46,8 +46,8 @@ using namespace mu::engraving; using namespace muse::draw; namespace mu::engraving { -SlurSegment::SlurSegment(System* parent) - : SlurTieSegment(ElementType::SLUR_SEGMENT, parent) +SlurSegment::SlurSegment(System* parent, ElementType type) + : SlurTieSegment(type, parent) { } @@ -251,39 +251,7 @@ void SlurSegment::changeAnchor(EditData& ed, EngravingItem* element) } // update start/end elements (which could be grace notes) - for (EngravingObject* lsp : spanner()->linkList()) { - Spanner* sp = static_cast(lsp); - if (sp == spanner()) { - score()->undo(new ChangeSpannerElements(sp, scr, ecr)); - } else { - EngravingItem* se = 0; - EngravingItem* ee = 0; - if (scr) { - std::list sel = scr->linkList(); - for (EngravingObject* lcr : sel) { - EngravingItem* le = toEngravingItem(lcr); - if (le->score() == sp->score() && le->track() == sp->track()) { - se = le; - break; - } - } - } - if (ecr) { - std::list sel = ecr->linkList(); - for (EngravingObject* lcr : sel) { - EngravingItem* le = toEngravingItem(lcr); - if (le->score() == sp->score() && le->track() == sp->track2()) { - ee = le; - break; - } - } - } - if (se && ee) { - score()->undo(new ChangeStartEndSpanner(sp, se, ee)); - renderer()->layoutItem(sp); - } - } - } + slur()->undoChangeStartEndElements(scr, ecr); const size_t segments = spanner()->spannerSegments().size(); ups(ed.curGrip).off = PointF(); @@ -390,6 +358,11 @@ double SlurSegment::dottedWidth() const return style().styleMM(Sid::slurDottedWidth); } +Color SlurSegment::curColor() const +{ + return EngravingItem::curColor(getProperty(Pid::VISIBLE).toBool(), getProperty(Pid::COLOR).value()); +} + Slur::Slur(const Slur& s) : SlurTie(s) { @@ -401,8 +374,8 @@ Slur::Slur(const Slur& s) // Slur //--------------------------------------------------------- -Slur::Slur(EngravingItem* parent) - : SlurTie(ElementType::SLUR, parent) +Slur::Slur(EngravingItem* parent, ElementType type) + : SlurTie(type, parent) { setAnchor(Anchor::CHORD); } @@ -509,6 +482,43 @@ bool Slur::isOutgoing() const return _partialSpannerDirection == PartialSpannerDirection::BOTH || _partialSpannerDirection == PartialSpannerDirection::OUTGOING; } +void Slur::undoChangeStartEndElements(ChordRest* scr, ChordRest* ecr) +{ + for (EngravingObject* lsp : linkList()) { + Spanner* sp = static_cast(lsp); + if (sp == this) { + score()->undo(new ChangeSpannerElements(this, scr, ecr)); + } else { + EngravingItem* se = 0; + EngravingItem* ee = 0; + if (scr) { + std::list sel = scr->linkList(); + for (EngravingObject* lcr : sel) { + EngravingItem* le = toEngravingItem(lcr); + if (le->score() == sp->score() && le->track() == sp->track()) { + se = le; + break; + } + } + } + if (ecr) { + std::list sel = ecr->linkList(); + for (EngravingObject* lcr : sel) { + EngravingItem* le = toEngravingItem(lcr); + if (le->score() == sp->score() && le->track() == sp->track2()) { + ee = le; + break; + } + } + } + if (se && ee) { + score()->undo(new ChangeStartEndSpanner(sp, se, ee)); + renderer()->layoutItem(sp); + } + } + } +} + //--------------------------------------------------------- // setTrack //--------------------------------------------------------- diff --git a/src/engraving/dom/slur.h b/src/engraving/dom/slur.h index f903fe7824d4b..dd527557319ad 100644 --- a/src/engraving/dom/slur.h +++ b/src/engraving/dom/slur.h @@ -32,7 +32,7 @@ namespace mu::engraving { /// a single segment of slur; also used for Tie //--------------------------------------------------------- -class SlurSegment final : public SlurTieSegment +class SlurSegment : public SlurTieSegment { OBJECT_ALLOCATOR(engraving, SlurSegment) DECLARE_CLASSOF(ElementType::SLUR_SEGMENT) @@ -42,7 +42,7 @@ class SlurSegment final : public SlurTieSegment M_PROPERTY2(PointF, endPointOff2, setEndPointOff2, PointF(0.0, 0.0)) public: - SlurSegment(System* parent); + SlurSegment(System* parent, ElementType type = ElementType::SLUR_SEGMENT); SlurSegment(const SlurSegment& ss); SlurSegment* clone() const override { return new SlurSegment(*this); } @@ -60,6 +60,8 @@ class SlurSegment final : public SlurTieSegment double midWidth() const override; double dottedWidth() const override; + Color curColor() const override; + protected: void changeAnchor(EditData&, EngravingItem*) override; }; @@ -68,12 +70,14 @@ class SlurSegment final : public SlurTieSegment // @@ Slur //--------------------------------------------------------- -class Slur final : public SlurTie +class Slur : public SlurTie { OBJECT_ALLOCATOR(engraving, Slur) DECLARE_CLASSOF(ElementType::SLUR) public: + Slur(EngravingItem* parent, ElementType type = ElementType::SLUR); + Slur(const Slur&); struct StemFloated { @@ -124,6 +128,9 @@ class Slur final : public SlurTie void setOutgoing(bool outgoing); bool isIncoming() const; bool isOutgoing() const; + + void undoChangeStartEndElements(ChordRest* scr, ChordRest* ecr); + private: M_PROPERTY2(ConnectedElement, connectedElement, setConnectedElement, ConnectedElement::NONE) M_PROPERTY2(PartialSpannerDirection, partialSpannerDirection, setPartialSpannerDirection, PartialSpannerDirection::NONE) @@ -132,8 +139,6 @@ class Slur final : public SlurTie PartialSpannerDirection calcOutgoingDirection(bool outgoing); friend class Factory; - Slur(EngravingItem* parent); - Slur(const Slur&); StemFloated m_stemFloated; // end point position is attached to stem but floated towards the note }; diff --git a/src/engraving/dom/slurtie.cpp b/src/engraving/dom/slurtie.cpp index 088bdd31423fa..c13bb2600c770 100644 --- a/src/engraving/dom/slurtie.cpp +++ b/src/engraving/dom/slurtie.cpp @@ -180,6 +180,17 @@ std::vector SlurTieSegment::gripsPositions(const EditData&) const return grips; } +bool SlurTieSegment::isUserModified() const +{ + return SpannerSegment::isUserModified() || !(visible() && autoplace() + && color() == configuration()->defaultColor() + && offset().isNull() + && ups(Grip::START).off.isNull() + && ups(Grip::BEZIER1).off.isNull() + && ups(Grip::BEZIER2).off.isNull() + && ups(Grip::END).off.isNull()); +} + //--------------------------------------------------------- // startEditDrag //--------------------------------------------------------- diff --git a/src/engraving/dom/slurtie.h b/src/engraving/dom/slurtie.h index 12935f0e0bd0c..af1d987eac092 100644 --- a/src/engraving/dom/slurtie.h +++ b/src/engraving/dom/slurtie.h @@ -105,7 +105,7 @@ class SlurTieSegment : public SpannerSegment PropertyValue getProperty(Pid propertyId) const override; bool setProperty(Pid propertyId, const PropertyValue&) override; PropertyValue propertyDefault(Pid id) const override; - void reset() override; + virtual void reset() override; void undoChangeProperty(Pid id, const PropertyValue&, PropertyFlags ps) override; void move(const PointF& s) override; bool isEditable() const override { return true; } @@ -123,6 +123,8 @@ class SlurTieSegment : public SpannerSegment Grip defaultGrip() const override { return Grip::DRAG; } std::vector gripsPositions(const EditData& = EditData()) const override; + virtual bool isUserModified() const override; + virtual double endWidth() const = 0; virtual double midWidth() const = 0; virtual double dottedWidth() const = 0; diff --git a/src/engraving/dom/spanner.h b/src/engraving/dom/spanner.h index aab654601696e..10ffe10193dba 100644 --- a/src/engraving/dom/spanner.h +++ b/src/engraving/dom/spanner.h @@ -118,7 +118,7 @@ class SpannerSegment : public EngravingItem std::list linkListForPropertyPropagation() const override; bool isPropertyLinkedToMaster(Pid id) const override; - bool isUserModified() const override; + virtual bool isUserModified() const override; bool allowTimeAnchor() const override; diff --git a/src/engraving/dom/system.cpp b/src/engraving/dom/system.cpp index db3b7df5173d5..a5b176ce59b75 100644 --- a/src/engraving/dom/system.cpp +++ b/src/engraving/dom/system.cpp @@ -489,6 +489,7 @@ void System::add(EngravingItem* el) case ElementType::PICK_SCRAPE_SEGMENT: case ElementType::GUITAR_BEND_SEGMENT: case ElementType::GUITAR_BEND_HOLD_SEGMENT: + case ElementType::HAMMER_ON_PULL_OFF_SEGMENT: { SpannerSegment* ss = toSpannerSegment(el); #ifndef NDEBUG diff --git a/src/engraving/dom/undo.cpp b/src/engraving/dom/undo.cpp index 62f4bb3fd9c3c..c9c90eedea95d 100644 --- a/src/engraving/dom/undo.cpp +++ b/src/engraving/dom/undo.cpp @@ -557,8 +557,9 @@ void UndoStack::redo(EditData* ed) bool UndoMacro::canRecordSelectedElement(const EngravingItem* e) { - return e->isNote() || (e->isChordRest() && !e->isChord()) || (e->isTextBase() && !e->isInstrumentName()) || e->isFretDiagram() - || e->isSoundFlag(); + return e->isNote() || (e->isChordRest() && !e->isChord()) + || (e->isTextBase() && !e->isInstrumentName() && !e->isHammerOnPullOffText()) + || e->isFretDiagram() || e->isSoundFlag(); } void UndoMacro::fillSelectionInfo(SelectionInfo& info, const Selection& sel) diff --git a/src/engraving/infrastructure/shape.cpp b/src/engraving/infrastructure/shape.cpp index 86a675452befe..856bceeadb6b0 100644 --- a/src/engraving/infrastructure/shape.cpp +++ b/src/engraving/infrastructure/shape.cpp @@ -368,6 +368,30 @@ double Shape::bottom() const return dist; } +double Shape::topAtX(double x) const +{ + double localTop = DBL_MAX; + for (const ShapeElement& el : m_elements) { + if (el.left() < x && el.right() > x) { + localTop = std::min(localTop, el.top()); + } + } + + return localTop != DBL_MAX ? localTop : top(); +} + +double Shape::bottomAtX(double x) const +{ + double localBottom = -DBL_MAX; + for (const ShapeElement& el : m_elements) { + if (el.left() < x && el.right() > x) { + localBottom = std::max(localBottom, el.bottom()); + } + } + + return localBottom != DBL_MAX ? localBottom : bottom(); +} + double Shape::rightMostEdgeAtHeight(double yAbove, double yBelow) const { double edge = -DBL_MAX; diff --git a/src/engraving/infrastructure/shape.h b/src/engraving/infrastructure/shape.h index 07f22780c2e7a..1177f836fc91f 100644 --- a/src/engraving/infrastructure/shape.h +++ b/src/engraving/infrastructure/shape.h @@ -159,6 +159,8 @@ class Shape double right() const; double top() const; double bottom() const; + double topAtX(double x) const; + double bottomAtX(double x) const; double rightMostEdgeAtHeight(double yAbove, double yBelow) const; double leftMostEdgeAtHeight(double yAbove, double yBelow) const; double leftMostEdgeAtTop() const; diff --git a/src/engraving/rendering/editmode/editmoderenderer.cpp b/src/engraving/rendering/editmode/editmoderenderer.cpp index 2b2d1234b4d4c..5845072248113 100644 --- a/src/engraving/rendering/editmode/editmoderenderer.cpp +++ b/src/engraving/rendering/editmode/editmoderenderer.cpp @@ -99,6 +99,8 @@ void EditModeRenderer::drawItem(EngravingItem* item, muse::draw::Painter* painte drawTextBase(item_cast(item), painter, ed, currentViewScaling); break; case ElementType::SLUR_SEGMENT: + case ElementType::TIE_SEGMENT: + case ElementType::HAMMER_ON_PULL_OFF_SEGMENT: drawSlurTieSegment(item_cast(item), painter, ed, currentViewScaling); break; case ElementType::STAFF_TEXT: @@ -119,9 +121,6 @@ void EditModeRenderer::drawItem(EngravingItem* item, muse::draw::Painter* painte case ElementType::TEXT: drawTextBase(item_cast(item), painter, ed, currentViewScaling); break; - case ElementType::TIE_SEGMENT: - drawSlurTieSegment(item_cast(item), painter, ed, currentViewScaling); - break; case ElementType::TRIPLET_FEEL: drawTextBase(item_cast(item), painter, ed, currentViewScaling); break; diff --git a/src/engraving/rendering/score/slurtielayout.cpp b/src/engraving/rendering/score/slurtielayout.cpp index 172fd2d3a8ca2..c53c5a2a46869 100644 --- a/src/engraving/rendering/score/slurtielayout.cpp +++ b/src/engraving/rendering/score/slurtielayout.cpp @@ -44,6 +44,7 @@ #include "dom/laissezvib.h" #include "dom/parenthesis.h" #include "dom/partialtie.h" +#include "dom/hammeronpulloff.h" #include "tlayout.h" #include "chordlayout.h" @@ -65,8 +66,8 @@ SpannerSegment* SlurTieLayout::layoutSystem(Slur* item, System* system, LayoutCo Fraction stick = system->firstMeasure()->tick(); Fraction etick = system->lastMeasure()->endTick(); - SlurSegment* slurSegment = toSlurSegment(TLayout::getNextLayoutSystemSegment(item, system, [](System* parent) { - return new SlurSegment(parent); + SlurSegment* slurSegment = toSlurSegment(TLayout::getNextLayoutSystemSegment(item, system, [item](System* parent) { + return item->newSlurTieSegment(parent); })); SpannerSegmentType sst; diff --git a/src/engraving/rendering/score/systemlayout.cpp b/src/engraving/rendering/score/systemlayout.cpp index 8baa177a60ff7..961322322be26 100644 --- a/src/engraving/rendering/score/systemlayout.cpp +++ b/src/engraving/rendering/score/systemlayout.cpp @@ -1238,6 +1238,7 @@ void SystemLayout::collectSpannersToLayout(ElementsToLayout& elements, const Lay } else { switch (spanner->type()) { case ElementType::SLUR: + case ElementType::HAMMER_ON_PULL_OFF: if (!toSlur(spanner)->isCrossStaff()) { elements.slurs.push_back(spanner); } @@ -1629,6 +1630,9 @@ void SystemLayout::processLines(System* system, LayoutContext& ctx, const std::v continue; } system->staff(stfIdx)->skyline().add(ss->shape().translate(ss->pos())); + if (ss->isHammerOnPullOffSegment()) { + TLayout::layoutHammerOnPullOffSegment(toHammerOnPullOffSegment(ss), ctx); + } } } } diff --git a/src/engraving/rendering/score/tdraw.cpp b/src/engraving/rendering/score/tdraw.cpp index a40a6abbeb8a3..3356cc0c119c5 100644 --- a/src/engraving/rendering/score/tdraw.cpp +++ b/src/engraving/rendering/score/tdraw.cpp @@ -63,6 +63,7 @@ #include "dom/guitarbend.h" #include "dom/hairpin.h" +#include "dom/hammeronpulloff.h" #include "dom/harppedaldiagram.h" #include "dom/harmonicmark.h" #include "dom/harmony.h" @@ -243,6 +244,10 @@ void TDraw::drawItem(const EngravingItem* item, Painter* painter) case ElementType::HAIRPIN_SEGMENT: draw(item_cast(item), painter); break; + case ElementType::HAMMER_ON_PULL_OFF_SEGMENT: draw(item_cast(item), painter); + break; + case ElementType::HAMMER_ON_PULL_OFF_TEXT: draw(item_cast(item), painter); + break; case ElementType::HARP_DIAGRAM: draw(item_cast(item), painter); break; case ElementType::HARMONIC_MARK_SEGMENT: draw(item_cast(item), painter); @@ -1873,6 +1878,17 @@ void TDraw::draw(const HairpinSegment* item, Painter* painter) } } +void TDraw::draw(const HammerOnPullOffSegment* item, muse::draw::Painter* painter) +{ + draw(toSlurSegment(item), painter); +} + +void TDraw::draw(const HammerOnPullOffText* item, muse::draw::Painter* painter) +{ + TRACE_DRAW_ITEM; + drawTextBase(item, painter); +} + void TDraw::draw(const HarpPedalDiagram* item, Painter* painter) { TRACE_DRAW_ITEM; @@ -2609,7 +2625,7 @@ void TDraw::draw(const SlurSegment* item, Painter* painter) { TRACE_DRAW_ITEM; - Pen pen(item->curColor(item->getProperty(Pid::VISIBLE).toBool(), item->getProperty(Pid::COLOR).value())); + Pen pen(item->curColor()); double mag = item->staff() ? item->staff()->staffMag(item->slur()->tick()) : 1.0; //Replace generic Qt dash patterns with improved equivalents to show true dots (keep in sync with tie.cpp) diff --git a/src/engraving/rendering/score/tdraw.h b/src/engraving/rendering/score/tdraw.h index 00f3a8a3dbf08..c287d8af3034b 100644 --- a/src/engraving/rendering/score/tdraw.h +++ b/src/engraving/rendering/score/tdraw.h @@ -69,6 +69,8 @@ class GradualTempoChange; class HairpinSegment; class Hairpin; +class HammerOnPullOffSegment; +class HammerOnPullOffText; class HarpPedalDiagram; class HarmonicMarkSegment; class Harmony; @@ -225,6 +227,8 @@ class TDraw static void draw(const GuitarBendHoldSegment* item, muse::draw::Painter* painter); static void draw(const HairpinSegment* item, muse::draw::Painter* painter); + static void draw(const HammerOnPullOffSegment* item, muse::draw::Painter* painter); + static void draw(const HammerOnPullOffText* item, muse::draw::Painter* painter); static void draw(const HarpPedalDiagram* item, muse::draw::Painter* painter); static void draw(const HarmonicMarkSegment* item, muse::draw::Painter* painter); static void draw(const Harmony* item, muse::draw::Painter* painter); diff --git a/src/engraving/rendering/score/tlayout.cpp b/src/engraving/rendering/score/tlayout.cpp index 5a09165add430..2d6bc118c8418 100644 --- a/src/engraving/rendering/score/tlayout.cpp +++ b/src/engraving/rendering/score/tlayout.cpp @@ -69,6 +69,7 @@ #include "dom/guitarbend.h" #include "dom/hairpin.h" +#include "dom/hammeronpulloff.h" #include "dom/harppedaldiagram.h" #include "dom/harmonicmark.h" #include "dom/harmony.h" @@ -276,6 +277,12 @@ void TLayout::layoutItem(EngravingItem* item, LayoutContext& ctx) break; case ElementType::HAIRPIN_SEGMENT: layoutHairpinSegment(item_cast(item), ctx); break; + case ElementType::HAMMER_ON_PULL_OFF: layoutHammerOnPullOff(item_cast(item), ctx); + break; + case ElementType::HAMMER_ON_PULL_OFF_SEGMENT: layoutHammerOnPullOffSegment(item_cast(item), ctx); + break; + case ElementType::HAMMER_ON_PULL_OFF_TEXT: layoutHammerOnPullOffText(item_cast(item), ctx); + break; case ElementType::HARP_DIAGRAM: layoutHarpPedalDiagram(item_cast(item), static_cast(ldata)); break; @@ -3242,6 +3249,78 @@ void TLayout::layoutHairpin(Hairpin* item, LayoutContext& ctx) layoutTextLineBase(item, ctx); } +void TLayout::layoutHammerOnPullOff(HammerOnPullOff* item, LayoutContext& ctx) +{ + layoutSlur(static_cast(item), ctx); +} + +void TLayout::layoutHammerOnPullOffSegment(HammerOnPullOffSegment* item, LayoutContext& ctx) +{ + // The layout of the slur has already been done. Here we layout the H/P letters. + item->updateHopoText(); + + System* system = item->system(); + Fraction systemEndTick = system->endTick(); + Skyline& sk = system->staff(item->staffIdx())->skyline(); + + for (HammerOnPullOffText* hopoText : item->hopoText()) { + bool above = hopoText->placeAbove(); + + Align align; + align.vertical = above ? AlignV::BASELINE : AlignV::TOP; + align.horizontal = AlignH::HCENTER; + hopoText->setAlign(align); + layoutItem(hopoText, ctx); + + const Chord* startChord = hopoText->startChord(); + const Chord* endChord = hopoText->endChord(); + double startX = startChord->systemPos().x() + startChord->upNote()->headWidth(); + double endX = startX; + if (endChord->tick() < systemEndTick) { + endX = endChord->systemPos().x(); + } else { + // The last endChord of this segment is in next system. Use end barline instead. + Segment* endSeg = system->lastMeasure()->last(SegmentType::BarLineType); + endX = endSeg ? endSeg->systemPos().x() : endX; + } + if (startChord->stem() && endChord->stem() && startChord->up() == above && endChord->up() == above) { + // Mid-way between centered on the notes and centered on the stems + endX += (above ? 0.5 : -0.5) * endChord->upNote()->headWidth(); + } + double centerX = 0.5 * (startX + endX); + + double vertPadding = 0.5 * item->spatium(); + Shape hopoTextShape = hopoText->ldata()->shape().translated(PointF(centerX, 0.0)); + SkylineLine& skyline = above ? sk.north() : sk.south(); + double y = above ? -skyline.minDistanceToShapeAbove(hopoTextShape) : skyline.minDistanceToShapeBelow(hopoTextShape); + y += above ? -vertPadding : vertPadding; + y = above ? std::min(y, -vertPadding) : std::max(y, item->staff()->staffHeight(item->tick()) + vertPadding); + + Note* startNote = above ? startChord->upNote() : startChord->downNote(); + Note* endNote = above ? endChord->upNote() : endChord->downNote(); + double yNoteLimit = above ? std::min(startNote->y(), endNote->y()) - 2 * vertPadding + : std::max(startNote->y(), endNote->y()) + 2 * vertPadding; + y = above ? std::min(y, yNoteLimit) : std::max(y, yNoteLimit); + + hopoText->mutldata()->setPos(centerX, y); + + hopoTextShape.translateY(y); + skyline.add(hopoTextShape); + } + + Shape hopoSegmentShape = item->mutldata()->shape(); + for (HammerOnPullOffText* hopoText : item->hopoText()) { + hopoSegmentShape.add(hopoText->ldata()->shape().translated(hopoText->pos())); + } + + item->mutldata()->setShape(hopoSegmentShape); +} + +void TLayout::layoutHammerOnPullOffText(HammerOnPullOffText* item, LayoutContext& ctx) +{ + layoutBaseTextBase(item, ctx); +} + void TLayout::fillHairpinSegmentShape(const HairpinSegment* item, HairpinSegment::LayoutData* ldata) { LAYOUT_CALL_ITEM(item); @@ -7025,7 +7104,7 @@ void TLayout::layoutWhammyBarSegment(WhammyBarSegment* item, LayoutContext& ctx) Autoplace::autoplaceSpannerSegment(item, ldata, ctx.conf().spatium()); } -using LayoutSystemTypes = rtti::TypeList; +using LayoutSystemTypes = rtti::TypeList; class LayoutSystemVisitor : public rtti::Visitor { diff --git a/src/engraving/rendering/score/tlayout.h b/src/engraving/rendering/score/tlayout.h index b9284c2ff752b..43dcced97cd96 100644 --- a/src/engraving/rendering/score/tlayout.h +++ b/src/engraving/rendering/score/tlayout.h @@ -122,6 +122,9 @@ class GradualTempoChange; class HairpinSegment; class Hairpin; +class HammerOnPullOff; +class HammerOnPullOffSegment; +class HammerOnPullOffText; class HarmonicMarkSegment; class LedgerLine; @@ -247,6 +250,9 @@ class TLayout static void layoutHairpinSegment(HairpinSegment* item, LayoutContext& ctx); static void layoutHairpin(Hairpin* item, LayoutContext& ctx); + static void layoutHammerOnPullOff(HammerOnPullOff* item, LayoutContext& ctx); + static void layoutHammerOnPullOffSegment(HammerOnPullOffSegment* item, LayoutContext& ctx); + static void layoutHammerOnPullOffText(HammerOnPullOffText* item, LayoutContext& ctx); static void fillHairpinSegmentShape(const HairpinSegment* item, HairpinSegment::LayoutData* ldata); static void layoutHarpPedalDiagram(const HarpPedalDiagram* item, HarpPedalDiagram::LayoutData* ldata); static void layoutHarmonicMarkSegment(HarmonicMarkSegment* item, LayoutContext& ctx); diff --git a/src/engraving/rendering/single/singledraw.cpp b/src/engraving/rendering/single/singledraw.cpp index fc80384cd9dab..4fa9858695beb 100644 --- a/src/engraving/rendering/single/singledraw.cpp +++ b/src/engraving/rendering/single/singledraw.cpp @@ -60,6 +60,7 @@ #include "dom/guitarbend.h" #include "dom/hairpin.h" +#include "dom/hammeronpulloff.h" #include "dom/harppedaldiagram.h" #include "dom/harmonicmark.h" #include "dom/harmony.h" @@ -209,6 +210,10 @@ void SingleDraw::drawItem(const EngravingItem* item, Painter* painter) case ElementType::HAIRPIN_SEGMENT: draw(item_cast(item), painter); break; + case ElementType::HAMMER_ON_PULL_OFF_SEGMENT: draw(item_cast(item), painter); + break; + case ElementType::HAMMER_ON_PULL_OFF_TEXT: draw(item_cast(item), painter); + break; case ElementType::HARP_DIAGRAM: draw(item_cast(item), painter); break; case ElementType::HARMONIC_MARK_SEGMENT: draw(item_cast(item), painter); @@ -1764,6 +1769,16 @@ void SingleDraw::draw(const HairpinSegment* item, Painter* painter) } } +void SingleDraw::draw(const HammerOnPullOffSegment* item, muse::draw::Painter* painter) +{ + draw(toSlurSegment(item), painter); +} + +void SingleDraw::draw(const HammerOnPullOffText* item, Painter* painter) +{ + drawTextBase(item, painter); +} + void SingleDraw::draw(const HarpPedalDiagram* item, Painter* painter) { TRACE_DRAW_ITEM; diff --git a/src/engraving/rendering/single/singledraw.h b/src/engraving/rendering/single/singledraw.h index ccfce45fec280..b144c7c16002a 100644 --- a/src/engraving/rendering/single/singledraw.h +++ b/src/engraving/rendering/single/singledraw.h @@ -71,6 +71,8 @@ class GuitarBendSegment; class Hairpin; class HairpinSegment; +class HammerOnPullOffSegment; +class HammerOnPullOffText; class HarpPedalDiagram; class HarmonicMarkSegment; class Harmony; @@ -200,6 +202,8 @@ class SingleDraw static void draw(const GuitarBendSegment* item, muse::draw::Painter* painter); static void draw(const HairpinSegment* item, muse::draw::Painter* painter); + static void draw(const HammerOnPullOffSegment* item, muse::draw::Painter* painter); + static void draw(const HammerOnPullOffText* item, muse::draw::Painter* painter); static void draw(const HarpPedalDiagram* item, muse::draw::Painter* painter); static void draw(const HarmonicMarkSegment* item, muse::draw::Painter* painter); static void draw(const Harmony* item, muse::draw::Painter* painter); diff --git a/src/engraving/rendering/single/singlelayout.cpp b/src/engraving/rendering/single/singlelayout.cpp index 33cac59c7b4fd..268d40db557b5 100644 --- a/src/engraving/rendering/single/singlelayout.cpp +++ b/src/engraving/rendering/single/singlelayout.cpp @@ -53,6 +53,7 @@ #include "dom/gradualtempochange.h" #include "dom/guitarbend.h" #include "dom/hairpin.h" +#include "dom/hammeronpulloff.h" #include "dom/harppedaldiagram.h" #include "dom/instrchange.h" #include "dom/jump.h" @@ -157,6 +158,8 @@ void SingleLayout::layoutItem(EngravingItem* item) break; case ElementType::HAIRPIN: layout(toHairpin(item), ctx); break; + case ElementType::HAMMER_ON_PULL_OFF: layout(toHammerOnPullOff(item), ctx); + break; case ElementType::HARP_DIAGRAM: layout(toHarpPedalDiagram(item), ctx); break; case ElementType::IMAGE: layout(toImage(item), ctx); @@ -994,6 +997,59 @@ void SingleLayout::layout(Hairpin* item, const Context& ctx) layoutLine(item, ctx); } +void SingleLayout::layout(HammerOnPullOff* item, const Context& ctx) +{ + double spatium = item->spatium(); + HammerOnPullOffSegment* s = nullptr; + if (item->spannerSegments().empty()) { + s = new HammerOnPullOffSegment(ctx.dummyParent()->system()); + s->setTrack(item->track()); + item->add(s); + } else { + s = toHammerOnPullOffSegment(item->frontSegment()); + } + + s->setSpannerSegmentType(SpannerSegmentType::SINGLE); + + s->setPos(PointF()); + s->ups(Grip::START).p = PointF(0, 0); + s->ups(Grip::END).p = PointF(spatium * 6, 0); + s->setExtraHeight(0.0); + + SlurTieLayout::computeBezier(s); + + layout(s, ctx); + + item->setbbox(s->ldata()->bbox()); +} + +void SingleLayout::layout(HammerOnPullOffSegment* item, const Context& ctx) +{ + const std::vector& hopoTexts = item->hopoText(); + if (item->hopoText().empty()) { + HammerOnPullOffText* hopoText = new HammerOnPullOffText(item); + hopoText->setParent(item); + hopoText->setXmlText("H/P"); + item->addHopoText(hopoText); + } + + HammerOnPullOffText* hopoText = hopoTexts.front(); + Align align; + align.vertical = AlignV::BASELINE; + align.horizontal = AlignH::HCENTER; + hopoText->setAlign(align); + layoutTextBase(hopoText, ctx, hopoText->mutldata()); + + RectF bbox = item->ldata()->bbox(); + double x = 0.5 * (bbox.left() + bbox.right()); + double y = bbox.top() - 0.5 * item->spatium(); + hopoText->mutldata()->setPos(x, y); + + Shape itemShape = item->mutldata()->shape(); + itemShape.add(hopoText->shape().translated(hopoText->pos())); + item->mutldata()->setShape(itemShape); +} + void SingleLayout::layout(HairpinSegment* item, const Context& ctx) { const double spatium = item->spatium(); diff --git a/src/engraving/rendering/single/singlelayout.h b/src/engraving/rendering/single/singlelayout.h index f76f14a56db77..9c2d6d089f08a 100644 --- a/src/engraving/rendering/single/singlelayout.h +++ b/src/engraving/rendering/single/singlelayout.h @@ -186,6 +186,8 @@ class SingleLayout static void layout(GuitarBend* item, const Context& ctx); static void layout(Hairpin* item, const Context& ctx); + static void layout(HammerOnPullOff* item, const Context& ctx); + static void layout(HammerOnPullOffSegment* item, const Context& ctx); static void layout(HarpPedalDiagram* item, const Context& ctx); static void layout(Image* item, const Context& ctx); diff --git a/src/engraving/rw/read460/connectorinforeader.cpp b/src/engraving/rw/read460/connectorinforeader.cpp index 3bad0838dce85..df5ec440a8258 100644 --- a/src/engraving/rw/read460/connectorinforeader.cpp +++ b/src/engraving/rw/read460/connectorinforeader.cpp @@ -256,6 +256,7 @@ void ConnectorInfoReader::readAddConnector(ChordRest* item, ConnectorInfoReader* const ElementType type = info->type(); switch (type) { case ElementType::SLUR: + case ElementType::HAMMER_ON_PULL_OFF: { Spanner* spanner = toSpanner(info->connector()); const Location& l = info->location(); diff --git a/src/engraving/rw/read460/tread.cpp b/src/engraving/rw/read460/tread.cpp index f6005f1d219c7..0d6b3c5f6a799 100644 --- a/src/engraving/rw/read460/tread.cpp +++ b/src/engraving/rw/read460/tread.cpp @@ -111,6 +111,7 @@ #include "../../dom/groups.h" #include "../../dom/harppedaldiagram.h" #include "../../dom/hairpin.h" +#include "../../dom/hammeronpulloff.h" #include "../../dom/keysig.h" #include "../../dom/layoutbreak.h" #include "../../dom/ledgerline.h" @@ -216,6 +217,8 @@ void TRead::readItem(EngravingItem* item, XmlReader& xml, ReadContext& ctx) break; case ElementType::HAIRPIN: read(item_cast(item), xml, ctx); break; + case ElementType::HAMMER_ON_PULL_OFF: read(item_cast(item), xml, ctx); + break; case ElementType::HARMONY: read(item_cast(item), xml, ctx); break; case ElementType::HARMONIC_MARK: read(item_cast(item), xml, ctx); @@ -3134,6 +3137,15 @@ void TRead::read(Hairpin* h, XmlReader& e, ReadContext& ctx) h->styleChanged(); } +void TRead::read(HammerOnPullOff* h, XmlReader& xml, ReadContext& ctx) +{ + while (xml.readNextStartElement()) { + if (!readProperties(static_cast(h), xml, ctx)) { + xml.unknown(); + } + } +} + void TRead::read(Harmony* h, XmlReader& e, ReadContext& ctx) { while (e.readNextStartElement()) { @@ -3996,7 +4008,8 @@ bool TRead::readProperties(SlurTie* s, XmlReader& e, ReadContext& ctx) if (TRead::readProperty(s, tag, e, ctx, Pid::SLUR_DIRECTION)) { } else if (tag == "lineType") { s->setStyleType(static_cast(e.readInt())); - } else if (tag == "SlurSegment" || tag == "TieSegment" || tag == "LaissezVibSegment" || tag == "PartialTieSegment") { + } else if (tag == "SlurSegment" || tag == "TieSegment" || tag == "LaissezVibSegment" || tag == "PartialTieSegment" + || tag == "HammerOnPullOffSegment") { const int idx = e.intAttribute("no", 0); const int n = int(s->spannerSegments().size()); for (int i = n; i < idx; ++i) { @@ -4026,12 +4039,32 @@ void TRead::read(SlurTieSegment* s, XmlReader& e, ReadContext& ctx) s->ups(Grip::BEZIER2).off = e.readPoint() * _spatium; } else if (tag == "o4") { s->ups(Grip::END).off = e.readPoint() * _spatium; + } else if (tag == "HammerOnPullOffText") { + DO_ASSERT(s->isHammerOnPullOffSegment()); + readHopoText(toHammerOnPullOffSegment(s), e, ctx, e.intAttribute("idx")); } else if (!readItemProperties(s, e, ctx)) { e.unknown(); } } } +void TRead::readHopoText(HammerOnPullOffSegment* hopoSeg, XmlReader& xml, ReadContext& ctx, int idx) +{ + int hopoTextCount = static_cast(hopoSeg->hopoText().size()); + for (int i = hopoTextCount; i < idx; ++i) { + hopoSeg->addHopoText(new HammerOnPullOffText(hopoSeg)); + } + + HammerOnPullOffText* hopoText = new HammerOnPullOffText(hopoSeg); + while (xml.readNextStartElement()) { + if (!readProperties(toTextBase(hopoText), xml, ctx)) { + xml.unknown(); + } + } + + hopoSeg->addHopoText(hopoText); +} + bool TRead::readProperties(Spanner* s, XmlReader& e, ReadContext& ctx) { const AsciiStringView tag(e.name()); diff --git a/src/engraving/rw/read460/tread.h b/src/engraving/rw/read460/tread.h index 5c3a106b9e3fd..cea793fe45fc7 100644 --- a/src/engraving/rw/read460/tread.h +++ b/src/engraving/rw/read460/tread.h @@ -81,6 +81,7 @@ class GuitarBendSegment; class GuitarBendHold; class Hairpin; +class HammerOnPullOff; class Harmony; class HarmonicMark; class HarpPedalDiagram; @@ -232,6 +233,7 @@ class TRead static void read(GuitarBendHold* h, XmlReader& xml, ReadContext& ctx); static void read(Hairpin* h, XmlReader& xml, ReadContext& ctx); + static void read(HammerOnPullOff* h, XmlReader& xml, ReadContext& ctx); static void read(Harmony* h, XmlReader& xml, ReadContext& ctx); static void read(HarpPedalDiagram* h, XmlReader& xml, ReadContext& ctx); static void read(HarmonicMark* h, XmlReader& xml, ReadContext& ctx); @@ -387,5 +389,7 @@ class TRead static bool readProperties(StaffTextBase* t, XmlReader& xml, ReadContext& ctx); static void readSystemLock(Score* score, XmlReader& e); + + static void readHopoText(HammerOnPullOffSegment* hopoSeg, XmlReader& xml, ReadContext& ctx, int idx); }; } diff --git a/src/engraving/rw/write/twrite.cpp b/src/engraving/rw/write/twrite.cpp index e1dc6fe83492c..9e6cdf3533b81 100644 --- a/src/engraving/rw/write/twrite.cpp +++ b/src/engraving/rw/write/twrite.cpp @@ -75,6 +75,7 @@ #include "dom/guitarbend.h" #include "dom/hairpin.h" +#include "dom/hammeronpulloff.h" #include "dom/harmony.h" #include "dom/harmonicmark.h" #include "dom/harppedaldiagram.h" @@ -232,6 +233,8 @@ void TWrite::writeItem(const EngravingItem* item, XmlWriter& xml, WriteContext& break; case ElementType::HAIRPIN: write(item_cast(item), xml, ctx); break; + case ElementType::HAMMER_ON_PULL_OFF: write(item_cast(item), xml, ctx); + break; case ElementType::HARMONY: write(item_cast(item), xml, ctx); break; case ElementType::HARMONIC_MARK: write(item_cast(item), xml, ctx); @@ -1643,6 +1646,44 @@ void TWrite::write(const Hairpin* item, XmlWriter& xml, WriteContext& ctx) xml.endElement(); } +void TWrite::write(const HammerOnPullOff* item, XmlWriter& xml, WriteContext& ctx) +{ + if (item->broken()) { + return; + } + if (!ctx.canWrite(item)) { + return; + } + + xml.startElement(item); + + writeProperty(item, xml, Pid::PARTIAL_SPANNER_DIRECTION); + + writeProperties(static_cast(item), xml, ctx); + + xml.endElement(); +} + +void TWrite::writeProperties(const HammerOnPullOffSegment* seg, XmlWriter& xml, WriteContext& ctx) +{ + for (size_t i = 0; i < seg->hopoText().size(); ++i) { + HammerOnPullOffText* hopoText = seg->hopoText()[i]; + if (!hopoText->isUserModified()) { + continue; + } + write(hopoText, xml, ctx, i); + } +} + +void TWrite::write(const HammerOnPullOffText* item, XmlWriter& xml, WriteContext& ctx, size_t idx) +{ + xml.startElement(item, { { "idx", idx } }); + + writeProperties(toTextBase(item), xml, ctx, /*writeText*/ false); + + xml.endElement(); +} + void TWrite::write(const Harmony* item, XmlWriter& xml, WriteContext& ctx) { if (!ctx.canWrite(item)) { @@ -2593,14 +2634,7 @@ void TWrite::writeProperties(const SlurTie* item, XmlWriter& xml, WriteContext& void TWrite::writeSlur(const SlurTieSegment* seg, XmlWriter& xml, WriteContext& ctx, int no) { - if (seg->visible() && seg->autoplace() - && (seg->color() == ctx.configuration()->defaultColor()) - && seg->offset().isNull() - && seg->ups(Grip::START).off.isNull() - && seg->ups(Grip::BEZIER1).off.isNull() - && seg->ups(Grip::BEZIER2).off.isNull() - && seg->ups(Grip::END).off.isNull() - ) { + if (!seg->isUserModified()) { return; } @@ -2619,6 +2653,11 @@ void TWrite::writeSlur(const SlurTieSegment* seg, XmlWriter& xml, WriteContext& if (!seg->ups(Grip::END).off.isNull()) { xml.tagPoint("o4", seg->ups(Grip::END).off / _spatium); } + + if (seg->isHammerOnPullOffSegment()) { + writeProperties(toHammerOnPullOffSegment(seg), xml, ctx); + } + writeItemProperties(seg, xml, ctx); xml.endElement(); } diff --git a/src/engraving/rw/write/twrite.h b/src/engraving/rw/write/twrite.h index 8faef88df7e51..ea705743c3545 100644 --- a/src/engraving/rw/write/twrite.h +++ b/src/engraving/rw/write/twrite.h @@ -75,6 +75,9 @@ class GuitarBend; class GuitarBendSegment; class Hairpin; +class HammerOnPullOff; +class HammerOnPullOffSegment; +class HammerOnPullOffText; class Harmony; class HarmonicMark; class HarpPedalDiagram; @@ -217,6 +220,7 @@ class TWrite static void write(const GuitarBend* item, XmlWriter& xml, WriteContext& ctx); static void write(const Hairpin* item, XmlWriter& xml, WriteContext& ctx); + static void write(const HammerOnPullOff* item, XmlWriter& xml, WriteContext& ctx); static void write(const Harmony* item, XmlWriter& xml, WriteContext& ctx); static void write(const HarmonicMark* item, XmlWriter& xml, WriteContext& ctx); static void write(const HarpPedalDiagram* item, XmlWriter& xml, WriteContext& ctx); @@ -334,6 +338,10 @@ class TWrite static void writeProperties(const StaffTextBase* item, XmlWriter& xml, WriteContext& ctx); static void writeProperties(const SlurTie* item, XmlWriter& xml, WriteContext& ctx); static void writeSlur(const SlurTieSegment* seg, XmlWriter& xml, WriteContext& ctx, int no); + + static void writeProperties(const HammerOnPullOffSegment* seg, XmlWriter& xml, WriteContext& ctx); + static void write(const HammerOnPullOffText* item, XmlWriter& xml, WriteContext& ctx, size_t idx); + static void writeProperties(const SLine* item, XmlWriter& xml, WriteContext& ctx); static void writeProperties(const GuitarBendSegment* item, XmlWriter& xml, WriteContext& ctx); static void writeProperties(const Spanner* item, XmlWriter& xml, WriteContext& ctx); diff --git a/src/engraving/style/styledef.cpp b/src/engraving/style/styledef.cpp index 0cf0980d67a17..d26e9f9cf79e1 100644 --- a/src/engraving/style/styledef.cpp +++ b/src/engraving/style/styledef.cpp @@ -897,6 +897,26 @@ const std::array StyleDef::styleValue styleDef(rhGuitarFingeringFrameBgColor, PropertyValue::fromValue(Color::transparent)), styleDef(rhGuitarFingeringOffset, PointF()), + styleDef(hammerOnPullOffTappingFontFace, "Edwin"), + styleDef(hammerOnPullOffTappingFontSize, 8.0), + styleDef(hammerOnPullOffTappingLineSpacing, 1.0), + styleDef(hammerOnPullOffTappingFontSpatiumDependent, true), + styleDef(hammerOnPullOffTappingFontStyle, int(FontStyle::Normal)), + styleDef(hammerOnPullOffTappingColor, PropertyValue::fromValue(Color::BLACK)), + styleDef(hammerOnPullOffTappingAlign, Align(AlignH::LEFT, AlignV::BASELINE)), + styleDef(hammerOnPullOffTappingFrameType, int(FrameType::NO_FRAME)), + styleDef(hammerOnPullOffTappingFramePadding, 0.2), + styleDef(hammerOnPullOffTappingFrameWidth, 0.1), + styleDef(hammerOnPullOffTappingFrameRound, 0), + styleDef(hammerOnPullOffTappingFrameFgColor, PropertyValue::fromValue(Color::BLACK)), + styleDef(hammerOnPullOffTappingFrameBgColor, PropertyValue::fromValue(Color::transparent)), + styleDef(hammerOnPullOffTappingOffset, PointF()), + + styleDef(hopoShowOnStandardStaves, true), + styleDef(hopoShowOnTabStaves, true), + styleDef(hopoUpperCase, true), + styleDef(hopoShowAll, true), + styleDef(stringNumberFontFace, "Edwin"), styleDef(stringNumberFontSize, 8.0), styleDef(stringNumberLineSpacing, 1.0), diff --git a/src/engraving/style/styledef.h b/src/engraving/style/styledef.h index 8e407d520a0b3..76e6d1ddb444d 100644 --- a/src/engraving/style/styledef.h +++ b/src/engraving/style/styledef.h @@ -909,6 +909,26 @@ enum class Sid { rhGuitarFingeringFrameBgColor, rhGuitarFingeringOffset, + hammerOnPullOffTappingFontFace, + hammerOnPullOffTappingFontSize, + hammerOnPullOffTappingLineSpacing, + hammerOnPullOffTappingFontSpatiumDependent, + hammerOnPullOffTappingFontStyle, + hammerOnPullOffTappingColor, + hammerOnPullOffTappingAlign, + hammerOnPullOffTappingFrameType, + hammerOnPullOffTappingFramePadding, + hammerOnPullOffTappingFrameWidth, + hammerOnPullOffTappingFrameRound, + hammerOnPullOffTappingFrameFgColor, + hammerOnPullOffTappingFrameBgColor, + hammerOnPullOffTappingOffset, + + hopoShowOnStandardStaves, + hopoShowOnTabStaves, + hopoUpperCase, + hopoShowAll, + stringNumberFontFace, stringNumberFontSize, stringNumberLineSpacing, diff --git a/src/engraving/style/textstyle.cpp b/src/engraving/style/textstyle.cpp index 0d35f67414eb9..8cbbc17d1ca0e 100644 --- a/src/engraving/style/textstyle.cpp +++ b/src/engraving/style/textstyle.cpp @@ -207,6 +207,24 @@ const TextStyle rhGuitarFingeringTextStyle { { { TextStylePropertyType::MusicalSymbolsScale, Sid::dummyMusicalSymbolsScale, Pid::MUSICAL_SYMBOLS_SCALE }, } }; +const TextStyle hammerOnPullOffTextStyle { { + { TextStylePropertyType::FontFace, Sid::hammerOnPullOffTappingFontFace, Pid::FONT_FACE }, + { TextStylePropertyType::FontSize, Sid::hammerOnPullOffTappingFontSize, Pid::FONT_SIZE }, + { TextStylePropertyType::LineSpacing, Sid::hammerOnPullOffTappingLineSpacing, Pid::TEXT_LINE_SPACING }, + { TextStylePropertyType::SizeSpatiumDependent, Sid::hammerOnPullOffTappingFontSpatiumDependent, Pid::SIZE_SPATIUM_DEPENDENT }, + { TextStylePropertyType::FontStyle, Sid::hammerOnPullOffTappingFontStyle, Pid::FONT_STYLE }, + { TextStylePropertyType::Color, Sid::hammerOnPullOffTappingColor, Pid::COLOR }, + { TextStylePropertyType::TextAlign, Sid::hammerOnPullOffTappingAlign, Pid::ALIGN }, + { TextStylePropertyType::Offset, Sid::hammerOnPullOffTappingOffset, Pid::OFFSET }, + { TextStylePropertyType::FrameType, Sid::hammerOnPullOffTappingFrameType, Pid::FRAME_TYPE }, + { TextStylePropertyType::FramePadding, Sid::hammerOnPullOffTappingFramePadding, Pid::FRAME_PADDING }, + { TextStylePropertyType::FrameWidth, Sid::hammerOnPullOffTappingFrameWidth, Pid::FRAME_WIDTH }, + { TextStylePropertyType::FrameRound, Sid::hammerOnPullOffTappingFrameRound, Pid::FRAME_ROUND }, + { TextStylePropertyType::FrameBorderColor, Sid::hammerOnPullOffTappingFrameFgColor, Pid::FRAME_FG_COLOR }, + { TextStylePropertyType::FrameFillColor, Sid::hammerOnPullOffTappingFrameBgColor, Pid::FRAME_BG_COLOR }, + { TextStylePropertyType::MusicalSymbolsScale, Sid::dummyMusicalSymbolsScale, Pid::MUSICAL_SYMBOLS_SCALE }, +} }; + const TextStyle stringNumberTextStyle { { { TextStylePropertyType::FontFace, Sid::stringNumberFontFace, Pid::FONT_FACE }, { TextStylePropertyType::FontSize, Sid::stringNumberFontSize, Pid::FONT_SIZE }, @@ -1262,6 +1280,7 @@ const TextStyle* textStyle(TextStyleType idx) case TextStyleType::FINGERING: return &fingeringTextStyle; case TextStyleType::LH_GUITAR_FINGERING: return &lhGuitarFingeringTextStyle; case TextStyleType::RH_GUITAR_FINGERING: return &rhGuitarFingeringTextStyle; + case TextStyleType::HAMMER_ON_PULL_OFF: return &hammerOnPullOffTextStyle; case TextStyleType::STRING_NUMBER: return &stringNumberTextStyle; case TextStyleType::STRING_TUNINGS: return &stringTuningsStyle; // todo case TextStyleType::FRET_DIAGRAM_FINGERING: return &fretDiagramFingeringStyle; diff --git a/src/engraving/types/types.h b/src/engraving/types/types.h index 7b9c05bd1e311..7b15a547d1c25 100644 --- a/src/engraving/types/types.h +++ b/src/engraving/types/types.h @@ -205,6 +205,9 @@ enum class ElementType : unsigned char { TREMOLO_SINGLECHORD, TIME_TICK_ANCHOR, PARENTHESIS, + HAMMER_ON_PULL_OFF, + HAMMER_ON_PULL_OFF_SEGMENT, + HAMMER_ON_PULL_OFF_TEXT, ROOT_ITEM, DUMMY, @@ -802,6 +805,7 @@ enum class TextStyleType : unsigned char { FINGERING, LH_GUITAR_FINGERING, RH_GUITAR_FINGERING, + HAMMER_ON_PULL_OFF, STRING_NUMBER, STRING_TUNINGS, FRET_DIAGRAM_FINGERING, diff --git a/src/engraving/types/typesconv.cpp b/src/engraving/types/typesconv.cpp index d84a8350c2922..2420c7df3d249 100644 --- a/src/engraving/types/typesconv.cpp +++ b/src/engraving/types/typesconv.cpp @@ -307,6 +307,10 @@ static const std::vector > ELEMENT_TYPES = { { ElementType::TREMOLO_SINGLECHORD, "TremoloSingleChord", muse::TranslatableString("engraving", "Tremolo") }, { ElementType::TREMOLO_TWOCHORD, "TremoloTwoChord", muse::TranslatableString("engraving", "Tremolo") }, { ElementType::TIME_TICK_ANCHOR, "TimeTickAnchor", muse::TranslatableString("engraving", "Time tick anchor") }, + { ElementType::HAMMER_ON_PULL_OFF, "HammerOnPullOff", muse::TranslatableString("engraving", "Hammer-on / pull-off") }, + { ElementType::HAMMER_ON_PULL_OFF_SEGMENT, "HammerOnPullOffSegment", + muse::TranslatableString("engraving", "Hammer-on / pull-off segment") }, + { ElementType::HAMMER_ON_PULL_OFF_TEXT, "HammerOnPullOffText", muse::TranslatableString("engraving", "Hammer-on / pull-off text") }, { ElementType::ROOT_ITEM, "RootItem", muse::TranslatableString::untranslatable("Root item") }, { ElementType::DUMMY, "Dummy", muse::TranslatableString::untranslatable("Dummy") }, }; @@ -1255,6 +1259,8 @@ static const std::vector > TEXTSTYLE_TYPES = { { TextStyleType::FINGERING, "fingering", muse::TranslatableString("engraving", "Fingering") }, { TextStyleType::LH_GUITAR_FINGERING, "guitar_fingering_lh", muse::TranslatableString("engraving", "LH guitar fingering") }, { TextStyleType::RH_GUITAR_FINGERING, "guitar_fingering_rh", muse::TranslatableString("engraving", "RH guitar fingering") }, + { TextStyleType::HAMMER_ON_PULL_OFF, "hammer_on_pull_off", + muse::TranslatableString("engraving", "Hammer-ons, pull-offs, and tapping") }, { TextStyleType::STRING_NUMBER, "string_number", muse::TranslatableString("engraving", "String number") }, { TextStyleType::STRING_TUNINGS, "string_tunings", muse::TranslatableString("engraving", "String tunings") }, { TextStyleType::FRET_DIAGRAM_FINGERING, "fret_diagram_fingering", diff --git a/src/framework/ui/data/MusescoreIcon.ttf b/src/framework/ui/data/MusescoreIcon.ttf index ad2da51b64332..ac18ccaa4538e 100644 Binary files a/src/framework/ui/data/MusescoreIcon.ttf and b/src/framework/ui/data/MusescoreIcon.ttf differ diff --git a/src/framework/ui/view/iconcodes.h b/src/framework/ui/view/iconcodes.h index 6612d7fc1f411..a2dcda1c1b9e4 100644 --- a/src/framework/ui/view/iconcodes.h +++ b/src/framework/ui/view/iconcodes.h @@ -488,6 +488,9 @@ class IconCode FRET_FRAME = 0xF491, DURATION_CURSOR = 0xF492, + LOWER_CASE = 0xF49E, + UPPER_CASE = 0xF49F, + NONE = 0xFFFF }; diff --git a/src/importexport/musicxml/internal/musicxml/import/importmusicxmlpass1.cpp b/src/importexport/musicxml/internal/musicxml/import/importmusicxmlpass1.cpp index ce6e9315529ce..b73caa5a01042 100644 --- a/src/importexport/musicxml/internal/musicxml/import/importmusicxmlpass1.cpp +++ b/src/importexport/musicxml/internal/musicxml/import/importmusicxmlpass1.cpp @@ -1808,6 +1808,7 @@ static void updateStyles(Score* score, } bool needUseDefaultSize = tid == TextStyleType::HARMONY_ROMAN + || tid == TextStyleType::HAMMER_ON_PULL_OFF || isTitleFrameStyle(tid) || isHarpPedalStyle(tid); diff --git a/src/importexport/musicxml/tests/data/importTie2_ref.mscx b/src/importexport/musicxml/tests/data/importTie2_ref.mscx index ec027a9edba67..d3f78fddd0cec 100644 --- a/src/importexport/musicxml/tests/data/importTie2_ref.mscx +++ b/src/importexport/musicxml/tests/data/importTie2_ref.mscx @@ -47,6 +47,7 @@ 10.25 Times New Roman 10.25 + Times New Roman Times New Roman 10.25 10.25 diff --git a/src/importexport/musicxml/tests/data/importTie3_ref.mscx b/src/importexport/musicxml/tests/data/importTie3_ref.mscx index 64eecebf9222a..9d52b75d19aee 100644 --- a/src/importexport/musicxml/tests/data/importTie3_ref.mscx +++ b/src/importexport/musicxml/tests/data/importTie3_ref.mscx @@ -41,6 +41,7 @@ Times New Roman Times New Roman Times New Roman + Times New Roman Times New Roman Times New Roman Times New Roman diff --git a/src/importexport/musicxml/tests/data/importTie4_ref.mscx b/src/importexport/musicxml/tests/data/importTie4_ref.mscx index 822c1ece89082..686f15e8282aa 100644 --- a/src/importexport/musicxml/tests/data/importTie4_ref.mscx +++ b/src/importexport/musicxml/tests/data/importTie4_ref.mscx @@ -49,6 +49,7 @@ 22 Palatino,serif 22 + Palatino,serif Palatino,serif 22 22 diff --git a/src/importexport/musicxml/tests/data/testDoletOttavas_ref.mscx b/src/importexport/musicxml/tests/data/testDoletOttavas_ref.mscx index 19b26db2da0eb..60fd851fae997 100644 --- a/src/importexport/musicxml/tests/data/testDoletOttavas_ref.mscx +++ b/src/importexport/musicxml/tests/data/testDoletOttavas_ref.mscx @@ -40,6 +40,7 @@ Times New Roman Times New Roman Times New Roman + Times New Roman Times New Roman Times New Roman Times New Roman diff --git a/src/importexport/musicxml/tests/data/testDuplicateFermataOnGraceNoteAndMainNote_ref.mscx b/src/importexport/musicxml/tests/data/testDuplicateFermataOnGraceNoteAndMainNote_ref.mscx index 15572be3b3a4c..7c286bdc05a92 100644 --- a/src/importexport/musicxml/tests/data/testDuplicateFermataOnGraceNoteAndMainNote_ref.mscx +++ b/src/importexport/musicxml/tests/data/testDuplicateFermataOnGraceNoteAndMainNote_ref.mscx @@ -34,6 +34,7 @@ 10 FreeSerif 10 + FreeSerif FreeSerif 10 10 diff --git a/src/importexport/musicxml/tests/data/testDuplicateFermataOnGraceNote_ref.mscx b/src/importexport/musicxml/tests/data/testDuplicateFermataOnGraceNote_ref.mscx index 32109023dd602..278675811580a 100644 --- a/src/importexport/musicxml/tests/data/testDuplicateFermataOnGraceNote_ref.mscx +++ b/src/importexport/musicxml/tests/data/testDuplicateFermataOnGraceNote_ref.mscx @@ -34,6 +34,7 @@ 10 FreeSerif 10 + FreeSerif FreeSerif 10 10 diff --git a/src/importexport/musicxml/tests/data/testFinaleInstr2_ref.mscx b/src/importexport/musicxml/tests/data/testFinaleInstr2_ref.mscx index ca4d03ebaeb96..79e44a2db1311 100644 --- a/src/importexport/musicxml/tests/data/testFinaleInstr2_ref.mscx +++ b/src/importexport/musicxml/tests/data/testFinaleInstr2_ref.mscx @@ -48,6 +48,7 @@ 8.8 Times 8.8 + Times Times 8.8 8.8 diff --git a/src/importexport/musicxml/tests/data/testFinaleInstr_ref.mscx b/src/importexport/musicxml/tests/data/testFinaleInstr_ref.mscx index f0860aedbfc3f..53c6000718138 100644 --- a/src/importexport/musicxml/tests/data/testFinaleInstr_ref.mscx +++ b/src/importexport/musicxml/tests/data/testFinaleInstr_ref.mscx @@ -48,6 +48,7 @@ 7.4 Times 7.4 + Times Times 7.4 7.4 diff --git a/src/importexport/musicxml/tests/data/testInferCodaII_ref.mscx b/src/importexport/musicxml/tests/data/testInferCodaII_ref.mscx index 1847693c4d557..0b3cd3b128f37 100644 --- a/src/importexport/musicxml/tests/data/testInferCodaII_ref.mscx +++ b/src/importexport/musicxml/tests/data/testInferCodaII_ref.mscx @@ -40,6 +40,7 @@ Times New Roman Times New Roman Times New Roman + Times New Roman Times New Roman Times New Roman Times New Roman diff --git a/src/importexport/musicxml/tests/data/testInferSegnoII_ref.mscx b/src/importexport/musicxml/tests/data/testInferSegnoII_ref.mscx index 6a874868bf4f9..28c02eb435531 100644 --- a/src/importexport/musicxml/tests/data/testInferSegnoII_ref.mscx +++ b/src/importexport/musicxml/tests/data/testInferSegnoII_ref.mscx @@ -40,6 +40,7 @@ Times New Roman Times New Roman Times New Roman + Times New Roman Times New Roman Times New Roman Times New Roman diff --git a/src/importexport/musicxml/tests/data/testInferredCrescLines2_ref.mscx b/src/importexport/musicxml/tests/data/testInferredCrescLines2_ref.mscx index b64d2e7f85858..67e8ed72deeaa 100644 --- a/src/importexport/musicxml/tests/data/testInferredCrescLines2_ref.mscx +++ b/src/importexport/musicxml/tests/data/testInferredCrescLines2_ref.mscx @@ -41,6 +41,7 @@ Times New Roman Times New Roman Times New Roman + Times New Roman Times New Roman Times New Roman Times New Roman diff --git a/src/importexport/musicxml/tests/data/testInferredCrescLines_ref.mscx b/src/importexport/musicxml/tests/data/testInferredCrescLines_ref.mscx index c465532a8a5a3..85bd10236ead5 100644 --- a/src/importexport/musicxml/tests/data/testInferredCrescLines_ref.mscx +++ b/src/importexport/musicxml/tests/data/testInferredCrescLines_ref.mscx @@ -41,6 +41,7 @@ Times New Roman Times New Roman Times New Roman + Times New Roman Times New Roman Times New Roman Times New Roman diff --git a/src/importexport/musicxml/tests/data/testInferredTempoText_ref.mscx b/src/importexport/musicxml/tests/data/testInferredTempoText_ref.mscx index 77f4e78d98ce5..8b575bf563146 100644 --- a/src/importexport/musicxml/tests/data/testInferredTempoText_ref.mscx +++ b/src/importexport/musicxml/tests/data/testInferredTempoText_ref.mscx @@ -40,6 +40,7 @@ Times New Roman Times New Roman Times New Roman + Times New Roman Times New Roman Times New Roman Times New Roman diff --git a/src/importexport/musicxml/tests/data/testSibMetronomeMarks_ref.mscx b/src/importexport/musicxml/tests/data/testSibMetronomeMarks_ref.mscx index 6c019acd9499f..d414f4629d8ee 100644 --- a/src/importexport/musicxml/tests/data/testSibMetronomeMarks_ref.mscx +++ b/src/importexport/musicxml/tests/data/testSibMetronomeMarks_ref.mscx @@ -40,6 +40,7 @@ Times New Roman Times New Roman Times New Roman + Times New Roman Times New Roman Times New Roman Times New Roman diff --git a/src/importexport/musicxml/tests/data/testSibOttavas_ref.mscx b/src/importexport/musicxml/tests/data/testSibOttavas_ref.mscx index 40776947325c1..5b0fedbe03646 100644 --- a/src/importexport/musicxml/tests/data/testSibOttavas_ref.mscx +++ b/src/importexport/musicxml/tests/data/testSibOttavas_ref.mscx @@ -57,6 +57,7 @@ 11.9365 Quicksand 11.9365 + Quicksand Quicksand 11.9365 11.9365 diff --git a/src/importexport/musicxml/tests/data/testTitleSwapMu_ref.mscx b/src/importexport/musicxml/tests/data/testTitleSwapMu_ref.mscx index 053703a7f6664..ebf0c97c7cd24 100644 --- a/src/importexport/musicxml/tests/data/testTitleSwapMu_ref.mscx +++ b/src/importexport/musicxml/tests/data/testTitleSwapMu_ref.mscx @@ -42,6 +42,7 @@ 11.9365 Plantin MT Std 11.9365 + Plantin MT Std Plantin MT Std 11.9365 11.9365 diff --git a/src/importexport/musicxml/tests/data/testTitleSwapSib_ref.mscx b/src/importexport/musicxml/tests/data/testTitleSwapSib_ref.mscx index cb1544bafd05a..71df491139488 100644 --- a/src/importexport/musicxml/tests/data/testTitleSwapSib_ref.mscx +++ b/src/importexport/musicxml/tests/data/testTitleSwapSib_ref.mscx @@ -42,6 +42,7 @@ 11.9365 Plantin MT Std 11.9365 + Plantin MT Std Plantin MT Std 11.9365 11.9365 diff --git a/src/inspector/internal/services/elementrepositoryservice.cpp b/src/inspector/internal/services/elementrepositoryservice.cpp index 034e2c1329916..2faacf1f6590b 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::TEXTLINE: case mu::engraving::ElementType::NOTELINE: case mu::engraving::ElementType::SLUR: + case mu::engraving::ElementType::HAMMER_ON_PULL_OFF: case mu::engraving::ElementType::TIE: case mu::engraving::ElementType::LAISSEZ_VIB: case mu::engraving::ElementType::PARTIAL_TIE: @@ -323,6 +324,7 @@ QList ElementRepositoryService::findLines(mu::eng { mu::engraving::ElementType::TEXTLINE, mu::engraving::ElementType::TEXTLINE_SEGMENT }, { mu::engraving::ElementType::NOTELINE, mu::engraving::ElementType::NOTELINE_SEGMENT }, { mu::engraving::ElementType::SLUR, mu::engraving::ElementType::SLUR_SEGMENT }, + { mu::engraving::ElementType::HAMMER_ON_PULL_OFF, mu::engraving::ElementType::HAMMER_ON_PULL_OFF_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 }, diff --git a/src/inspector/models/abstractinspectormodel.cpp b/src/inspector/models/abstractinspectormodel.cpp index b1c0145943671..7bb8cafecada2 100644 --- a/src/inspector/models/abstractinspectormodel.cpp +++ b/src/inspector/models/abstractinspectormodel.cpp @@ -50,6 +50,8 @@ static const QMap NOTATION_ELEME { mu::engraving::ElementType::VIBRATO_SEGMENT, InspectorModelType::TYPE_VIBRATO }, { mu::engraving::ElementType::SLUR, InspectorModelType::TYPE_SLUR }, { mu::engraving::ElementType::SLUR_SEGMENT, InspectorModelType::TYPE_SLUR }, + { mu::engraving::ElementType::HAMMER_ON_PULL_OFF, InspectorModelType::TYPE_HAMMER_ON_PULL_OFF }, + { mu::engraving::ElementType::HAMMER_ON_PULL_OFF_SEGMENT, InspectorModelType::TYPE_HAMMER_ON_PULL_OFF }, { mu::engraving::ElementType::TIE, InspectorModelType::TYPE_TIE }, { mu::engraving::ElementType::TIE_SEGMENT, InspectorModelType::TYPE_TIE }, { mu::engraving::ElementType::LAISSEZ_VIB, InspectorModelType::TYPE_LAISSEZ_VIB }, diff --git a/src/inspector/models/abstractinspectormodel.h b/src/inspector/models/abstractinspectormodel.h index 9eb9ad563cf32..181e98b27682f 100644 --- a/src/inspector/models/abstractinspectormodel.h +++ b/src/inspector/models/abstractinspectormodel.h @@ -104,6 +104,7 @@ class AbstractInspectorModel : public QObject, public muse::async::Asyncable TYPE_VOLTA, TYPE_VIBRATO, TYPE_SLUR, + TYPE_HAMMER_ON_PULL_OFF, TYPE_TIE, TYPE_LAISSEZ_VIB, TYPE_PARTIAL_TIE, diff --git a/src/inspector/models/inspectormodelcreator.cpp b/src/inspector/models/inspectormodelcreator.cpp index c6997a0b6977a..8e314cc174ef0 100644 --- a/src/inspector/models/inspectormodelcreator.cpp +++ b/src/inspector/models/inspectormodelcreator.cpp @@ -156,6 +156,8 @@ AbstractInspectorModel* InspectorModelCreator::newInspectorModel(InspectorModelT return new SlurAndTieSettingsModel(parent, repository, SlurAndTieSettingsModel::LaissezVib); case InspectorModelType::TYPE_PARTIAL_TIE: return new SlurAndTieSettingsModel(parent, repository, SlurAndTieSettingsModel::PartialTie); + case InspectorModelType::TYPE_HAMMER_ON_PULL_OFF: + return new SlurAndTieSettingsModel(parent, repository, SlurAndTieSettingsModel::HammerOnPullOff); 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 22f69a33808b4..a883a1076c3e6 100644 --- a/src/inspector/models/notation/lines/slurandtiesettingsmodel.cpp +++ b/src/inspector/models/notation/lines/slurandtiesettingsmodel.cpp @@ -58,6 +58,11 @@ SlurAndTieSettingsModel::SlurAndTieSettingsModel(QObject* parent, IElementReposi setElementType(mu::engraving::ElementType::PARTIAL_TIE); setTitle(muse::qtrc("inspector", "Tie (partial)")); setIcon(IconCode::NOTE_TIE); + } else if (elementType == ElementType::HammerOnPullOff) { + setModelType(InspectorModelType::TYPE_HAMMER_ON_PULL_OFF); + setElementType(mu::engraving::ElementType::HAMMER_ON_PULL_OFF); + setTitle(muse::qtrc("inspector", "Hammer-on/pull-off")); + setIcon(IconCode::NOTE_SLUR); } createProperties(); diff --git a/src/inspector/models/notation/lines/slurandtiesettingsmodel.h b/src/inspector/models/notation/lines/slurandtiesettingsmodel.h index d8be1cd2de911..0044f6afac2d1 100644 --- a/src/inspector/models/notation/lines/slurandtiesettingsmodel.h +++ b/src/inspector/models/notation/lines/slurandtiesettingsmodel.h @@ -42,7 +42,8 @@ class SlurAndTieSettingsModel : public AbstractInspectorModel Slur, Tie, LaissezVib, - PartialTie + PartialTie, + HammerOnPullOff, }; explicit SlurAndTieSettingsModel(QObject* parent, IElementRepositoryService* repository, ElementType elementType); diff --git a/src/inspector/types/texttypes.h b/src/inspector/types/texttypes.h index 358d2358dece5..536e35250e9d8 100644 --- a/src/inspector/types/texttypes.h +++ b/src/inspector/types/texttypes.h @@ -164,7 +164,8 @@ static const QList TEXT_ELEMENT_TYPES = { mu::engraving::ElementType::CAPO, mu::engraving::ElementType::STRING_TUNINGS, mu::engraving::ElementType::HARP_DIAGRAM, - mu::engraving::ElementType::SOUND_FLAG + mu::engraving::ElementType::SOUND_FLAG, + mu::engraving::ElementType::HAMMER_ON_PULL_OFF_TEXT }; } diff --git a/src/inspector/view/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml b/src/inspector/view/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml index 926eec64b4908..83ed977a52016 100644 --- a/src/inspector/view/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml +++ b/src/inspector/view/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml @@ -93,7 +93,8 @@ Loader { case Inspector.TYPE_SLUR: case Inspector.TYPE_TIE: case Inspector.TYPE_LAISSEZ_VIB: - case Inspector.TYPE_PARTIAL_TIE: return slurAndTieComp + case Inspector.TYPE_PARTIAL_TIE: + case Inspector.TYPE_HAMMER_ON_PULL_OFF: 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/inotationinteraction.h b/src/notation/inotationinteraction.h index e6e3a2c4dab70..4f9e1fa7edfc6 100644 --- a/src/notation/inotationinteraction.h +++ b/src/notation/inotationinteraction.h @@ -192,6 +192,7 @@ class INotationInteraction virtual void addTiedNoteToChord() = 0; virtual void addLaissezVibToSelection() = 0; virtual void addSlurToSelection() = 0; + virtual void addHammerOnPullOffToSelection() = 0; virtual void addOttavaToSelection(OttavaType type) = 0; virtual void addHairpinOnGripDrag(engraving::EditData& ed, bool isLeftGrip) = 0; virtual void addHairpinsToSelection(HairpinType type) = 0; diff --git a/src/notation/internal/notationactioncontroller.cpp b/src/notation/internal/notationactioncontroller.cpp index 5b9ec0a065679..e47f7d171bae7 100644 --- a/src/notation/internal/notationactioncontroller.cpp +++ b/src/notation/internal/notationactioncontroller.cpp @@ -229,6 +229,7 @@ void NotationActionController::init() registerAction("chord-tie", &Controller::chordTie); registerAction("lv", &Controller::addLaissezVib); registerAction("add-slur", &Controller::addSlur); + registerAction("hammer-on-pull-off", &Controller::addHammerOnPullOff); registerAction(UNDO_ACTION_CODE, &Interaction::undo, &Controller::canUndo); registerAction(REDO_ACTION_CODE, &Interaction::redo, &Controller::canRedo); @@ -1390,6 +1391,16 @@ void NotationActionController::addSlur() } } +void NotationActionController::addHammerOnPullOff() +{ + auto interaction = currentNotationInteraction(); + if (!interaction) { + return; + } + + interaction->addHammerOnPullOffToSelection(); +} + void NotationActionController::addFret(int num) { auto interaction = currentNotationInteraction(); diff --git a/src/notation/internal/notationactioncontroller.h b/src/notation/internal/notationactioncontroller.h index 6b2308cd5248d..3e175a9076028 100644 --- a/src/notation/internal/notationactioncontroller.h +++ b/src/notation/internal/notationactioncontroller.h @@ -115,6 +115,7 @@ class NotationActionController : public muse::actions::Actionable, public muse:: void chordTie(); void addLaissezVib(); void addSlur(); + void addHammerOnPullOff(); void addFret(int num); void insertClef(mu::engraving::ClefType type); diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index dcc1fd3e7fd55..95f3ea6e80d24 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -59,6 +59,7 @@ #include "engraving/dom/factory.h" #include "engraving/dom/figuredbass.h" #include "engraving/dom/guitarbend.h" +#include "engraving/dom/hammeronpulloff.h" #include "engraving/dom/image.h" #include "engraving/dom/instrchange.h" #include "engraving/dom/gradualtempochange.h" @@ -1541,6 +1542,7 @@ bool NotationInteraction::updateDropSingle(const PointF& pos, Qt::KeyboardModifi case ElementType::REHEARSAL_MARK: case ElementType::CHORD: case ElementType::SLUR: + case ElementType::HAMMER_ON_PULL_OFF: case ElementType::HARMONY: case ElementType::BAGPIPE_EMBELLISHMENT: case ElementType::AMBITUS: @@ -1857,6 +1859,7 @@ bool NotationInteraction::dropSingle(const PointF& pos, Qt::KeyboardModifiers mo } break; case ElementType::SLUR: + case ElementType::HAMMER_ON_PULL_OFF: { EngravingItem* el = dropTarget(edd.ed); mu::engraving::Slur* dropElement = toSlur(edd.ed.dropElement); @@ -2152,6 +2155,8 @@ bool NotationInteraction::applyPaletteElement(mu::engraving::EngravingItem* elem if (element->isSlur() || cr1->staffIdx() == cr2->staffIdx()) { addSingle = true; } + } else if (sel.element() && sel.element()->isSlurSegment() && element->isHammerOnPullOff()) { + addSingle = true; } auto isEntryDrumStaff = [score]() { @@ -2679,6 +2684,9 @@ void NotationInteraction::doAddSlur(const Slur* slurTemplate) } else if (sel.isSingle()) { if (sel.element()->isNote() && !toNote(sel.element())->isTrillCueNote()) { doAddSlur(toNote(sel.element())->chord(), nullptr, slurTemplate); + } else if (sel.element()->isSlurSegment() && slurTemplate->isHammerOnPullOff()) { + Slur* slur = toSlurSegment(sel.element())->slur(); + doAddSlur(slur->startElement(), slur->endElement(), slurTemplate); } } else { EngravingItem* firstItem = nullptr; @@ -2760,6 +2768,14 @@ void NotationInteraction::doAddSlur(EngravingItem* firstItem, EngravingItem* sec Slur* slur = firstChordRest->slur(secondChordRest); if (!slur || slur->slurDirection() != DirectionV::AUTO) { slur = score()->addSlur(firstChordRest, secondChordRest, slurTemplate); + } else if (slur && slurTemplate->isHammerOnPullOff()) { + // Replace existing slur with HOPO + endEditElement(); + score()->undoRemoveElement(slur); + slur = score()->addSlur(firstChordRest, secondChordRest, slurTemplate); + SlurSegment* segment = slur->frontSegment(); + select({ segment }, SelectType::SINGLE); + startEditGrip(segment, Grip::END); } if (m_noteInput->isNoteInputMode()) { @@ -5058,6 +5074,17 @@ void NotationInteraction::addSlurToSelection() doAddSlur(); } +void NotationInteraction::addHammerOnPullOffToSelection() +{ + if (selection()->isNone()) { + return; + } + + HammerOnPullOff* hopo = Factory::createHammerOnPullOff(score()->dummy()); + // Calls `startEdit` internally + doAddSlur(hopo); +} + void NotationInteraction::addOttavaToSelection(OttavaType type) { if (selection()->isNone()) { diff --git a/src/notation/internal/notationinteraction.h b/src/notation/internal/notationinteraction.h index b39ed325117d9..023a6c67b5240 100644 --- a/src/notation/internal/notationinteraction.h +++ b/src/notation/internal/notationinteraction.h @@ -199,6 +199,7 @@ class NotationInteraction : public INotationInteraction, public muse::Injectable void addLaissezVibToSelection() override; void addTiedNoteToChord() override; void addSlurToSelection() override; + void addHammerOnPullOffToSelection() override; void addOttavaToSelection(OttavaType type) override; void addHairpinOnGripDrag(engraving::EditData& ed, bool isLeftGrip) override; void addHairpinsToSelection(HairpinType type) override; diff --git a/src/notation/internal/notationuiactions.cpp b/src/notation/internal/notationuiactions.cpp index 093ef72d53b35..cb4e3887a3b2d 100644 --- a/src/notation/internal/notationuiactions.cpp +++ b/src/notation/internal/notationuiactions.cpp @@ -2525,6 +2525,12 @@ const UiActionList NotationUiActions::m_actions = { TranslatableString("action", "Slight bend"), IconCode::Code::GUITAR_SLIGHT_BEND ), + UiAction("hammer-on-pull-off", + mu::context::UiCtxProjectFocused, + mu::context::CTX_NOTATION_OPENED, + TranslatableString("action", "Hammer-on/pull-off"), + TranslatableString("action", "Add hammer-on/pull-off") + ) }; const UiActionList NotationUiActions::m_scoreConfigActions = { diff --git a/src/notation/notationmodule.cpp b/src/notation/notationmodule.cpp index 2e1937e2298b4..31d94c874bf2f 100644 --- a/src/notation/notationmodule.cpp +++ b/src/notation/notationmodule.cpp @@ -98,6 +98,7 @@ #include "view/styledialog/glissandosectionmodel.h" #include "view/styledialog/notelinesectionmodel.h" #include "view/styledialog/clefkeytimesigpagemodel.h" +#include "view/styledialog/hammeronpullofftappingpagemodel.h" #include "diagnostics/idiagnosticspathsregister.h" @@ -230,6 +231,7 @@ void NotationModule::registerUiTypes() qmlRegisterType("MuseScore.NotationScene", 1, 0, "GlissandoSectionModel"); qmlRegisterType("MuseScore.NotationScene", 1, 0, "NoteLineSectionModel"); qmlRegisterType("MuseScore.NotationScene", 1, 0, "ClefKeyTimeSigPageModel"); + qmlRegisterType("MuseScore.NotationScene", 1, 0, "HammerOnPullOffTappingPageModel"); qmlRegisterUncreatableType("MuseScore.NotationScene", 1, 0, "NoteInputBarCustomiseItem", "Cannot create"); diff --git a/src/notation/notationscene.qrc b/src/notation/notationscene.qrc index e9f7ef1043477..b3ac58ec65c28 100644 --- a/src/notation/notationscene.qrc +++ b/src/notation/notationscene.qrc @@ -99,5 +99,8 @@ qml/MuseScore/NotationScene/internal/EditStyle/courtesyImages/repeats-on-parens.png qml/MuseScore/NotationScene/internal/EditStyle/courtesyImages/repeats-on.png qml/MuseScore/NotationScene/internal/SelectionFilterSection.qml + qml/MuseScore/NotationScene/internal/EditStyle/HammerOnPullOffTappingPage.qml + qml/MuseScore/NotationScene/internal/EditStyle/hammerOnPullOffImages/hopoShowAll.png + qml/MuseScore/NotationScene/internal/EditStyle/hammerOnPullOffImages/hopoNotShowAll.png diff --git a/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/HammerOnPullOffTappingPage.qml b/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/HammerOnPullOffTappingPage.qml new file mode 100644 index 0000000000000..78f5c117d9301 --- /dev/null +++ b/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/HammerOnPullOffTappingPage.qml @@ -0,0 +1,169 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2021 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.Layouts 1.15 +import QtQuick.Controls + +import MuseScore.NotationScene 1.0 +import Muse.UiComponents 1.0 +import Muse.Ui 1.0 + +StyledFlickable { + id: root + + signal goToTextStylePage(string s) + + contentWidth: column.width + contentHeight: column.height + + HammerOnPullOffTappingPageModel { + id: hopoPage + } + + ColumnLayout { + id: column + spacing: 12 + + StyledGroupBox { + Layout.fillWidth: true + Layout.minimumWidth: 500 + title: qsTrc("notation/editstyle/hammeronpulloff", "Visibility") + + ColumnLayout { + width: parent.width + spacing: 10 + + StyledTextLabel { + text: qsTrc("notation/editstyle/hammeronpulloff", "Show ‘H’ and ‘P’ symbols on") + } + + RowLayout { + ToggleButton { + checked: hopoPage.showOnStandardStaves.value === true + onToggled: { + hopoPage.showOnStandardStaves.value = !hopoPage.showOnStandardStaves.value + } + } + + StyledTextLabel { + Layout.fillWidth: true + horizontalAlignment: Text.AlignLeft + text: "Standard staves" + } + } + + RowLayout { + ToggleButton { + checked: hopoPage.showOnTabStaves.value === true + onToggled: { + hopoPage.showOnTabStaves.value = !hopoPage.showOnTabStaves.value + } + } + + StyledTextLabel { + id : toggleText + Layout.fillWidth: true + horizontalAlignment: Text.AlignLeft + text: "Tablature staves" + } + } + } + } + + StyledGroupBox { + Layout.fillWidth: true + Layout.minimumWidth: 500 + title: qsTrc("notation/editstyle/hammeronpulloff", "Case") + + RowLayout { + width: parent.width + spacing: 10 + + RadioButtonGroup { + orientation: ListView.Horizontal + spacing: 6 + + model: [ + {iconCode: IconCode.UPPER_CASE, value: true }, + {iconCode: IconCode.LOWER_CASE, value: false }, + ] + + delegate: FlatRadioButton { + width: 40 + height: 30 + iconCode: modelData.iconCode + checked: hopoPage.hopoUpperCase.value === modelData.value + onToggled: hopoPage.hopoUpperCase.value = modelData.value + } + } + + SeparatorLine { + orientation: Qt.Vertical + } + + FlatButton { + text: qsTrc("notation", "Edit text style") + + onClicked: { + root.goToTextStylePage("hammer-ons-pull-offs-and-tapping") + } + } + + Item { + Layout.fillWidth: true + } + } + } + + StyledGroupBox { + Layout.fillWidth: true + Layout.minimumWidth: 500 + title: qsTrc("notation/editstyle/hammeronpulloff", "Consecutive hammer-on/pull-offs") + + ColumnLayout { + width: parent.width + spacing: 10 + + RadioButtonGroup { + orientation: ListView.Vertical + spacing: 6 + + model: [ + {text: qsTrc("notation/editstyle/hammeronpulloff", "Show ‘H’ or ‘P’ between each pair of notes"), value: true }, + {text: qsTrc("notation/editstyle/hammeronpulloff", "Show ‘H’ or ‘P’ once per group of ascending/descending notes"), value: false }, + ] + + delegate: RoundedRadioButton { + text: modelData.text + checked: hopoPage.hopoShowAll.value === modelData.value + onToggled: hopoPage.hopoShowAll.value = modelData.value + } + } + + StyledImage { + forceHeight: 180 + source: hopoPage.hopoShowAll.value === true ? "hammerOnPullOffImages/hopoShowAll" : "hammerOnPullOffImages/hopoNotShowAll" + } + } + } + } +} diff --git a/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/hammerOnPullOffImages/hopoNotShowAll.png b/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/hammerOnPullOffImages/hopoNotShowAll.png new file mode 100644 index 0000000000000..bcccaf5f3fb19 Binary files /dev/null and b/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/hammerOnPullOffImages/hopoNotShowAll.png differ diff --git a/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/hammerOnPullOffImages/hopoShowAll.png b/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/hammerOnPullOffImages/hopoShowAll.png new file mode 100644 index 0000000000000..efb09a74f8da9 Binary files /dev/null and b/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/hammerOnPullOffImages/hopoShowAll.png differ diff --git a/src/notation/tests/mocks/notationinteractionmock.h b/src/notation/tests/mocks/notationinteractionmock.h index 0c10c6ad6471d..1d9709b8b5fa3 100644 --- a/src/notation/tests/mocks/notationinteractionmock.h +++ b/src/notation/tests/mocks/notationinteractionmock.h @@ -154,6 +154,7 @@ class NotationInteractionMock : public INotationInteraction MOCK_METHOD(void, addLaissezVibToSelection, (), (override)); MOCK_METHOD(void, addTiedNoteToChord, (), (override)); MOCK_METHOD(void, addSlurToSelection, (), (override)); + MOCK_METHOD(void, addHammerOnPullOffToSelection, (), (override)); MOCK_METHOD(void, addOttavaToSelection, (OttavaType), (override)); MOCK_METHOD(void, addHairpinOnGripDrag, (engraving::EditData&, bool), (override)); MOCK_METHOD(void, addHairpinsToSelection, (HairpinType), (override)); diff --git a/src/notation/view/styledialog/hammeronpullofftappingpagemodel.cpp b/src/notation/view/styledialog/hammeronpullofftappingpagemodel.cpp new file mode 100644 index 0000000000000..b85858c4b4894 --- /dev/null +++ b/src/notation/view/styledialog/hammeronpullofftappingpagemodel.cpp @@ -0,0 +1,53 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2021 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 . + */ +#include "hammeronpullofftappingpagemodel.h" + +using namespace mu::notation; +using namespace mu::engraving; + +HammerOnPullOffTappingPageModel::HammerOnPullOffTappingPageModel(QObject* parent) + : AbstractStyleDialogModel(parent, { StyleId::hopoShowOnStandardStaves, + StyleId::hopoShowOnTabStaves, + StyleId::hopoUpperCase, + StyleId::hopoShowAll }) +{ +} + +StyleItem* HammerOnPullOffTappingPageModel::showOnStandardStaves() const +{ + return styleItem(StyleId::hopoShowOnStandardStaves); +} + +StyleItem* HammerOnPullOffTappingPageModel::showOnTabStaves() const +{ + return styleItem(StyleId::hopoShowOnTabStaves); +} + +StyleItem* HammerOnPullOffTappingPageModel::hopoUpperCase() const +{ + return styleItem(StyleId::hopoUpperCase); +} + +StyleItem* HammerOnPullOffTappingPageModel::hopoShowAll() const +{ + return styleItem(StyleId::hopoShowAll); +} diff --git a/src/notation/view/styledialog/hammeronpullofftappingpagemodel.h b/src/notation/view/styledialog/hammeronpullofftappingpagemodel.h new file mode 100644 index 0000000000000..36eb01143fbf7 --- /dev/null +++ b/src/notation/view/styledialog/hammeronpullofftappingpagemodel.h @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2021 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 . + */ + +#pragma once + +#include "abstractstyledialogmodel.h" + +namespace mu::notation { +class HammerOnPullOffTappingPageModel : public AbstractStyleDialogModel +{ + Q_OBJECT + + Q_PROPERTY(StyleItem * showOnStandardStaves READ showOnStandardStaves CONSTANT) + Q_PROPERTY(StyleItem * showOnTabStaves READ showOnTabStaves CONSTANT) + Q_PROPERTY(StyleItem * hopoUpperCase READ hopoUpperCase CONSTANT) + Q_PROPERTY(StyleItem * hopoShowAll READ hopoShowAll CONSTANT) + +public: + explicit HammerOnPullOffTappingPageModel(QObject* parent = nullptr); + + StyleItem* showOnStandardStaves() const; + StyleItem* showOnTabStaves() const; + StyleItem* hopoUpperCase() const; + StyleItem* hopoShowAll() const; +}; +} diff --git a/src/notation/view/styledialog/styledialog.cmake b/src/notation/view/styledialog/styledialog.cmake index 6bfc437653e04..10436b6c2ea9e 100644 --- a/src/notation/view/styledialog/styledialog.cmake +++ b/src/notation/view/styledialog/styledialog.cmake @@ -24,4 +24,6 @@ set(STYLEDIALOG_SRC ${CMAKE_CURRENT_LIST_DIR}/notelinesectionmodel.h ${CMAKE_CURRENT_LIST_DIR}/clefkeytimesigpagemodel.cpp ${CMAKE_CURRENT_LIST_DIR}/clefkeytimesigpagemodel.h + ${CMAKE_CURRENT_LIST_DIR}/hammeronpullofftappingpagemodel.cpp + ${CMAKE_CURRENT_LIST_DIR}/hammeronpullofftappingpagemodel.h ) diff --git a/src/notation/view/widgets/editstyle.cpp b/src/notation/view/widgets/editstyle.cpp index 43bbc118a4f77..4c5c44326ae69 100644 --- a/src/notation/view/widgets/editstyle.cpp +++ b/src/notation/view/widgets/editstyle.cpp @@ -87,6 +87,7 @@ static const QStringList ALL_PAGE_CODES { "text-line", "system-text-line", "articulations-and-ornaments", + "hammer-ons-pull-offs-and-tapping", "fermatas", "staff-text", "tempo-text", @@ -138,6 +139,7 @@ static const QStringList ALL_TEXT_STYLE_SUBPAGE_CODES { "fingering", "lh-guitar-fingering", "rh-guitar-fingering", + "hammer-ons-pull-offs-and-tapping", "string-number", "string-tunings", "fretboard-diagram-fingering", @@ -939,6 +941,17 @@ EditStyle::EditStyle(QWidget* parent) connect(fretboardsPage.view->rootObject(), SIGNAL(goToTextStylePage(QString)), this, SLOT(goToTextStylePage(QString))); fretboardsWidget->layout()->addWidget(fretboardsPage.widget); + // ==================================================== + // Hammer-on/pull-off and tapping STYLE PAGE (QML) + // ==================================================== + + auto hoposTappingPage = createQmlWidget( + hoposPageWidget, + QUrl(QString::fromUtf8("qrc:/qml/MuseScore/NotationScene/internal/EditStyle/HammerOnPullOffTappingPage.qml"))); + hoposTappingPage.widget->setMinimumSize(224, 400); + connect(hoposTappingPage.view->rootObject(), SIGNAL(goToTextStylePage(QString)), this, SLOT(goToTextStylePage(QString))); + hoposPageWidget->layout()->addWidget(hoposTappingPage.widget); + // ==================================================== // GLISSANDO STYLE SECTION (QML) // ==================================================== @@ -1619,6 +1632,11 @@ QString EditStyle::pageCodeForElement(const EngravingItem* element) case ElementType::PARTIAL_TIE_SEGMENT: return "slurs-and-ties"; + case ElementType::HAMMER_ON_PULL_OFF: + case ElementType::HAMMER_ON_PULL_OFF_SEGMENT: + case ElementType::HAMMER_ON_PULL_OFF_TEXT: + return "hammer-ons-pull-offs-and-tapping"; + case ElementType::HAIRPIN: case ElementType::HAIRPIN_SEGMENT: return "dynamics-hairpins"; diff --git a/src/notation/view/widgets/editstyle.ui b/src/notation/view/widgets/editstyle.ui index 3a47445722431..abaa9f0d5668f 100644 --- a/src/notation/view/widgets/editstyle.ui +++ b/src/notation/view/widgets/editstyle.ui @@ -199,6 +199,11 @@ Articulations & ornaments + + + Hammer-ons, pull-offs & tapping + + Fermatas @@ -11465,6 +11470,15 @@ + + + + + + + + + @@ -14211,8 +14225,8 @@ first note of the system 0 0 - 98 - 28 + 624 + 478 diff --git a/src/notation/view/widgets/timeline.cpp b/src/notation/view/widgets/timeline.cpp index 0fe86a0977fab..74d4eba6b5874 100644 --- a/src/notation/view/widgets/timeline.cpp +++ b/src/notation/view/widgets/timeline.cpp @@ -1966,6 +1966,7 @@ void Timeline::drawSelection() case ElementType::SLUR_SEGMENT: case ElementType::TIE: case ElementType::SLUR: + case ElementType::HAMMER_ON_PULL_OFF: continue; break; default: break; diff --git a/src/palette/internal/palettecreator.cpp b/src/palette/internal/palettecreator.cpp index 05dc7af9f8dcb..7d019f3d9fecf 100644 --- a/src/palette/internal/palettecreator.cpp +++ b/src/palette/internal/palettecreator.cpp @@ -51,6 +51,7 @@ #include "engraving/dom/gradualtempochange.h" #include "engraving/dom/guitarbend.h" #include "engraving/dom/hairpin.h" +#include "engraving/dom/hammeronpulloff.h" #include "engraving/dom/harppedaldiagram.h" #include "engraving/dom/instrchange.h" #include "engraving/dom/jump.h" @@ -1603,8 +1604,8 @@ PalettePtr PaletteCreator::newTextPalette(bool defaultPalette) sp->appendElement(meaNum, QT_TRANSLATE_NOOP("palette", "Measure number"))->setElementTranslated(true); static const std::vector playTechAnnotationsMaster = { - { QT_TRANSLATE_NOOP("palette", "détaché"), PlayingTechniqueType::Detache }, - { QT_TRANSLATE_NOOP("palette", "martelé"), PlayingTechniqueType::Martele }, + { QT_TRANSLATE_NOOP("palette", "détaché"), PlayingTechniqueType::Detache }, + { QT_TRANSLATE_NOOP("palette", "martelé"), PlayingTechniqueType::Martele }, { QT_TRANSLATE_NOOP("palette", "col legno"), PlayingTechniqueType::ColLegno }, { QT_TRANSLATE_NOOP("palette", "sul pont."), PlayingTechniqueType::SulPonticello }, { QT_TRANSLATE_NOOP("palette", "sul tasto"), PlayingTechniqueType::SulTasto }, @@ -1762,7 +1763,7 @@ PalettePtr PaletteCreator::newGuitarPalette(bool defaultPalette) capoLine->setLen(w); capoLine->setBeginText(u"VII"); capoLine->setEndHookType(HookType::HOOK_90); - sp->appendElement(capoLine, QT_TRANSLATE_NOOP("palette", "Barré line"), 0.8); + sp->appendElement(capoLine, QT_TRANSLATE_NOOP("palette", "Barré line"), 0.8); auto pm = makeElement(gpaletteScore); pm->setLen(w); @@ -1826,6 +1827,9 @@ PalettePtr PaletteCreator::newGuitarPalette(bool defaultPalette) sp->appendElement(f, QT_TRANSLATE_NOOP("palette", "String number %1")); } + auto hopo = Factory::makeHammerOnPullOff(gpaletteScore->dummy()); + sp->appendElement(hopo, QT_TRANSLATE_NOOP("palette", "Hammer-on / pull-off"), 0.8); + static const SymIdList luteSymbols { SymId::stringsThumbPosition, SymId::luteFingeringRHThumb, SymId::luteFingeringRHFirst,