From 71a465aa93e14e04cf725edfb978b7a18d524f07 Mon Sep 17 00:00:00 2001 From: re1san Date: Thu, 22 Aug 2024 17:49:54 +0530 Subject: [PATCH] Add shortcut functionality and automatic switching of hairpin type --- src/app/configs/data/shortcuts.xml | 4 + src/app/configs/data/shortcuts_azerty.xml | 4 + src/app/configs/data/shortcuts_mac.xml | 4 + src/engraving/dom/edit.cpp | 9 ++ src/engraving/dom/hairpin.cpp | 30 ++++++ src/engraving/dom/hairpin.h | 3 + src/notation/inotationinteraction.h | 3 + .../internal/notationactioncontroller.cpp | 1 + src/notation/internal/notationinteraction.cpp | 94 +++++++++++++++++++ src/notation/internal/notationinteraction.h | 3 + src/notation/internal/notationuiactions.cpp | 6 ++ .../tests/mocks/notationinteractionmock.h | 3 + .../view/internal/dynamicpopupmodel.cpp | 10 ++ .../view/notationviewinputcontroller.cpp | 5 + 14 files changed, 179 insertions(+) diff --git a/src/app/configs/data/shortcuts.xml b/src/app/configs/data/shortcuts.xml index 7aff6538cc952..a89e31ad02982 100644 --- a/src/app/configs/data/shortcuts.xml +++ b/src/app/configs/data/shortcuts.xml @@ -533,6 +533,10 @@ add-slur S + + add-dynamic + Ctrl+D + add-hairpin Shift+, diff --git a/src/app/configs/data/shortcuts_azerty.xml b/src/app/configs/data/shortcuts_azerty.xml index d841a82891c63..b2dd48999efb2 100644 --- a/src/app/configs/data/shortcuts_azerty.xml +++ b/src/app/configs/data/shortcuts_azerty.xml @@ -542,6 +542,10 @@ add-slur S + + add-dynamic + Ctrl+D + add-hairpin Shift+, diff --git a/src/app/configs/data/shortcuts_mac.xml b/src/app/configs/data/shortcuts_mac.xml index b9df7baef3024..d5e7b2e8c59c6 100644 --- a/src/app/configs/data/shortcuts_mac.xml +++ b/src/app/configs/data/shortcuts_mac.xml @@ -533,6 +533,10 @@ add-slur S + + add-dynamic + Ctrl+D + add-hairpin Shift+, diff --git a/src/engraving/dom/edit.cpp b/src/engraving/dom/edit.cpp index a003c1ba0e6f7..1f61ce8785c5f 100644 --- a/src/engraving/dom/edit.cpp +++ b/src/engraving/dom/edit.cpp @@ -766,6 +766,15 @@ TextBase* Score::addText(TextStyleType type, EngravingItem* destinationElement) chordRest->undoAddAnnotation(textBox); break; } + case TextStyleType::DYNAMICS: { + ChordRest* chordRest = chordOrRest(destinationElement); + if (!chordRest) { + break; + } + textBox = Factory::createDynamic(dummy()->segment()); + chordRest->undoAddAnnotation(textBox); + break; + } case TextStyleType::INSTRUMENT_CHANGE: { ChordRest* chordRest = chordOrRest(destinationElement); if (!chordRest) { diff --git a/src/engraving/dom/hairpin.cpp b/src/engraving/dom/hairpin.cpp index b4f2280c2c30d..b48b4786c3aea 100644 --- a/src/engraving/dom/hairpin.cpp +++ b/src/engraving/dom/hairpin.cpp @@ -500,6 +500,36 @@ DynamicType Hairpin::dynamicTypeTo() const return TConv::dynamicType(ba.constChar()); } +const Dynamic* Hairpin::dynamicSnappedBefore() const +{ + const LineSegment* seg = frontSegment(); + if (!seg) { + return nullptr; + } + + const EngravingItem* item = seg->ldata()->itemSnappedBefore(); + if (!item || !item->isDynamic()) { + return nullptr; + } + + return toDynamic(item); +} + +const Dynamic* Hairpin::dynamicSnappedAfter() const +{ + const LineSegment* seg = backSegment(); + if (!seg) { + return nullptr; + } + + const EngravingItem* item = seg->ldata()->itemSnappedAfter(); + if (!item || !item->isDynamic()) { + return nullptr; + } + + return toDynamic(item); +} + //--------------------------------------------------------- // setHairpinType //--------------------------------------------------------- diff --git a/src/engraving/dom/hairpin.h b/src/engraving/dom/hairpin.h index 0da64c38287e2..80eff18ad77fa 100644 --- a/src/engraving/dom/hairpin.h +++ b/src/engraving/dom/hairpin.h @@ -113,6 +113,9 @@ class Hairpin final : public TextLineBase DynamicType dynamicTypeFrom() const; DynamicType dynamicTypeTo() const; + const Dynamic* dynamicSnappedBefore() const; + const Dynamic* dynamicSnappedAfter() const; + HairpinType hairpinType() const { return m_hairpinType; } void setHairpinType(HairpinType val); diff --git a/src/notation/inotationinteraction.h b/src/notation/inotationinteraction.h index 0eaa07bbb256c..6422c77554795 100644 --- a/src/notation/inotationinteraction.h +++ b/src/notation/inotationinteraction.h @@ -197,6 +197,9 @@ class INotationInteraction virtual void increaseDecreaseDuration(int steps, bool stepByDots) = 0; + virtual void flipHairpinsType(engraving::Dynamic* selDyn) = 0; + + virtual void toggleDynamicPopup() = 0; virtual bool toggleLayoutBreakAvailable() const = 0; virtual void toggleLayoutBreak(LayoutBreakType breakType) = 0; diff --git a/src/notation/internal/notationactioncontroller.cpp b/src/notation/internal/notationactioncontroller.cpp index facb60fdff728..7321b7d9cceed 100644 --- a/src/notation/internal/notationactioncontroller.cpp +++ b/src/notation/internal/notationactioncontroller.cpp @@ -308,6 +308,7 @@ void NotationActionController::init() registerAction("add-8va", &Interaction::addOttavaToSelection, OttavaType::OTTAVA_8VA); registerAction("add-8vb", &Interaction::addOttavaToSelection, OttavaType::OTTAVA_8VB); + registerAction("add-dynamic", &Interaction::toggleDynamicPopup, &Controller::noteOrRestSelected); registerAction("add-hairpin", &Interaction::addHairpinsToSelection, HairpinType::CRESC_HAIRPIN, &Controller::noteOrRestSelected); registerAction("add-hairpin-reverse", &Interaction::addHairpinsToSelection, HairpinType::DECRESC_HAIRPIN, &Controller::noteOrRestSelected); diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index 09d77fac342d3..70ca48cfc4450 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -4416,6 +4416,100 @@ void NotationInteraction::increaseDecreaseDuration(int steps, bool stepByDots) apply(); } +void NotationInteraction::flipHairpinsType(Dynamic* selDyn) +{ + if (!selDyn) { + return; + } + + if (selDyn->dynamicType() == DynamicType::OTHER || selDyn->dynamicType() >= DynamicType::FP) { + return; + } + + selDyn->findAdjacentHairpins(); + + startEdit(); + + if (selDyn->leftHairpin()) { + Hairpin* leftHp = selDyn->leftHairpin(); + const Dynamic* startDyn = leftHp->dynamicSnappedBefore(); + + if (!(startDyn->dynamicType() == DynamicType::OTHER || startDyn->dynamicType() >= DynamicType::FP) && !leftHp->isLineType()) { + if (int(startDyn->dynamicType()) > int(selDyn->dynamicType())) { + leftHp->undoChangeProperty(Pid::HAIRPIN_TYPE, int(HairpinType::DECRESC_HAIRPIN)); + } else { + leftHp->undoChangeProperty(Pid::HAIRPIN_TYPE, int(HairpinType::CRESC_HAIRPIN)); + } + } + } + + if (selDyn->rightHairpin()) { + Hairpin* rightHp = selDyn->rightHairpin(); + const Dynamic* endDyn = rightHp->dynamicSnappedAfter(); + + if (!(endDyn->dynamicType() == DynamicType::OTHER || endDyn->dynamicType() >= DynamicType::FP) && !rightHp->isLineType()) { + if (int(endDyn->dynamicType()) > int(selDyn->dynamicType())) { + rightHp->undoChangeProperty(Pid::HAIRPIN_TYPE, int(HairpinType::CRESC_HAIRPIN)); + } else { + rightHp->undoChangeProperty(Pid::HAIRPIN_TYPE, int(HairpinType::DECRESC_HAIRPIN)); + } + } + } + + apply(); +} + +void NotationInteraction::toggleDynamicPopup() +{ + if (selection()->isNone()) { + return; + } + + // If multiple selected selection()->element() returns null + if (!selection()->element()) { + return; + } + + EngravingItem* el = selection()->element(); + + if (el->isHairpinSegment()) { + HairpinSegment* hairpinSeg = toHairpinSegment(el); + + switch (m_editData.curGrip) { + case Grip::START: { + EngravingItem* startDynOrExp = hairpinSeg->findElementToSnapBefore(); + if (startDynOrExp != nullptr) { + select({ startDynOrExp }); // If there is already a dynamic select it instead of opening an empty popup + if (startDynOrExp->isDynamic()) { + startEditElement(startDynOrExp, false); + flipHairpinsType(toDynamic(startDynOrExp)); + } + } else { + addTextToItem(TextStyleType::DYNAMICS, hairpinSeg->spanner()->startCR()); + } + } + return; + case Grip::END: { + EngravingItem* endDynOrExp = hairpinSeg->findElementToSnapAfter(); + if (endDynOrExp != nullptr) { + select({ endDynOrExp }); // If there is already a dynamic select it instead of opening an empty popup + if (endDynOrExp->isDynamic()) { + startEditElement(endDynOrExp, false); + flipHairpinsType(toDynamic(endDynOrExp)); + } + } else { + addTextToItem(TextStyleType::DYNAMICS, hairpinSeg->spanner()->endCR()); + } + } + return; + default: + return; + } + } else { + addTextToItem(TextStyleType::DYNAMICS, el); + } +} + bool NotationInteraction::toggleLayoutBreakAvailable() const { return !selection()->isNone() && !isTextEditingStarted(); diff --git a/src/notation/internal/notationinteraction.h b/src/notation/internal/notationinteraction.h index 5d695dd93d258..98efbf685b82e 100644 --- a/src/notation/internal/notationinteraction.h +++ b/src/notation/internal/notationinteraction.h @@ -200,6 +200,9 @@ class NotationInteraction : public INotationInteraction, public muse::Injectable void increaseDecreaseDuration(int steps, bool stepByDots) override; + void flipHairpinsType(engraving::Dynamic* selDyn) override; + + void toggleDynamicPopup() override; bool toggleLayoutBreakAvailable() const override; void toggleLayoutBreak(LayoutBreakType breakType) override; diff --git a/src/notation/internal/notationuiactions.cpp b/src/notation/internal/notationuiactions.cpp index 90d1213fb7750..113461509f333 100644 --- a/src/notation/internal/notationuiactions.cpp +++ b/src/notation/internal/notationuiactions.cpp @@ -1193,6 +1193,12 @@ const UiActionList NotationUiActions::m_actions = { TranslatableString("action", "Ottava 8va &bassa"), TranslatableString("action", "Add ottava 8va bassa") ), + UiAction("add-dynamic", + mu::context::UiCtxNotationOpened, + mu::context::CTX_ANY, + TranslatableString("action", "&Dynamic"), + TranslatableString("action", "Add dynamic") + ), UiAction("add-hairpin", mu::context::UiCtxNotationOpened, mu::context::CTX_ANY, diff --git a/src/notation/tests/mocks/notationinteractionmock.h b/src/notation/tests/mocks/notationinteractionmock.h index f65f7c6d52aab..e051257a2f7f5 100644 --- a/src/notation/tests/mocks/notationinteractionmock.h +++ b/src/notation/tests/mocks/notationinteractionmock.h @@ -159,6 +159,9 @@ class NotationInteractionMock : public INotationInteraction MOCK_METHOD(void, increaseDecreaseDuration, (int, bool), (override)); + MOCK_METHOD(void, flipHairpinsType, (engraving::Dynamic * selDyn), (override)); + + MOCK_METHOD(void, toggleDynamicPopup, (), (override)); MOCK_METHOD(bool, toggleLayoutBreakAvailable, (), (const, override)); MOCK_METHOD(void, toggleLayoutBreak, (LayoutBreakType), (override)); diff --git a/src/notation/view/internal/dynamicpopupmodel.cpp b/src/notation/view/internal/dynamicpopupmodel.cpp index 25ad767dd88b9..787777305f876 100644 --- a/src/notation/view/internal/dynamicpopupmodel.cpp +++ b/src/notation/view/internal/dynamicpopupmodel.cpp @@ -158,6 +158,16 @@ void DynamicPopupModel::addOrChangeDynamic(int page, int index) m_item->undoChangeProperty(Pid::DYNAMIC_TYPE, DYN_POPUP_PAGES[page][index].dynType); endCommand(); + INotationInteractionPtr interaction = currentNotation()->interaction(); + + interaction->flipHairpinsType(toDynamic(m_item)); + + // Hide the bounding box which appears when called using Ctrl+D shortcut + if (interaction->isTextEditingStarted()) { + interaction->endEditText(); + interaction->startEditGrip(m_item, Grip::DRAG); + } + updateNotation(); } diff --git a/src/notation/view/notationviewinputcontroller.cpp b/src/notation/view/notationviewinputcontroller.cpp index fc480b5403a16..db9dfc84e25b6 100644 --- a/src/notation/view/notationviewinputcontroller.cpp +++ b/src/notation/view/notationviewinputcontroller.cpp @@ -877,6 +877,11 @@ void NotationViewInputController::mouseReleaseEvent(QMouseEvent* event) if (interaction->isDragStarted()) { interaction->endDrag(); + // When dragging of hairpin ends on a note or rest, open dynamic popup + // Check for note or rest happens in Score::addText which is called through addTextToItem in toggleDynamicPopup + if (interaction->selection()->element()->isHairpinSegment()) { + interaction->toggleDynamicPopup(); + } } if (interaction->isDragCopyStarted()) {