From 72eee164f8e224aa25bd10f3470bcc7ed5786a46 Mon Sep 17 00:00:00 2001 From: Steve Bennett Date: Tue, 14 Jul 2026 14:10:19 +1000 Subject: [PATCH 1/5] Add shortcut to adjust dynamics. Fixes #34082 --- src/app/configs/data/shortcuts.xml | 8 +++ src/app/configs/data/shortcuts_mac.xml | 8 +++ src/notation/inotationinteraction.h | 2 + src/notation/internal/notationinteraction.cpp | 55 +++++++++++++++---- src/notation/internal/notationinteraction.h | 3 + .../tests/mocks/notationinteractionmock.h | 1 + .../internal/notationactioncontroller.cpp | 3 + .../internal/notationuiactions.cpp | 12 ++++ 8 files changed, 80 insertions(+), 12 deletions(-) diff --git a/src/app/configs/data/shortcuts.xml b/src/app/configs/data/shortcuts.xml index 962f9345c0352..c9c01b6700bdb 100644 --- a/src/app/configs/data/shortcuts.xml +++ b/src/app/configs/data/shortcuts.xml @@ -600,6 +600,14 @@ add-hairpin-reverse Shift+. + + increase-dynamic + Alt+. + + + decrease-dynamic + Alt+, + time-delete Ctrl+Del diff --git a/src/app/configs/data/shortcuts_mac.xml b/src/app/configs/data/shortcuts_mac.xml index b75fcf42d3264..0c8ecce0ae8ca 100644 --- a/src/app/configs/data/shortcuts_mac.xml +++ b/src/app/configs/data/shortcuts_mac.xml @@ -603,6 +603,14 @@ add-hairpin-reverse Shift+. + + + increase-dynamic + Alt+. + + + decrease-dynamic + Alt+, time-delete diff --git a/src/notation/inotationinteraction.h b/src/notation/inotationinteraction.h index 1f86f5c71ee90..a98f3fbdde1d1 100644 --- a/src/notation/inotationinteraction.h +++ b/src/notation/inotationinteraction.h @@ -224,6 +224,8 @@ class INotationInteraction virtual void autoFlipHairpinsType(engraving::Dynamic* selDyn) = 0; virtual void toggleDynamicPopup() = 0; + virtual void increaseDecreaseDynamicsForSelection(int delta) = 0; + virtual bool toggleLayoutBreakAvailable() const = 0; virtual void toggleLayoutBreak(LayoutBreakType breakType) = 0; virtual void moveMeasureToPrevSystem() = 0; diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index 1e8eadf0b0069..e41fe55536e18 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -5762,6 +5762,33 @@ void NotationInteraction::addHairpinsToSelection(HairpinType type) select({ segment }); startEditGrip(segment, mu::engraving::Grip::END); } +}; + +void NotationInteraction::increaseDecreaseDynamicsForSelection(int delta) +{ + if (selection()->isNone()) { + return; + } + startEdit(TranslatableString("undoableAction", delta > 0 ? "Increase dynamics" : "Decrease dynamics")); + + for (EngravingItem* item : selection()->elements()) { + if (!item->isDynamic()) { + continue; + } + Dynamic* dynamic = toDynamic(item); + DynamicType newType; + if (delta > 0 && dynamic->dynamicType() <= DynamicType::FFFF) { + newType = static_cast(static_cast(dynamic->dynamicType()) + 1); + } else if (delta < 0 && dynamic->dynamicType() >= DynamicType::PPPPP) { + newType = static_cast(static_cast(dynamic->dynamicType()) - 1); + } else { + continue; + } + dynamic->undoChangeProperty(Pid::DYNAMIC_TYPE, newType); + dynamic->undoChangeProperty(Pid::TEXT, Dynamic::dynamicText(newType)); + autoFlipHairpinsTypeImpl(dynamic); + } + apply(); } void NotationInteraction::putRestToSelection() @@ -6026,20 +6053,9 @@ void NotationInteraction::increaseDecreaseDuration(int steps, bool stepByDots) notifyAboutNotationChanged(); } -void NotationInteraction::autoFlipHairpinsType(Dynamic* selDyn) -{ - if (!selDyn) { - return; - } - - if (selDyn->dynamicType() == DynamicType::OTHER || selDyn->dynamicType() >= DynamicType::FP) { - return; - } - +void NotationInteraction::autoFlipHairpinsTypeImpl(Dynamic* selDyn) { selDyn->findAdjacentHairpins(); - startEdit(TranslatableString("undoableAction", "Change hairpin type")); - if (Hairpin* leftHp = selDyn->leftHairpin()) { const Dynamic* startDyn = leftHp->dynamicSnappedBefore(); if (startDyn @@ -6066,6 +6082,21 @@ void NotationInteraction::autoFlipHairpinsType(Dynamic* selDyn) } } +} + +void NotationInteraction::autoFlipHairpinsType(Dynamic* selDyn) +{ + if (!selDyn) { + return; + } + + if (selDyn->dynamicType() == DynamicType::OTHER || selDyn->dynamicType() >= DynamicType::FP) { + return; + } + + startEdit(TranslatableString("undoableAction", "Change hairpin type")); + autoFlipHairpinsTypeImpl(selDyn); + apply(); } diff --git a/src/notation/internal/notationinteraction.h b/src/notation/internal/notationinteraction.h index 796b6dcb42937..d9cb651c3220c 100644 --- a/src/notation/internal/notationinteraction.h +++ b/src/notation/internal/notationinteraction.h @@ -239,8 +239,11 @@ class NotationInteraction : public INotationInteraction, public muse::Contextabl void increaseDecreaseDuration(int steps, bool stepByDots) override; void autoFlipHairpinsType(engraving::Dynamic* selDyn) override; + void autoFlipHairpinsTypeImpl(engraving::Dynamic* selDyn); void toggleDynamicPopup() override; + void increaseDecreaseDynamicsForSelection(int delta) override; + bool toggleLayoutBreakAvailable() const override; void toggleLayoutBreak(LayoutBreakType breakType) override; void moveMeasureToPrevSystem() override; diff --git a/src/notation/tests/mocks/notationinteractionmock.h b/src/notation/tests/mocks/notationinteractionmock.h index 8269962de6366..09ac25cfe2a61 100644 --- a/src/notation/tests/mocks/notationinteractionmock.h +++ b/src/notation/tests/mocks/notationinteractionmock.h @@ -185,6 +185,7 @@ class NotationInteractionMock : public INotationInteraction MOCK_METHOD(void, autoFlipHairpinsType, (engraving::Dynamic * selDyn), (override)); MOCK_METHOD(void, toggleDynamicPopup, (), (override)); + MOCK_METHOD(void, increaseDecreaseDynamicsForSelection, (int), (override)); MOCK_METHOD(bool, toggleLayoutBreakAvailable, (), (const, override)); MOCK_METHOD(void, toggleLayoutBreak, (LayoutBreakType), (override)); MOCK_METHOD(void, moveMeasureToPrevSystem, (), (override)); diff --git a/src/notationscene/internal/notationactioncontroller.cpp b/src/notationscene/internal/notationactioncontroller.cpp index 19b59d361cb10..8a811e1d328b2 100644 --- a/src/notationscene/internal/notationactioncontroller.cpp +++ b/src/notationscene/internal/notationactioncontroller.cpp @@ -341,6 +341,9 @@ void NotationActionController::init() registerAction("add-hairpin", &Interaction::addHairpinsToSelection, HairpinType::CRESC_HAIRPIN, &Controller::noteOrRestSelected); registerAction("add-hairpin-reverse", &Interaction::addHairpinsToSelection, HairpinType::DIM_HAIRPIN, &Controller::noteOrRestSelected); + registerAction("increase-dynamic", [this]() { currentNotationInteraction()->increaseDecreaseDynamicsForSelection(1); }); + registerAction("decrease-dynamic", [this]() { currentNotationInteraction()->increaseDecreaseDynamicsForSelection(-1); }); + registerAction("add-noteline", &Interaction::addAnchoredLineToSelectedNotes); registerAction("add-image", [this]() { addImage(); }); diff --git a/src/notationscene/internal/notationuiactions.cpp b/src/notationscene/internal/notationuiactions.cpp index c808570d547d2..f443c108dd4de 100644 --- a/src/notationscene/internal/notationuiactions.cpp +++ b/src/notationscene/internal/notationuiactions.cpp @@ -1263,6 +1263,18 @@ const UiActionList NotationUiActions::s_actions = { TranslatableString("action", "&Diminuendo"), TranslatableString("action", "Add hairpin: diminuendo") ), + UiAction("increase-dynamic", + mu::context::UiCtxProjectOpened, + mu::context::CTX_NOTATION_OPENED, + TranslatableString("action", "Increase dynamics"), + TranslatableString("action", "Increase dynamics by one step") + ), + UiAction("decrease-dynamic", + mu::context::UiCtxProjectOpened, + mu::context::CTX_NOTATION_OPENED, + TranslatableString("action", "Decrease dynamics"), + TranslatableString("action", "Decrease dynamics by one step") + ), UiAction("add-noteline", mu::context::UiCtxProjectOpened, mu::context::CTX_NOTATION_OPENED, From dd3020124dc6e53d4bb788b10cba57ac46ef326d Mon Sep 17 00:00:00 2001 From: Steve Bennett Date: Tue, 14 Jul 2026 16:06:12 +1000 Subject: [PATCH 2/5] Make dynamic altering more robust. --- src/notation/internal/notationinteraction.cpp | 16 ++++++++++------ .../internal/notationactioncontroller.cpp | 5 +++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index e41fe55536e18..e61ac17267ffa 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -5776,14 +5776,18 @@ void NotationInteraction::increaseDecreaseDynamicsForSelection(int delta) continue; } Dynamic* dynamic = toDynamic(item); - DynamicType newType; - if (delta > 0 && dynamic->dynamicType() <= DynamicType::FFFF) { - newType = static_cast(static_cast(dynamic->dynamicType()) + 1); - } else if (delta < 0 && dynamic->dynamicType() >= DynamicType::PPPPP) { - newType = static_cast(static_cast(dynamic->dynamicType()) - 1); - } else { + const DynamicType currentType = dynamic->dynamicType(); + auto inRange = [](DynamicType type) { + return type >= DynamicType::PPPPP && type <= DynamicType::FFFFF; + }; + if (!inRange(currentType)) { continue; } + DynamicType newType = static_cast(static_cast(currentType) + delta); + if (!inRange(newType)) { + continue; + } + dynamic->undoChangeProperty(Pid::DYNAMIC_TYPE, newType); dynamic->undoChangeProperty(Pid::TEXT, Dynamic::dynamicText(newType)); autoFlipHairpinsTypeImpl(dynamic); diff --git a/src/notationscene/internal/notationactioncontroller.cpp b/src/notationscene/internal/notationactioncontroller.cpp index 8a811e1d328b2..93964831bef90 100644 --- a/src/notationscene/internal/notationactioncontroller.cpp +++ b/src/notationscene/internal/notationactioncontroller.cpp @@ -341,8 +341,9 @@ void NotationActionController::init() registerAction("add-hairpin", &Interaction::addHairpinsToSelection, HairpinType::CRESC_HAIRPIN, &Controller::noteOrRestSelected); registerAction("add-hairpin-reverse", &Interaction::addHairpinsToSelection, HairpinType::DIM_HAIRPIN, &Controller::noteOrRestSelected); - registerAction("increase-dynamic", [this]() { currentNotationInteraction()->increaseDecreaseDynamicsForSelection(1); }); - registerAction("decrease-dynamic", [this]() { currentNotationInteraction()->increaseDecreaseDynamicsForSelection(-1); }); + + registerAction("increase-dynamic", [this]() { currentNotationInteraction()->increaseDecreaseDynamicsForSelection(1); }, &Controller::hasSelection); + registerAction("decrease-dynamic", [this]() { currentNotationInteraction()->increaseDecreaseDynamicsForSelection(-1); }, &Controller::hasSelection); registerAction("add-noteline", &Interaction::addAnchoredLineToSelectedNotes); From 6bb05ce13d80d076b3d2a205fbcb1ac4a98a233d Mon Sep 17 00:00:00 2001 From: Steve Bennett Date: Tue, 14 Jul 2026 17:26:25 +1000 Subject: [PATCH 3/5] code style --- .../internal/notationactioncontroller.cpp | 10 ++++++++-- .../internal/notationuiactions.cpp | 20 +++++++++---------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/notationscene/internal/notationactioncontroller.cpp b/src/notationscene/internal/notationactioncontroller.cpp index 93964831bef90..0caf036271990 100644 --- a/src/notationscene/internal/notationactioncontroller.cpp +++ b/src/notationscene/internal/notationactioncontroller.cpp @@ -342,8 +342,14 @@ void NotationActionController::init() registerAction("add-hairpin-reverse", &Interaction::addHairpinsToSelection, HairpinType::DIM_HAIRPIN, &Controller::noteOrRestSelected); - registerAction("increase-dynamic", [this]() { currentNotationInteraction()->increaseDecreaseDynamicsForSelection(1); }, &Controller::hasSelection); - registerAction("decrease-dynamic", [this]() { currentNotationInteraction()->increaseDecreaseDynamicsForSelection(-1); }, &Controller::hasSelection); + registerAction("increase-dynamic", [this]() { + currentNotationInteraction()->increaseDecreaseDynamicsForSelection( + 1); + }, &Controller::hasSelection); + registerAction("decrease-dynamic", [this]() { + currentNotationInteraction()->increaseDecreaseDynamicsForSelection( + -1); + }, &Controller::hasSelection); registerAction("add-noteline", &Interaction::addAnchoredLineToSelectedNotes); diff --git a/src/notationscene/internal/notationuiactions.cpp b/src/notationscene/internal/notationuiactions.cpp index f443c108dd4de..2ff5596dba839 100644 --- a/src/notationscene/internal/notationuiactions.cpp +++ b/src/notationscene/internal/notationuiactions.cpp @@ -1264,17 +1264,17 @@ const UiActionList NotationUiActions::s_actions = { TranslatableString("action", "Add hairpin: diminuendo") ), UiAction("increase-dynamic", - mu::context::UiCtxProjectOpened, - mu::context::CTX_NOTATION_OPENED, - TranslatableString("action", "Increase dynamics"), - TranslatableString("action", "Increase dynamics by one step") - ), + mu::context::UiCtxProjectOpened, + mu::context::CTX_NOTATION_OPENED, + TranslatableString("action", "Increase dynamics"), + TranslatableString("action", "Increase dynamics by one step") + ), UiAction("decrease-dynamic", - mu::context::UiCtxProjectOpened, - mu::context::CTX_NOTATION_OPENED, - TranslatableString("action", "Decrease dynamics"), - TranslatableString("action", "Decrease dynamics by one step") - ), + mu::context::UiCtxProjectOpened, + mu::context::CTX_NOTATION_OPENED, + TranslatableString("action", "Decrease dynamics"), + TranslatableString("action", "Decrease dynamics by one step") + ), UiAction("add-noteline", mu::context::UiCtxProjectOpened, mu::context::CTX_NOTATION_OPENED, From ca090dd353cdcefce9bcc68edc41a07d8d7ad8af Mon Sep 17 00:00:00 2001 From: Steve Bennett Date: Tue, 14 Jul 2026 17:47:02 +1000 Subject: [PATCH 4/5] code style --- src/notation/internal/notationinteraction.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index e61ac17267ffa..66bb4feb61651 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -5762,7 +5762,7 @@ void NotationInteraction::addHairpinsToSelection(HairpinType type) select({ segment }); startEditGrip(segment, mu::engraving::Grip::END); } -}; +} void NotationInteraction::increaseDecreaseDynamicsForSelection(int delta) { @@ -6057,7 +6057,8 @@ void NotationInteraction::increaseDecreaseDuration(int steps, bool stepByDots) notifyAboutNotationChanged(); } -void NotationInteraction::autoFlipHairpinsTypeImpl(Dynamic* selDyn) { +void NotationInteraction::autoFlipHairpinsTypeImpl(Dynamic* selDyn) +{ selDyn->findAdjacentHairpins(); if (Hairpin* leftHp = selDyn->leftHairpin()) { @@ -6085,7 +6086,6 @@ void NotationInteraction::autoFlipHairpinsTypeImpl(Dynamic* selDyn) { } } } - } void NotationInteraction::autoFlipHairpinsType(Dynamic* selDyn) From d1b2f6e8d243e7ffe25c59b79bdc283cde4c9a98 Mon Sep 17 00:00:00 2001 From: Steve Bennett Date: Wed, 15 Jul 2026 14:26:26 +1000 Subject: [PATCH 5/5] Support tweaking dynamic by selecting note --- src/notation/inotationinteraction.h | 1 + src/notation/internal/notationinteraction.cpp | 79 +++++++++++++++---- src/notation/internal/notationinteraction.h | 1 + .../internal/notationuiactions.cpp | 4 +- 4 files changed, 66 insertions(+), 19 deletions(-) diff --git a/src/notation/inotationinteraction.h b/src/notation/inotationinteraction.h index a98f3fbdde1d1..3c478b2d5db82 100644 --- a/src/notation/inotationinteraction.h +++ b/src/notation/inotationinteraction.h @@ -224,6 +224,7 @@ class INotationInteraction virtual void autoFlipHairpinsType(engraving::Dynamic* selDyn) = 0; virtual void toggleDynamicPopup() = 0; + virtual void increaseDecreaseDynamic(engraving::Dynamic* dynamic, int delta) = 0; virtual void increaseDecreaseDynamicsForSelection(int delta) = 0; virtual bool toggleLayoutBreakAvailable() const = 0; diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index 66bb4feb61651..767a9a40eeddd 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -5764,33 +5764,78 @@ void NotationInteraction::addHairpinsToSelection(HairpinType type) } } -void NotationInteraction::increaseDecreaseDynamicsForSelection(int delta) +void NotationInteraction::increaseDecreaseDynamic(Dynamic* dynamic, int delta) { - if (selection()->isNone()) { + const DynamicType currentType = dynamic->dynamicType(); + auto inRange = [](DynamicType type) { + return type >= DynamicType::PPPPP && type <= DynamicType::FFFFF; + }; + if (!inRange(currentType)) { + return; + } + DynamicType newType = static_cast(static_cast(currentType) + delta); + if (!inRange(newType)) { return; } - startEdit(TranslatableString("undoableAction", delta > 0 ? "Increase dynamics" : "Decrease dynamics")); - for (EngravingItem* item : selection()->elements()) { + dynamic->undoChangeProperty(Pid::DYNAMIC_TYPE, newType); + dynamic->undoChangeProperty(Pid::TEXT, Dynamic::dynamicText(newType)); + autoFlipHairpinsTypeImpl(dynamic); +} + +std::vector findDynamicsForElement(const EngravingItem* element) +{ + const ChordRest* cr = nullptr; + + if (element->isChordRest()) { + cr = toChordRest(element); + } else if (element->isNote()) { + auto parent = element->parent(); + if (parent && parent->isChordRest()) { + cr = toChordRest(parent); + } + } + + if (!cr) { + return {}; + } + + Segment* segment = cr->segment(); + + std::vector dynamics; + for (EngravingItem* item : segment->annotations()) { if (!item->isDynamic()) { continue; } + Dynamic* dynamic = toDynamic(item); - const DynamicType currentType = dynamic->dynamicType(); - auto inRange = [](DynamicType type) { - return type >= DynamicType::PPPPP && type <= DynamicType::FFFFF; - }; - if (!inRange(currentType)) { - continue; - } - DynamicType newType = static_cast(static_cast(currentType) + delta); - if (!inRange(newType)) { - continue; + if (dynamic->elementAppliesToTrack(cr->track()) + && (dynamic->staffIdx() == cr->staffIdx() || dynamic->appliesToAllVoicesInInstrument())) { + dynamics.push_back(dynamic); } + } + + return dynamics; +} - dynamic->undoChangeProperty(Pid::DYNAMIC_TYPE, newType); - dynamic->undoChangeProperty(Pid::TEXT, Dynamic::dynamicText(newType)); - autoFlipHairpinsTypeImpl(dynamic); +void NotationInteraction::increaseDecreaseDynamicsForSelection(int delta) +{ + if (selection()->isNone()) { + return; + } + startEdit(TranslatableString("undoableAction", delta > 0 ? "Increase dynamics" : "Decrease dynamics")); + if (selection()->element() && !selection()->element()->isDynamic()) { + std::vector dynamics = findDynamicsForElement(selection()->element()); + for (Dynamic* dynamic : dynamics) { + increaseDecreaseDynamic(dynamic, delta); + } + } else { + for (EngravingItem* item : selection()->elements()) { + if (!item->isDynamic()) { + continue; + } + increaseDecreaseDynamic(toDynamic(item), delta); + } } apply(); } diff --git a/src/notation/internal/notationinteraction.h b/src/notation/internal/notationinteraction.h index d9cb651c3220c..6c258948cca4c 100644 --- a/src/notation/internal/notationinteraction.h +++ b/src/notation/internal/notationinteraction.h @@ -242,6 +242,7 @@ class NotationInteraction : public INotationInteraction, public muse::Contextabl void autoFlipHairpinsTypeImpl(engraving::Dynamic* selDyn); void toggleDynamicPopup() override; + void increaseDecreaseDynamic(engraving::Dynamic* dynamic, int delta) override; void increaseDecreaseDynamicsForSelection(int delta) override; bool toggleLayoutBreakAvailable() const override; diff --git a/src/notationscene/internal/notationuiactions.cpp b/src/notationscene/internal/notationuiactions.cpp index 2ff5596dba839..728694c40b7ae 100644 --- a/src/notationscene/internal/notationuiactions.cpp +++ b/src/notationscene/internal/notationuiactions.cpp @@ -1267,13 +1267,13 @@ const UiActionList NotationUiActions::s_actions = { mu::context::UiCtxProjectOpened, mu::context::CTX_NOTATION_OPENED, TranslatableString("action", "Increase dynamics"), - TranslatableString("action", "Increase dynamics by one step") + TranslatableString("action", "Increase selected dynamics") ), UiAction("decrease-dynamic", mu::context::UiCtxProjectOpened, mu::context::CTX_NOTATION_OPENED, TranslatableString("action", "Decrease dynamics"), - TranslatableString("action", "Decrease dynamics by one step") + TranslatableString("action", "Decrease selected dynamics") ), UiAction("add-noteline", mu::context::UiCtxProjectOpened,