From b70d58c179d5ee2f4a4bc7fad351af83dab1759a Mon Sep 17 00:00:00 2001 From: Igor Korsukov Date: Fri, 17 Jul 2026 13:20:12 +0200 Subject: [PATCH] added notation selection target --- muse | 2 +- src/notation/inotationinteraction.h | 34 ++- src/notation/internal/notationinteraction.cpp | 205 +++++++++++++- src/notation/internal/notationinteraction.h | 34 ++- src/notation/notationtypes.h | 79 +++++- .../tests/mocks/notationinteractionmock.h | 1 + .../internal/notationactioncontroller.cpp | 264 +++++++----------- .../internal/notationactioncontroller.h | 19 +- .../internal/notationcommandsregister.cpp | 70 ++++- .../internal/notationcommandsstate.cpp | 12 +- src/notationscene/notationcommands.h | 19 +- 11 files changed, 515 insertions(+), 224 deletions(-) diff --git a/muse b/muse index 5ded6c34e9fb3..d4bd027592dce 160000 --- a/muse +++ b/muse @@ -1 +1 @@ -Subproject commit 5ded6c34e9fb362cf9307ba5415d89188612b327 +Subproject commit d4bd027592dce0e180ca8a0f3c9a3817bfd74e1c diff --git a/src/notation/inotationinteraction.h b/src/notation/inotationinteraction.h index 72177d46dcbce..5148c57fb6826 100644 --- a/src/notation/inotationinteraction.h +++ b/src/notation/inotationinteraction.h @@ -84,20 +84,35 @@ class INotationInteraction virtual void setHitElementContext(const HitElementContext& context) = 0; // Select + virtual INotationSelectionPtr selection() const = 0; + virtual muse::async::Notification selectionChanged() const = 0; + virtual void select(const std::vector& elements, SelectType type = SelectType::REPLACE, staff_idx_t staffIndex = 0) = 0; + virtual void select(SelectionTarget target) = 0; + virtual void moveChordNoteSelection(MoveDirection d) = 0; - virtual void select(const std::vector& elements, SelectType type = SelectType::REPLACE, - engraving::staff_idx_t staffIndex = 0) = 0; + virtual void selectAndStartEditIfNeeded(EngravingItem* element) = 0; virtual void selectAll() = 0; virtual void selectSection() = 0; virtual void selectFirstElement(bool frame = true) = 0; virtual void selectLastElement() = 0; - virtual INotationSelectionPtr selection() const = 0; + virtual void clearSelection() = 0; - virtual muse::async::Notification selectionChanged() const = 0; + virtual void selectTopOrBottomOfChord(MoveDirection d) = 0; virtual void findAndSelectChordRest(const Fraction& tick) = 0; + // Change selection + virtual bool moveSelectionAvailable(MoveSelectionType type) const = 0; + virtual void moveSelection(MoveDirection d, MoveSelectionType type) = 0; + + virtual void moveLyrics(MoveDirection d) = 0; + virtual void expandSelection(ExpandSelectionMode mode) = 0; + virtual void addToSelection(MoveDirection d, MoveSelectionType type) = 0; + virtual void selectTopStaff() = 0; + virtual void selectEmptyTrailingMeasure() = 0; + virtual void moveSegmentSelection(MoveDirection d) = 0; + virtual EngravingItem* contextItem() const = 0; // SelectionFilter @@ -138,17 +153,6 @@ class INotationInteraction virtual void redo() = 0; virtual void undoRedoToIndex(size_t idx) = 0; - // Change selection - virtual bool moveSelectionAvailable(MoveSelectionType type) const = 0; - virtual void moveSelection(MoveDirection d, MoveSelectionType type) = 0; - - virtual void moveLyrics(MoveDirection d) = 0; - virtual void expandSelection(ExpandSelectionMode mode) = 0; - virtual void addToSelection(MoveDirection d, MoveSelectionType type) = 0; - virtual void selectTopStaff() = 0; - virtual void selectEmptyTrailingMeasure() = 0; - virtual void moveSegmentSelection(MoveDirection d) = 0; - // Move/nudge elements virtual void movePitch(MoveDirection d, PitchMode mode) = 0; virtual void nudge(MoveDirection d, bool quickly) = 0; diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index e3752cbe675c6..5119df7629332 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -136,6 +136,7 @@ #include "notationselection.h" #include "notationselectionfilter.h" #include "scorecallbacks.h" +#include "inotationelements.h" #include "utilities/scorerangeutilities.h" @@ -948,6 +949,128 @@ void NotationInteraction::moveSegmentSelection(MoveDirection d) showItem(e); } +FilterElementsOptions NotationInteraction::elementsFilterOptions(const EngravingItem* element) const +{ + TRACEFUNC; + FilterElementsOptions options; + options.elementType = element->type(); + + if (element->isNote()) { + const mu::engraving::Note* note = dynamic_cast(element); + if (note->chord()->isGrace()) { + options.subtype = -1; + } else { + options.subtype = element->subtype(); + } + } else if (element->isHairpinSegment() || element->isHarmony()) { + options.subtype = element->subtype(); + options.bySubtype = true; + } + + return options; +} + +void NotationInteraction::selectAllSimilarElements() +{ + TRACEFUNC; + auto notationElements = m_notation->elements(); + if (!notationElements) { + return; + } + + EngravingItem* selectedElement = selection()->element(); + if (!selectedElement) { + return; + } + + FilterElementsOptions options = elementsFilterOptions(selectedElement); + std::vector elements = notationElements->elements(options); + if (elements.empty()) { + return; + } + + clearSelection(); + + select(elements, SelectType::ADD); +} + +void NotationInteraction::selectAllSimilarElementsInStaff() +{ + TRACEFUNC; + auto notationElements = m_notation->elements(); + if (!notationElements) { + return; + } + + EngravingItem* selectedElement = selection()->element(); + if (!selectedElement) { + return; + } + + FilterElementsOptions options = elementsFilterOptions(selectedElement); + options.staffStart = static_cast(selectedElement->staffIdx()); + options.staffEnd = options.staffStart + 1; + + std::vector elements = notationElements->elements(options); + if (elements.empty()) { + return; + } + + clearSelection(); + + select(elements, SelectType::ADD); +} + +void NotationInteraction::selectAllSimilarElementsInRange() +{ + auto elements = m_notation->elements(); + if (!elements) { + return; + } + + mu::engraving::EngravingItem* lastHit = selection()->lastElementHit(); + if (!lastHit) { + return; + } + + mu::engraving::Score* score = elements->msScore(); + score->selectSimilarInRange(lastHit); + if (score->selectionChanged()) { + selectionChanged().notify(); + } +} + +void NotationInteraction::selectAllNotesInChord() +{ + TRACEFUNC; + + const std::vector& selectedElements = selection()->elements(); + if (selectedElements.empty()) { + return; + } + + std::set chords; + for (const EngravingItem* item : selectedElements) { + if (item->isNote()) { + chords.insert(toNote(item)->chord()); + } + } + + if (chords.empty()) { + return; + } + + std::vector allNotes; + for (const Chord* chord : chords) { + for (Note* note : chord->notes()) { + allNotes.push_back(note); + } + } + + clearSelection(); + select(allNotes, SelectType::ADD); +} + EngravingItem* NotationInteraction::contextItem() const { EngravingItem* item = selection()->element(); @@ -1114,6 +1237,78 @@ void NotationInteraction::doSelect(const std::vector& elements, score()->select(elements, type, staffIndex); } +void NotationInteraction::select(SelectionTarget target) +{ + //! TODO It's better to change the implementation, + // instead of calling different methods, it's better to do this: + // 1. get target elements + // 2. call select method with the elements + switch (target) { + case SelectionTarget::Undefined: + break; + case SelectionTarget::FirstItem: + selectFirstElement(); + break; + case SelectionTarget::LastItem: + selectLastElement(); + break; + case SelectionTarget::NextItem: + moveSelection(MoveDirection::Right, MoveSelectionType::EngravingItem); + break; + case SelectionTarget::PrevItem: + moveSelection(MoveDirection::Left, MoveSelectionType::EngravingItem); + break; + case SelectionTarget::NextTrack: + moveSelection(MoveDirection::Right, MoveSelectionType::Track); + break; + case SelectionTarget::PrevTrack: + moveSelection(MoveDirection::Left, MoveSelectionType::Track); + break; + case SelectionTarget::NextFrame: + moveSelection(MoveDirection::Right, MoveSelectionType::Frame); + break; + case SelectionTarget::PrevFrame: + moveSelection(MoveDirection::Left, MoveSelectionType::Frame); + break; + case SelectionTarget::NextSystem: + moveSelection(MoveDirection::Right, MoveSelectionType::System); + break; + case SelectionTarget::PrevSystem: + moveSelection(MoveDirection::Left, MoveSelectionType::System); + break; + case SelectionTarget::UpNoteInChord: + moveChordNoteSelection(MoveDirection::Up); + break; + case SelectionTarget::DownNoteInChord: + moveChordNoteSelection(MoveDirection::Down); + break; + case SelectionTarget::TopNoteInChord: + selectTopOrBottomOfChord(MoveDirection::Up); + break; + case SelectionTarget::BottomNoteInChord: + selectTopOrBottomOfChord(MoveDirection::Down); + break; + case SelectionTarget::Similar: + selectAllSimilarElements(); + break; + case SelectionTarget::SimilarInStaff: + selectAllSimilarElementsInStaff(); + break; + case SelectionTarget::SimilarInRange: + selectAllSimilarElementsInRange(); + break; + case SelectionTarget::NotesInChord: + selectAllNotesInChord(); + break; + case SelectionTarget::All: + selectAll(); + break; + case SelectionTarget::Section: + selectSection(); + break; + } +} + void NotationInteraction::selectElementsWithSameTypeOnSegment(mu::engraving::ElementType elementType, mu::engraving::Segment* segment) { TRACEFUNC; @@ -4187,16 +4382,6 @@ void NotationInteraction::toggleSnapToNext() void NotationInteraction::moveElementSelection(MoveDirection d) { - if (d == MoveDirection::First) { - selectFirstElement(false); - return; - } - - if (d == MoveDirection::Last) { - selectLastElement(); - return; - } - EngravingItem* el = score()->selection().element(); if (!el && !score()->selection().elements().empty()) { el = score()->selection().elements().back(); diff --git a/src/notation/internal/notationinteraction.h b/src/notation/internal/notationinteraction.h index 3a6765584c768..a0c0a4345b228 100644 --- a/src/notation/internal/notationinteraction.h +++ b/src/notation/internal/notationinteraction.h @@ -99,21 +99,39 @@ class NotationInteraction : public INotationInteraction, public muse::Contextabl void setHitElementContext(const HitElementContext& context) override; // Select + INotationSelectionPtr selection() const override; + muse::async::Notification selectionChanged() const override; + void select(const std::vector& elements, SelectType type = SelectType::REPLACE, staff_idx_t staffIndex = 0) override; + void select(SelectionTarget target) override; + void moveChordNoteSelection(MoveDirection d) override; - void select(const std::vector& elements, SelectType type = SelectType::REPLACE, - engraving::staff_idx_t staffIndex = 0) override; + void selectAndStartEditIfNeeded(EngravingItem* element) override; void selectAll() override; void selectSection() override; void selectFirstElement(bool frame = false) override; void selectLastElement() override; - INotationSelectionPtr selection() const override; + void clearSelection() override; - muse::async::Notification selectionChanged() const override; + void selectTopOrBottomOfChord(MoveDirection d) override; void findAndSelectChordRest(const Fraction& tick) override; void moveSegmentSelection(MoveDirection d) override; + FilterElementsOptions elementsFilterOptions(const EngravingItem* element) const; + void selectAllSimilarElements(); + void selectAllSimilarElementsInStaff(); + void selectAllSimilarElementsInRange(); + void selectAllNotesInChord(); + + // Change selection + bool moveSelectionAvailable(MoveSelectionType type) const override; + void moveSelection(MoveDirection d, MoveSelectionType type) override; + void expandSelection(ExpandSelectionMode mode) override; + void addToSelection(MoveDirection d, MoveSelectionType type) override; + void selectTopStaff() override; + void selectEmptyTrailingMeasure() override; + EngravingItem* contextItem() const override; // SelectionFilter @@ -152,14 +170,6 @@ class NotationInteraction : public INotationInteraction, public muse::Contextabl void redo() override; void undoRedoToIndex(size_t idx) override; - // Change selection - bool moveSelectionAvailable(MoveSelectionType type) const override; - void moveSelection(MoveDirection d, MoveSelectionType type) override; - void expandSelection(ExpandSelectionMode mode) override; - void addToSelection(MoveDirection d, MoveSelectionType type) override; - void selectTopStaff() override; - void selectEmptyTrailingMeasure() override; - // Move void movePitch(MoveDirection d, PitchMode mode) override; void nudge(MoveDirection d, bool quickly) override; diff --git a/src/notation/notationtypes.h b/src/notation/notationtypes.h index 4c2e10c2ec317..572b500dcafcb 100644 --- a/src/notation/notationtypes.h +++ b/src/notation/notationtypes.h @@ -24,6 +24,7 @@ #include #include +#include #include "engraving/dom/articulation.h" #include "engraving/dom/chord.h" @@ -101,14 +102,88 @@ enum class DragMode : unsigned char enum class MoveDirection : unsigned char { Undefined = 0, - First, - Last, Left, Right, Up, Down }; +enum class SelectionTarget : unsigned char +{ + Undefined = 0, + FirstItem, + LastItem, + NextItem, + PrevItem, + NextTrack, + PrevTrack, + NextFrame, + PrevFrame, + NextSystem, + PrevSystem, + UpNoteInChord, + DownNoteInChord, + TopNoteInChord, + BottomNoteInChord, + Similar, + SimilarInStaff, + SimilarInRange, + NotesInChord, + All, + Section, +}; + +static const std::map STR_SELECTION_TARGET = { + { "first-item", SelectionTarget::FirstItem }, + { "last-item", SelectionTarget::LastItem }, + { "next-item", SelectionTarget::NextItem }, + { "prev-item", SelectionTarget::PrevItem }, + { "next-track", SelectionTarget::NextTrack }, + { "prev-track", SelectionTarget::PrevTrack }, + { "next-frame", SelectionTarget::NextFrame }, + { "prev-frame", SelectionTarget::PrevFrame }, + { "next-system", SelectionTarget::NextSystem }, + { "prev-system", SelectionTarget::PrevSystem }, + { "up-note-in-chord", SelectionTarget::UpNoteInChord }, + { "down-note-in-chord", SelectionTarget::DownNoteInChord }, + { "top-note-in-chord", SelectionTarget::TopNoteInChord }, + { "bottom-note-in-chord", SelectionTarget::BottomNoteInChord }, + { "notes-in-chord", SelectionTarget::NotesInChord }, + { "similar", SelectionTarget::Similar }, + { "similar-in-staff", SelectionTarget::SimilarInStaff }, + { "similar-in-range", SelectionTarget::SimilarInRange }, + { "all", SelectionTarget::All }, + { "section", SelectionTarget::Section }, +}; + +inline SelectionTarget str_conv(const std::string& str, SelectionTarget def) +{ + auto it = STR_SELECTION_TARGET.find(str); + if (it != STR_SELECTION_TARGET.end()) { + return it->second; + } + return def; +} + +enum PlayMode { + NoPlay, PlayNote, PlayChord +}; + +inline static const std::map STR_PLAY_MODE = { + { "none", PlayMode::NoPlay }, + { "note", PlayMode::PlayNote }, + { "chord", PlayMode::PlayChord }, +}; + +inline PlayMode str_conv(const std::string& str, PlayMode def) +{ + auto it = STR_PLAY_MODE.find(str); + if (it != STR_PLAY_MODE.end()) { + return it->second; + } + return def; +} + enum class MoveSelectionType : unsigned char { Undefined = 0, diff --git a/src/notation/tests/mocks/notationinteractionmock.h b/src/notation/tests/mocks/notationinteractionmock.h index 3055063b94c0f..aad671cf646da 100644 --- a/src/notation/tests/mocks/notationinteractionmock.h +++ b/src/notation/tests/mocks/notationinteractionmock.h @@ -51,6 +51,7 @@ class NotationInteractionMock : public INotationInteraction MOCK_METHOD(void, moveChordNoteSelection, (MoveDirection), (override)); MOCK_METHOD(void, select, (const std::vector&, SelectType, engraving::staff_idx_t), (override)); + MOCK_METHOD(void, select, (SelectionTarget), (override)); MOCK_METHOD(void, selectAndStartEditIfNeeded, (EngravingItem*), (override)); MOCK_METHOD(void, selectAll, (), (override)); MOCK_METHOD(void, selectSection, (), (override)); diff --git a/src/notationscene/internal/notationactioncontroller.cpp b/src/notationscene/internal/notationactioncontroller.cpp index fecd8d9dc46fb..2de86f308289d 100644 --- a/src/notationscene/internal/notationactioncontroller.cpp +++ b/src/notationscene/internal/notationactioncontroller.cpp @@ -108,21 +108,59 @@ void NotationActionController::init() registerCommand(UNDO_COMMAND, &Interaction::undo); registerCommand(REDO_COMMAND, &Interaction::redo); - // navigation commands - registerMoveSelection(GOTO_FIRST_ELEMENT_COMMAND, MoveSelectionType::EngravingItem, MoveDirection::First, PlayMode::PlayChord); - registerMoveSelection(GOTO_LAST_ELEMENT_COMMAND, MoveSelectionType::EngravingItem, MoveDirection::Last, PlayMode::PlayChord); - registerMoveSelection(GOTO_NEXT_ELEMENT_COMMAND, MoveSelectionType::EngravingItem, MoveDirection::Right, PlayMode::PlayNote); - registerMoveSelection(GOTO_PREV_ELEMENT_COMMAND, MoveSelectionType::EngravingItem, MoveDirection::Left, PlayMode::PlayNote); - registerMoveSelection(GOTO_NEXT_TRACK_COMMAND, MoveSelectionType::Track, MoveDirection::Right, PlayMode::PlayChord); - registerMoveSelection(GOTO_PREV_TRACK_COMMAND, MoveSelectionType::Track, MoveDirection::Left, PlayMode::PlayChord); - registerMoveSelection(GOTO_NEXT_FRAME_COMMAND, MoveSelectionType::Frame, MoveDirection::Right); - registerMoveSelection(GOTO_PREV_FRAME_COMMAND, MoveSelectionType::Frame, MoveDirection::Left); - registerMoveSelection(GOTO_NEXT_SYSTEM_COMMAND, MoveSelectionType::System, MoveDirection::Right); - registerMoveSelection(GOTO_PREV_SYSTEM_COMMAND, MoveSelectionType::System, MoveDirection::Left); - registerCommand(GOTO_UP_CHORD_COMMAND, [this]() { moveWithinChord(MoveDirection::Up); }); - registerCommand(GOTO_DOWN_CHORD_COMMAND, [this]() { moveWithinChord(MoveDirection::Down); }); - registerCommand(GOTO_TOP_CHORD_COMMAND, [this]() { selectTopOrBottomOfChord(MoveDirection::Up); }); - registerCommand(GOTO_BOTTOM_CHORD_COMMAND, [this]() { selectTopOrBottomOfChord(MoveDirection::Down); }); + // navigation and selection commands + static const rcommand::CommandQuery SELECT_QUERY(SELECT_COMMAND); + + static const std::map SELECTION_ALIASES { + { GOTO_FIRST_ELEMENT_COMMAND, SELECT_QUERY.set("target", "first-item").set("play-mode", "chord") }, + { GOTO_LAST_ELEMENT_COMMAND, SELECT_QUERY.set("target", "last-item").set("play-mode", "chord") }, + { GOTO_NEXT_ELEMENT_COMMAND, SELECT_QUERY.set("target", "next-item").set("play-mode", "note") }, + { GOTO_PREV_ELEMENT_COMMAND, SELECT_QUERY.set("target", "prev-item").set("play-mode", "note") }, + { GOTO_NEXT_TRACK_COMMAND, SELECT_QUERY.set("target", "next-track").set("play-mode", "chord") }, + { GOTO_PREV_TRACK_COMMAND, SELECT_QUERY.set("target", "prev-track").set("play-mode", "chord") }, + { GOTO_NEXT_FRAME_COMMAND, SELECT_QUERY.set("target", "next-frame") }, + { GOTO_PREV_FRAME_COMMAND, SELECT_QUERY.set("target", "prev-frame") }, + { GOTO_NEXT_SYSTEM_COMMAND, SELECT_QUERY.set("target", "next-system") }, + { GOTO_PREV_SYSTEM_COMMAND, SELECT_QUERY.set("target", "prev-system") }, + { GOTO_UPNOTE_IN_CHORD_COMMAND, SELECT_QUERY.set("target", "up-note-in-chord") }, + { GOTO_DOWNNOTE_IN_CHORD_COMMAND, SELECT_QUERY.set("target", "down-note-in-chord") }, + { GOTO_TOPNOTE_IN_CHORD_COMMAND, SELECT_QUERY.set("target", "top-note-in-chord") }, + { GOTO_BOTTOMNOTE_IN_CHORD_COMMAND, SELECT_QUERY.set("target", "bottom-note-in-chord") }, + { SELECT_SIMILAR_COMMAND, SELECT_QUERY.set("target", "similar") }, + { SELECT_SIMILAR_IN_STAFF_COMMAND, SELECT_QUERY.set("target", "similar-in-staff") }, + { SELECT_SIMILAR_IN_RANGE_COMMAND, SELECT_QUERY.set("target", "similar-in-range") }, + { SELECT_NOTES_IN_CHORD_COMMAND, SELECT_QUERY.set("target", "notes-in-chord") }, + { SELECT_ALL_COMMAND, SELECT_QUERY.set("target", "all") }, + { SELECT_SECTION_COMMAND, SELECT_QUERY.set("target", "section") }, + }; + + registerCommand(SELECT_COMMAND, &Controller::select); + registerAliases(SELECTION_ALIASES, &Controller::select); + + //! TODO remove, for example + // registerSelectionCommand(GOTO_FIRST_ELEMENT_COMMAND, SelectionTarget::FirstItem, PlayMode::PlayChord); + // registerSelectionCommand(GOTO_LAST_ELEMENT_COMMAND, SelectionTarget::LastItem, PlayMode::PlayChord); + // registerSelectionCommand(GOTO_NEXT_ELEMENT_COMMAND, SelectionTarget::NextItem, PlayMode::PlayNote); + // registerSelectionCommand(GOTO_PREV_ELEMENT_COMMAND, SelectionTarget::PrevItem, PlayMode::PlayNote); + // registerSelectionCommand(GOTO_NEXT_TRACK_COMMAND, SelectionTarget::NextTrack, PlayMode::PlayChord); + // registerSelectionCommand(GOTO_PREV_TRACK_COMMAND, SelectionTarget::PrevTrack, PlayMode::PlayChord); + // registerSelectionCommand(GOTO_NEXT_FRAME_COMMAND, SelectionTarget::NextFrame); + // registerSelectionCommand(GOTO_PREV_FRAME_COMMAND, SelectionTarget::PrevFrame); + // registerSelectionCommand(GOTO_NEXT_SYSTEM_COMMAND, SelectionTarget::NextSystem); + // registerSelectionCommand(GOTO_PREV_SYSTEM_COMMAND, SelectionTarget::PrevSystem); + // registerSelectionCommand(GOTO_UPNOTE_IN_CHORD_COMMAND, SelectionTarget::UpNoteInChord); + // registerSelectionCommand(GOTO_DOWNNOTE_IN_CHORD_COMMAND, SelectionTarget::DownNoteInChord); + // registerSelectionCommand(GOTO_TOPNOTE_IN_CHORD_COMMAND, SelectionTarget::TopNoteInChord); + // registerSelectionCommand(GOTO_BOTTOMNOTE_IN_CHORD_COMMAND, SelectionTarget::BottomNoteInChord); + + // registerSelectionCommand(SELECT_SIMILAR_COMMAND, SelectionTarget::Similar); + // registerSelectionCommand(SELECT_SIMILAR_IN_STAFF_COMMAND, SelectionTarget::SimilarInStaff); + // registerSelectionCommand(SELECT_SIMILAR_IN_RANGE_COMMAND, SelectionTarget::SimilarInRange); + // registerSelectionCommand(SELECT_NOTES_IN_CHORD_COMMAND, SelectionTarget::NotesInChord); + // registerSelectionCommand(SELECT_ALL_COMMAND, SelectionTarget::All); + // registerSelectionCommand(SELECT_SECTION_COMMAND, SelectionTarget::Section); + + registerAction("select-dialog", &Controller::openSelectionMoreOptions, &Controller::hasSelection); registerCommand(EDIT_NEXT_WORD_COMMAND, [this]() { nextWord(); }); registerCommand(EDIT_NEXT_TEXT_ELEMENT_COMMAND, [this]() { nextTextElement(); }); @@ -284,13 +322,6 @@ void NotationActionController::init() registerAction("hammer-on-pull-off", &Controller::addHammerOnPullOff); - registerAction("select-similar", &Controller::selectAllSimilarElements, &Controller::hasSelection); - registerAction("select-similar-staff", &Controller::selectAllSimilarElementsInStaff, &Controller::hasSelection); - registerAction("select-similar-range", &Controller::selectAllSimilarElementsInRange, &Controller::hasSelection); - registerAction("select-dialog", &Controller::openSelectionMoreOptions, &Controller::hasSelection); - registerAction("select-notes-in-chord", &Controller::selectAllNotesInChord, &Controller::hasSelection); - registerAction("notation-select-all", &Interaction::selectAll); - registerAction("notation-select-section", &Interaction::selectSection); registerAction("move-up", &Interaction::moveChordRestToStaff, MoveDirection::Up, &Controller::hasSelection); registerAction("move-down", &Interaction::moveChordRestToStaff, MoveDirection::Down, &Controller::hasSelection); registerAction("move-left", &Interaction::swapChordRest, MoveDirection::Left, &Controller::isNoteInputMode); @@ -748,10 +779,16 @@ void NotationActionController::init() { "prev-frame", GOTO_PREV_FRAME_COMMAND }, { "next-system", GOTO_NEXT_SYSTEM_COMMAND }, { "prev-system", GOTO_PREV_SYSTEM_COMMAND }, - { "up-chord", GOTO_UP_CHORD_COMMAND }, - { "down-chord", GOTO_DOWN_CHORD_COMMAND }, - { "top-chord", GOTO_TOP_CHORD_COMMAND }, - { "bottom-chord", GOTO_BOTTOM_CHORD_COMMAND }, + { "up-chord", GOTO_UPNOTE_IN_CHORD_COMMAND }, + { "down-chord", GOTO_DOWNNOTE_IN_CHORD_COMMAND }, + { "top-chord", GOTO_TOPNOTE_IN_CHORD_COMMAND }, + { "bottom-chord", GOTO_BOTTOMNOTE_IN_CHORD_COMMAND }, + { "select-similar", SELECT_SIMILAR_COMMAND }, + { "select-similar-staff", SELECT_SIMILAR_IN_STAFF_COMMAND }, + { "select-similar-range", SELECT_SIMILAR_IN_RANGE_COMMAND }, + { "select-notes-in-chord", SELECT_NOTES_IN_CHORD_COMMAND }, + { "notation-select-all", SELECT_ALL_COMMAND }, + { "notation-select-section", SELECT_SECTION_COMMAND }, }; auto ad = dispatcher(); @@ -1603,14 +1640,14 @@ bool NotationActionController::isMoveSelectionAvailable(MoveSelectionType type) return interaction && interaction->moveSelectionAvailable(type); } -void NotationActionController::moveSelection(MoveSelectionType type, MoveDirection direction) +void NotationActionController::select(SelectionTarget target) { auto interaction = currentNotationInteraction(); if (!interaction) { return; } - interaction->moveSelection(direction, type); + interaction->select(target); seekSelectedElement(); } @@ -2074,109 +2111,6 @@ void NotationActionController::addFretboardDiagram() interaction->addFretboardDiagram(); } -void NotationActionController::selectAllSimilarElements() -{ - TRACEFUNC; - auto notationElements = currentNotationElements(); - auto interaction = currentNotationInteraction(); - if (!notationElements || !interaction) { - return; - } - - EngravingItem* selectedElement = interaction->selection()->element(); - if (!selectedElement) { - return; - } - - FilterElementsOptions options = elementsFilterOptions(selectedElement); - std::vector elements = notationElements->elements(options); - if (elements.empty()) { - return; - } - - interaction->clearSelection(); - - interaction->select(elements, SelectType::ADD); -} - -void NotationActionController::selectAllSimilarElementsInStaff() -{ - TRACEFUNC; - auto notationElements = currentNotationElements(); - auto interaction = currentNotationInteraction(); - if (!notationElements || !interaction) { - return; - } - - EngravingItem* selectedElement = interaction->selection()->element(); - if (!selectedElement) { - return; - } - - FilterElementsOptions options = elementsFilterOptions(selectedElement); - options.staffStart = static_cast(selectedElement->staffIdx()); - options.staffEnd = options.staffStart + 1; - - std::vector elements = notationElements->elements(options); - if (elements.empty()) { - return; - } - - interaction->clearSelection(); - - interaction->select(elements, SelectType::ADD); -} - -void NotationActionController::selectAllSimilarElementsInRange() -{ - auto elements = currentNotationElements(); - mu::engraving::EngravingItem* lastHit = currentNotationSelection()->lastElementHit(); - if (!elements || !lastHit) { - return; - } - - mu::engraving::Score* score = elements->msScore(); - score->selectSimilarInRange(lastHit); - if (score->selectionChanged()) { - currentNotationInteraction()->selectionChanged().notify(); - } -} - -void NotationActionController::selectAllNotesInChord() -{ - TRACEFUNC; - auto interaction = currentNotationInteraction(); - if (!interaction) { - return; - } - - const std::vector& selectedElements = interaction->selection()->elements(); - if (selectedElements.empty()) { - return; - } - - std::set chords; - for (const EngravingItem* item : selectedElements) { - if (item->isNote()) { - chords.insert(toNote(item)->chord()); - } - } - - if (chords.empty()) { - return; - } - - std::vector allNotes; - for (const Chord* chord : chords) { - for (Note* note : chord->notes()) { - allNotes.push_back(note); - } - } - - interaction->clearSelection(); - interaction->select(allNotes, SelectType::ADD); -} - void NotationActionController::openSelectionMoreOptions() { auto interaction = currentNotationInteraction(); @@ -2451,27 +2385,6 @@ void NotationActionController::saveStyle() } } -FilterElementsOptions NotationActionController::elementsFilterOptions(const EngravingItem* element) const -{ - TRACEFUNC; - FilterElementsOptions options; - options.elementType = element->type(); - - if (element->isNote()) { - const mu::engraving::Note* note = dynamic_cast(element); - if (note->chord()->isGrace()) { - options.subtype = -1; - } else { - options.subtype = element->subtype(); - } - } else if (element->isHairpinSegment() || element->isHarmony()) { - options.subtype = element->subtype(); - options.bySubtype = true; - } - - return options; -} - bool NotationActionController::measureNavigationAvailable() const { return isNotEditingOrHasPopup() || textNavigationAvailable(); @@ -3024,15 +2937,32 @@ void NotationActionController::registerTabPadNoteAction(const ActionCode& code, }, &NotationActionController::isTablatureStaff); } -void NotationActionController::registerMoveSelection(const muse::rcommand::Command& command, - MoveSelectionType type, - MoveDirection direction, - PlayMode playMode) +void NotationActionController::select(const muse::rcommand::CommandQuery& query) { - registerCommand(command, [this, type, direction, playMode]() { - moveSelection(type, direction); + LOGDA() << query.toString(); + + SelectionTarget target = str_conv(query.param("target").toString(), SelectionTarget::Undefined); + if (target == SelectionTarget::Undefined) { + return; + } + + select(target); + + PlayMode playMode = str_conv(query.param("play-mode").toString(), PlayMode::NoPlay); + if (playMode != PlayMode::NoPlay) { + seekSelectedElement(); + playSelectedElement(playMode == PlayMode::PlayChord); + } +} + +void NotationActionController::registerSelectionCommand(const muse::rcommand::Command& command, + SelectionTarget target, PlayMode playMode) +{ + registerCommand(command, [this, target, playMode]() { + select(target); if (playMode != PlayMode::NoPlay) { + seekSelectedElement(); playSelectedElement(playMode == PlayMode::PlayChord); } }); @@ -3241,6 +3171,24 @@ void NotationActionController::registerCommand(const muse::rcommand::Command& co }); } +void NotationActionController::registerAliases(const std::map& aliases, + void (NotationActionController::*handler)(const muse::rcommand::CommandQuery&)) +{ + for (auto it = aliases.cbegin(); it != aliases.cend(); ++it) { + auto alias = it->first; + auto query = it->second; + + commandDispatcher()->onRequest(this, alias, [this, query, handler]() { + if (!commandsState()->commandState(query.uri()).enabled) { + return muse::make_ret(Ret::Code::NotSupported); + } + + (this->*handler)(query); + return muse::make_ok(); + }); + } +} + void NotationActionController::registerNoteInputCommand(const muse::rcommand::Command& command, NoteInputMethod method) { registerCommand(command, [this, method]() { toggleNoteInput(method); }, &NotationActionController::toggleNoteInputAllowed); diff --git a/src/notationscene/internal/notationactioncontroller.h b/src/notationscene/internal/notationactioncontroller.h index 89db49faf096c..1a947a8c58590 100644 --- a/src/notationscene/internal/notationactioncontroller.h +++ b/src/notationscene/internal/notationactioncontroller.h @@ -143,7 +143,8 @@ class NotationActionController : public INotationCommandsController, public muse void putTuplet(const TupletOptions& options); void putTuplet(int tupletCount); - void moveSelection(MoveSelectionType type, MoveDirection direction); + void select(SelectionTarget target); + muse::Ret moveWithRet(MoveDirection direction, bool quickly = false); void move(MoveDirection direction, bool quickly = false); void moveInputNotes(bool up, PitchMode mode); @@ -170,10 +171,6 @@ class NotationActionController : public INotationCommandsController, public muse void addGuitarBend(GuitarBendType bendType); void addFretboardDiagram(); - void selectAllSimilarElements(); - void selectAllSimilarElementsInStaff(); - void selectAllSimilarElementsInRange(); - void selectAllNotesInChord(); void openSelectionMoreOptions(); void startEditSelectedElement(const muse::actions::ActionData& args); @@ -225,8 +222,6 @@ class NotationActionController : public INotationCommandsController, public muse void pasteSelection(PastingType type = PastingType::Default); Fraction resolvePastingScale(const INotationInteractionPtr& interaction, PastingType type) const; - FilterElementsOptions elementsFilterOptions(const EngravingItem* element) const; - bool measureNavigationAvailable() const; bool toggleLayoutBreakAvailable() const; @@ -286,10 +281,6 @@ class NotationActionController : public INotationCommandsController, public muse void registerPadNoteAction(const muse::actions::ActionCode&, Pad padding); void registerTabPadNoteAction(const muse::actions::ActionCode&, Pad padding); - enum PlayMode { - NoPlay, PlayNote, PlayChord - }; - void registerAddToSelectionAction(const muse::actions::ActionCode& code, MoveSelectionType type, MoveDirection direction); void registerExpandSelectionAction(const muse::actions::ActionCode& code, ExpandSelectionMode mode); @@ -312,12 +303,16 @@ class NotationActionController : public INotationCommandsController, public muse void registerCommand(const muse::rcommand::Command&, void (NotationActionController::*)()); void registerCommand(const muse::rcommand::Command&, void (NotationActionController::*)(), bool (NotationActionController::*)() const); void registerCommand(const muse::rcommand::Command&, void (NotationActionController::*)(const muse::rcommand::CommandQuery&)); + void registerAliases(const std::map& aliases, + void (NotationActionController::*handler)(const muse::rcommand::CommandQuery&)); void registerCommand(const muse::rcommand::Command&, void (INotationInteraction::*)(), PlayMode = PlayMode::NoPlay); void registerNoteInputCommand(const muse::rcommand::Command& command, NoteInputMethod method); void registerPadNoteCommand(const muse::rcommand::Command& command, Pad padding); void registerNoteCommand(const muse::rcommand::Command&, NoteName, NoteAddingMode addingMode = NoteAddingMode::NextChord); - void registerMoveSelection(const muse::rcommand::Command&, MoveSelectionType, MoveDirection, PlayMode playMode = PlayMode::NoPlay); + + void select(const muse::rcommand::CommandQuery& query); + void registerSelectionCommand(const muse::rcommand::Command&, SelectionTarget, PlayMode playMode = PlayMode::NoPlay); muse::async::Channel m_hasSelectionChanged; muse::async::Channel m_textEditingChanged; diff --git a/src/notationscene/internal/notationcommandsregister.cpp b/src/notationscene/internal/notationcommandsregister.cpp index 2310cf9a897d9..1ec125d9c29c2 100644 --- a/src/notationscene/internal/notationcommandsregister.cpp +++ b/src/notationscene/internal/notationcommandsregister.cpp @@ -739,7 +739,23 @@ static const std::vector s_commandInfos = { Decoration() }, - // navigation commands + // navigation and selection commands + + CommandInfo{ + SELECT_COMMAND, + TranslatableString("action", "Select"), + TranslatableString("action", "Select items by target"), + InputSchema({ + { "target", + Arg(DataType::String, + u"Selection target (first-item, last-item, next-item, prev-item, " + u"next-track, prev-track, next-frame, prev-frame, next-system, prev-system, " + u"up-note-in-chord, down-note-in-chord, top-note-in-chord, bottom-note-in-chord, notes-in-chord, " + u"similar, similar-in-staff, similar-in-range, all, section)") }, + { "play-mode", Arg(DataType::String, u"Play mode (none, note, chord)") }, + }), + Decoration() + }, CommandInfo{ GOTO_FIRST_ELEMENT_COMMAND, TranslatableString("action", "First element"), @@ -812,33 +828,77 @@ static const std::vector s_commandInfos = { Decoration() }, CommandInfo{ - GOTO_UP_CHORD_COMMAND, + GOTO_UPNOTE_IN_CHORD_COMMAND, TranslatableString("action", "Up note in chord"), TranslatableString("action", "Select note/rest above"), InputSchema(), Decoration() }, CommandInfo{ - GOTO_DOWN_CHORD_COMMAND, + GOTO_DOWNNOTE_IN_CHORD_COMMAND, TranslatableString("action", "Down note in chord"), TranslatableString("action", "Select note/rest below"), InputSchema(), Decoration() }, CommandInfo{ - GOTO_TOP_CHORD_COMMAND, + GOTO_TOPNOTE_IN_CHORD_COMMAND, TranslatableString("action", "Top note in chord"), TranslatableString("action", "Select top note in chord"), InputSchema(), Decoration() }, CommandInfo{ - GOTO_BOTTOM_CHORD_COMMAND, + GOTO_BOTTOMNOTE_IN_CHORD_COMMAND, TranslatableString("action", "Bottom note in chord"), TranslatableString("action", "Select bottom note in chord"), InputSchema(), Decoration() }, + + // selection commands + CommandInfo{ + SELECT_SIMILAR_COMMAND, + TranslatableString("action", "Similar"), + TranslatableString("action", "Select similar elements"), + InputSchema(), + Decoration() + }, + CommandInfo{ + SELECT_SIMILAR_IN_STAFF_COMMAND, + TranslatableString("action", "Similar on this staff"), + TranslatableString("action", "Select similar elements on the same staff"), + InputSchema(), + Decoration() + }, + CommandInfo{ + SELECT_SIMILAR_IN_RANGE_COMMAND, + TranslatableString("action", "Similar in this range"), + TranslatableString("action", "Select similar elements in the selected range"), + InputSchema(), + Decoration() + }, + CommandInfo{ + SELECT_NOTES_IN_CHORD_COMMAND, + TranslatableString("action", "Notes in chord"), + TranslatableString("action", "Select notes in chord"), + InputSchema(), + Decoration() + }, + CommandInfo{ + SELECT_ALL_COMMAND, + TranslatableString("action", "Select &all"), + TranslatableString("action", "Select all"), + InputSchema(), + Decoration() + }, + CommandInfo{ + SELECT_SECTION_COMMAND, + TranslatableString("action", "Select sectio&n"), + TranslatableString("action", "Select section"), + InputSchema(), + Decoration() + }, }; std::string NotationCommandsRegister::moduleName() const diff --git a/src/notationscene/internal/notationcommandsstate.cpp b/src/notationscene/internal/notationcommandsstate.cpp index 3b058b61139a0..43c5376844493 100644 --- a/src/notationscene/internal/notationcommandsstate.cpp +++ b/src/notationscene/internal/notationcommandsstate.cpp @@ -49,10 +49,14 @@ static const std::vector HAS_SELECTION_REQUIRED_COMMANDS = { DELETE_COMMAND, FLIP_COMMAND, FLIP_HORIZONTALLY_COMMAND, - GOTO_UP_CHORD_COMMAND, - GOTO_DOWN_CHORD_COMMAND, - GOTO_TOP_CHORD_COMMAND, - GOTO_BOTTOM_CHORD_COMMAND + GOTO_UPNOTE_IN_CHORD_COMMAND, + GOTO_DOWNNOTE_IN_CHORD_COMMAND, + GOTO_TOPNOTE_IN_CHORD_COMMAND, + GOTO_BOTTOMNOTE_IN_CHORD_COMMAND, + SELECT_SIMILAR_COMMAND, + SELECT_SIMILAR_IN_STAFF_COMMAND, + SELECT_SIMILAR_IN_RANGE_COMMAND, + SELECT_NOTES_IN_CHORD_COMMAND }; static const std::vector UNDO_REDO_COMMANDS = { diff --git a/src/notationscene/notationcommands.h b/src/notationscene/notationcommands.h index f02bff505fab4..0fcf34644b6ba 100644 --- a/src/notationscene/notationcommands.h +++ b/src/notationscene/notationcommands.h @@ -30,7 +30,9 @@ inline static const muse::rcommand::Command CANCEL_COMMAND("command://notation/c inline static const muse::rcommand::Command UNDO_COMMAND("command://notation/undo"); inline static const muse::rcommand::Command REDO_COMMAND("command://notation/redo"); -// navigation commands +// navigation and selection commands +inline static const muse::rcommand::Command SELECT_COMMAND("command://notation/select"); // with params +// aliases for select command inline static const muse::rcommand::Command GOTO_FIRST_ELEMENT_COMMAND("command://notation/goto-first-element"); inline static const muse::rcommand::Command GOTO_LAST_ELEMENT_COMMAND("command://notation/goto-last-element"); inline static const muse::rcommand::Command GOTO_NEXT_ELEMENT_COMMAND("command://notation/goto-next-element"); @@ -41,10 +43,17 @@ inline static const muse::rcommand::Command GOTO_NEXT_FRAME_COMMAND("command://n inline static const muse::rcommand::Command GOTO_PREV_FRAME_COMMAND("command://notation/goto-prev-frame"); inline static const muse::rcommand::Command GOTO_NEXT_SYSTEM_COMMAND("command://notation/goto-next-system"); inline static const muse::rcommand::Command GOTO_PREV_SYSTEM_COMMAND("command://notation/goto-prev-system"); -inline static const muse::rcommand::Command GOTO_UP_CHORD_COMMAND("command://notation/goto-up-chord"); -inline static const muse::rcommand::Command GOTO_DOWN_CHORD_COMMAND("command://notation/goto-down-chord"); -inline static const muse::rcommand::Command GOTO_TOP_CHORD_COMMAND("command://notation/goto-top-chord"); -inline static const muse::rcommand::Command GOTO_BOTTOM_CHORD_COMMAND("command://notation/goto-bottom-chord"); +inline static const muse::rcommand::Command GOTO_UPNOTE_IN_CHORD_COMMAND("command://notation/goto-upnote-in-chord"); +inline static const muse::rcommand::Command GOTO_DOWNNOTE_IN_CHORD_COMMAND("command://notation/goto-downnote-in-chord"); +inline static const muse::rcommand::Command GOTO_TOPNOTE_IN_CHORD_COMMAND("command://notation/goto-topnote-in-chord"); +inline static const muse::rcommand::Command GOTO_BOTTOMNOTE_IN_CHORD_COMMAND("command://notation/goto-bottomnote-in-chord"); +inline static const muse::rcommand::Command SELECT_SIMILAR_COMMAND("command://notation/select-similar"); +inline static const muse::rcommand::Command SELECT_SIMILAR_IN_STAFF_COMMAND("command://notation/select-similar-in-staff"); +inline static const muse::rcommand::Command SELECT_SIMILAR_IN_RANGE_COMMAND("command://notation/select-similar-in-range"); +inline static const muse::rcommand::Command SELECT_NOTES_IN_CHORD_COMMAND("command://notation/select-notes-in-chord"); +inline static const muse::rcommand::Command SELECT_ALL_COMMAND("command://notation/select-all"); +inline static const muse::rcommand::Command SELECT_SECTION_COMMAND("command://notation/select-section"); +// ------------------------------------------------------------ inline static const muse::rcommand::Command EDIT_NEXT_WORD_COMMAND("command://notation/edit-next-word"); inline static const muse::rcommand::Command EDIT_NEXT_TEXT_ELEMENT_COMMAND("command://notation/edit-next-text-element");