Skip to content

returned shortcut module v1#148

Merged
igorkorsukov merged 1 commit into
musescore:mainfrom
igorkorsukov:w/rcmd/rcmd_step0
Jul 14, 2026
Merged

returned shortcut module v1#148
igorkorsukov merged 1 commit into
musescore:mainfrom
igorkorsukov:w/rcmd/rcmd_step0

Conversation

@igorkorsukov

Copy link
Copy Markdown
Member

No description provided.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 96554f4e-cf1f-4a53-a2e2-c5dbe57dd726

📥 Commits

Reviewing files that changed from the base of the PR and between 7d6e9ca and 96c4aaa.

📒 Files selected for processing (56)
  • framework/CMakeLists.txt
  • framework/cmake/MuseDeclareOptions.cmake
  • framework/cmake/muse_framework_config.h.in
  • framework/shortcuts/CMakeLists.txt
  • framework/shortcuts/internal/shortcutsconfiguration.cpp
  • framework/shortcuts/internal/shortcutsconfiguration.h
  • framework/shortcuts/internal/shortcutscontroller.cpp
  • framework/shortcuts/internal/shortcutscontroller.h
  • framework/shortcuts/internal/shortcutsregister.cpp
  • framework/shortcuts/ishortcutsconfiguration.h
  • framework/shortcuts/qml/Muse/Shortcuts/editshortcutmodel.cpp
  • framework/shortcuts/qml/Muse/Shortcuts/internal/ShortcutsList.qml
  • framework/shortcuts/qml/Muse/Shortcuts/shortcutsinstancemodel.cpp
  • framework/shortcuts/qml/Muse/Shortcuts/shortcutsinstancemodel.h
  • framework/shortcuts/qml/Muse/Shortcuts/shortcutsmodel.cpp
  • framework/shortcuts/qml/Muse/Shortcuts/shortcutsmodel.h
  • framework/shortcuts/shortcutsmodule.cpp
  • framework/shortcuts/shortcutsmodule.h
  • framework/shortcuts/shortcutstypes.h
  • framework/shortcuts_v2/CMakeLists.txt
  • framework/shortcuts_v2/README.md
  • framework/shortcuts_v2/api/shortcutsapi.cpp
  • framework/shortcuts_v2/api/shortcutsapi.h
  • framework/shortcuts_v2/icommandshortcutsregister.h
  • framework/shortcuts_v2/internal/commandshortcutsregister.cpp
  • framework/shortcuts_v2/internal/commandshortcutsregister.h
  • framework/shortcuts_v2/internal/shortcutsconfiguration.cpp
  • framework/shortcuts_v2/internal/shortcutsconfiguration.h
  • framework/shortcuts_v2/internal/shortcutscontroller.cpp
  • framework/shortcuts_v2/internal/shortcutscontroller.h
  • framework/shortcuts_v2/internal/shortcutsregister.cpp
  • framework/shortcuts_v2/internal/shortcutsregister.h
  • framework/shortcuts_v2/ishortcutsconfiguration.h
  • framework/shortcuts_v2/ishortcutscontroller.h
  • framework/shortcuts_v2/ishortcutsregister.h
  • framework/shortcuts_v2/ishortcutsresolver.h
  • framework/shortcuts_v2/qml/Muse/Shortcuts/CMakeLists.txt
  • framework/shortcuts_v2/qml/Muse/Shortcuts/EditShortcutDialogContent.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/Shortcuts.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/ShortcutsPage.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/StandardEditShortcutDialog.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/editshortcutmodel.cpp
  • framework/shortcuts_v2/qml/Muse/Shortcuts/editshortcutmodel.h
  • framework/shortcuts_v2/qml/Muse/Shortcuts/internal/ShortcutsBottomPanel.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/internal/ShortcutsList.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/internal/ShortcutsTopPanel.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.h
  • framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm
  • framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsinstancemodel.cpp
  • framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsinstancemodel.h
  • framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp
  • framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.h
  • framework/shortcuts_v2/shortcutcontext.h
  • framework/shortcuts_v2/shortcutsmodule.cpp
  • framework/shortcuts_v2/shortcutsmodule.h
  • framework/shortcuts_v2/shortcutstypes.h
💤 Files with no reviewable changes (9)
  • framework/shortcuts/qml/Muse/Shortcuts/internal/ShortcutsList.qml
  • framework/shortcuts/internal/shortcutscontroller.h
  • framework/shortcuts/ishortcutsconfiguration.h
  • framework/shortcuts/CMakeLists.txt
  • framework/shortcuts/shortcutsmodule.cpp
  • framework/shortcuts/internal/shortcutsconfiguration.h
  • framework/shortcuts/shortcutsmodule.h
  • framework/shortcuts/qml/Muse/Shortcuts/shortcutsinstancemodel.h
  • framework/shortcuts/internal/shortcutsregister.cpp

📝 Walkthrough

Walkthrough

Adds a selectable Shortcuts v2 module with new CMake configuration, interfaces, storage, controller, API, and module lifecycle wiring. The existing shortcuts implementation is decoupled from command shortcut registration and simplified around unified shortcut data. New QML models and components provide shortcut listing, editing, conflict handling, import/export, reset, and runtime activation. macOS-specific code translates shortcut sequences through the active keyboard layout.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning No PR description was provided, so the required template sections and checklist are missing. Add the issue reference, a short change summary, and complete the required checklist sections in the repository template.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is related to the shortcuts module changes, but it is broad and does not mention the v1/v2 split.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
framework/shortcuts/shortcutstypes.h (1)

54-60: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore the context comparison in operator==.

During the decoupling of the legacy shortcuts module, the comparison for the context field was accidentally removed from operator==, despite context still being an active member of this struct.

Omitting it means two shortcuts with identical actions and sequences but strictly different contexts will evaluate to true when compared. Based on upstream usages (like ShortcutsModel::resetToDefaultSelectedShortcuts), this false positive will incorrectly trigger loop continue statements and silently skip critical shortcut conflict resolutions.

🐛 Proposed fix to restore struct equality logic
     bool operator ==(const Shortcut& sc) const
     {
         return action == sc.action
+               && context == sc.context
                && sequences == sc.sequences
                && standardKey == sc.standardKey
                && autoRepeat == sc.autoRepeat;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/shortcuts/shortcutstypes.h` around lines 54 - 60, Update
Shortcut::operator== to include a comparison of the active context member
alongside action, sequences, standardKey, and autoRepeat, so shortcuts with
different contexts are not considered equal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@framework/shortcuts_v2/internal/commandshortcutsregister.cpp`:
- Around line 344-358: Replace the ReadResourceLockGuard with
WriteResourceLockGuard in both importFromFile paths:
framework/shortcuts_v2/internal/commandshortcutsregister.cpp lines 344-358 and
framework/shortcuts_v2/internal/shortcutsregister.cpp lines 519-533. Keep the
existing file-copy, error handling, and reload behavior unchanged.
- Around line 159-174: Update
CommandShortcutsRegister::filterAndUpdateAdditionalShortcuts so the
additional-shortcut lookup uses shortcut.command rather than shortcut.action
when searching shortcuts. Preserve the existing refresh and removal behavior,
ensuring matching continues to use the command/scope identity expected by the
register.

In `@framework/shortcuts_v2/internal/shortcutscontroller.cpp`:
- Around line 27-33: Disable unconditional debug logging by changing the
SHORTCUTS_DEBUG configuration used by SC_LOG() in shortcutscontroller.cpp so
production builds resolve to LOGN() rather than LOGDA(). Preserve the existing
conditional macro structure and allow debug logging only through an intentional
build-time configuration.

In `@framework/shortcuts_v2/qml/Muse/Shortcuts/editshortcutmodel.cpp`:
- Around line 47-61: Update the shortcut identity handling in the self-skip loop
and trySave() so entries are matched by a stable unique identifier rather than
full QVariantMap equality. Use the identifier introduced by the shortcutToObject
root-cause fix for the comparisons currently using shortcut == originShortcut
and m_allShortcuts.indexOf(m_conflictShortcut), preserving the existing
conflict-selection behavior for distinct shortcuts with identical fields.
- Around line 183-200: Update checkNewSequenceForConflicts() and trySave() to
use newSequenceInNativeFormat() whenever comparing the edited sequence with
stored shortcut sequences. Preserve the existing conflict detection and
no-change guard logic while ensuring both comparisons use native-text
representations consistently.

In
`@framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm`:
- Around line 376-406: Document the rationale for the untranslated-sequence
condition in doLoadShortcuts, specifically explaining the low-byte key check and
the special handling of Qt::Key_A before the recordMapping call. Keep the
existing condition and behavior unchanged, and place the explanation alongside
the condition using the surrounding method’s established documentation style.
- Around line 250-286: Update keyCodeToString so the resolved LMGetKbdType
function pointer is checked for null before invocation, matching the guard
already used in keyboardLayout. Return the existing empty-string fallback when
the symbol cannot be resolved, and preserve the current UCKeyTranslate flow
otherwise.
- Around line 89-111: Update keyboardLayout() to release the TISInputSourceRef
returned by TISCopyCurrentKeyboardLayoutInputSource() after obtaining the
Unicode layout data, while keeping that data valid for the translation scope.
Prefer retaining the source/data ownership through the caller’s translation
operation or change keyboardLayout() to return an owning representation instead
of a raw UCKeyboardLayout*.
- Around line 288-311: Update translateToCurrentKeyboardLayout to validate
sequence is non-empty before accessing sequence[0], and validate
keyStr/fromString produces a non-empty sequence before accessing its first
element. Return {} for either empty case while preserving the existing
keyboard-layout and key-lookup handling.

In `@framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp`:
- Around line 373-382: Update ShortcutsModel::shortcutToObject to include a
stable unique identifier for each shortcut, then update EditShortcutModel::load
to compare and resolve shortcuts using that identifier rather than full
QVariantMap equality, ensuring distinct shortcuts with identical title,
sequence, context, and autoRepeat remain distinguishable.
- Around line 182-216: Make ShortcutsModel::apply() update
commandShortcutsRegister() and shortcutsRegister() atomically: if the second
setShortcuts() fails, restore the command register’s previous shortcuts (or use
an existing transaction mechanism) before returning false. Preserve the current
error logging and success behavior while ensuring neither register remains
partially updated.
- Around line 259-286: Update ShortcutsModel::importShortcutsFromFile(),
exportShortcutsToFile(), and resetToDefaultSelectedShortcuts() to process
command shortcuts through commandShortcutsRegister() alongside
shortcutsRegister(). Ensure command shortcut entries are included in
import/export round-trips and reset using the command register’s defaults
instead of being cleared.

In `@framework/shortcuts_v2/shortcutstypes.h`:
- Around line 95-110: Replace the static std::set lookup in needIgnoreKey with a
switch over the ignored Qt::Key values, returning true for each listed key and
false by default. Preserve the existing ignored-key list and function behavior
while removing the set allocation and lookup.
- Around line 74-84: Update sequencesToString and sequencesFromString to use a
serialization delimiter that cannot occur in QKeySequence text, preserving each
multi-stroke shortcut as one sequence during round-tripping. Apply the same
delimiter consistently for joining and splitting, without changing the
surrounding conversion behavior.

In `@framework/shortcuts/internal/shortcutscontroller.cpp`:
- Around line 72-74: Replace the IF_ASSERT_FAILED guard in the shortcut
activation flow with a standard emptiness check for shortcutsForSequence,
returning ActionCode() without asserting when the sequence is unknown. Add a
warning log if consistent with the surrounding logging conventions, while
preserving normal activation for matched sequences.

---

Outside diff comments:
In `@framework/shortcuts/shortcutstypes.h`:
- Around line 54-60: Update Shortcut::operator== to include a comparison of the
active context member alongside action, sequences, standardKey, and autoRepeat,
so shortcuts with different contexts are not considered equal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 96554f4e-cf1f-4a53-a2e2-c5dbe57dd726

📥 Commits

Reviewing files that changed from the base of the PR and between 7d6e9ca and 96c4aaa.

📒 Files selected for processing (56)
  • framework/CMakeLists.txt
  • framework/cmake/MuseDeclareOptions.cmake
  • framework/cmake/muse_framework_config.h.in
  • framework/shortcuts/CMakeLists.txt
  • framework/shortcuts/internal/shortcutsconfiguration.cpp
  • framework/shortcuts/internal/shortcutsconfiguration.h
  • framework/shortcuts/internal/shortcutscontroller.cpp
  • framework/shortcuts/internal/shortcutscontroller.h
  • framework/shortcuts/internal/shortcutsregister.cpp
  • framework/shortcuts/ishortcutsconfiguration.h
  • framework/shortcuts/qml/Muse/Shortcuts/editshortcutmodel.cpp
  • framework/shortcuts/qml/Muse/Shortcuts/internal/ShortcutsList.qml
  • framework/shortcuts/qml/Muse/Shortcuts/shortcutsinstancemodel.cpp
  • framework/shortcuts/qml/Muse/Shortcuts/shortcutsinstancemodel.h
  • framework/shortcuts/qml/Muse/Shortcuts/shortcutsmodel.cpp
  • framework/shortcuts/qml/Muse/Shortcuts/shortcutsmodel.h
  • framework/shortcuts/shortcutsmodule.cpp
  • framework/shortcuts/shortcutsmodule.h
  • framework/shortcuts/shortcutstypes.h
  • framework/shortcuts_v2/CMakeLists.txt
  • framework/shortcuts_v2/README.md
  • framework/shortcuts_v2/api/shortcutsapi.cpp
  • framework/shortcuts_v2/api/shortcutsapi.h
  • framework/shortcuts_v2/icommandshortcutsregister.h
  • framework/shortcuts_v2/internal/commandshortcutsregister.cpp
  • framework/shortcuts_v2/internal/commandshortcutsregister.h
  • framework/shortcuts_v2/internal/shortcutsconfiguration.cpp
  • framework/shortcuts_v2/internal/shortcutsconfiguration.h
  • framework/shortcuts_v2/internal/shortcutscontroller.cpp
  • framework/shortcuts_v2/internal/shortcutscontroller.h
  • framework/shortcuts_v2/internal/shortcutsregister.cpp
  • framework/shortcuts_v2/internal/shortcutsregister.h
  • framework/shortcuts_v2/ishortcutsconfiguration.h
  • framework/shortcuts_v2/ishortcutscontroller.h
  • framework/shortcuts_v2/ishortcutsregister.h
  • framework/shortcuts_v2/ishortcutsresolver.h
  • framework/shortcuts_v2/qml/Muse/Shortcuts/CMakeLists.txt
  • framework/shortcuts_v2/qml/Muse/Shortcuts/EditShortcutDialogContent.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/Shortcuts.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/ShortcutsPage.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/StandardEditShortcutDialog.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/editshortcutmodel.cpp
  • framework/shortcuts_v2/qml/Muse/Shortcuts/editshortcutmodel.h
  • framework/shortcuts_v2/qml/Muse/Shortcuts/internal/ShortcutsBottomPanel.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/internal/ShortcutsList.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/internal/ShortcutsTopPanel.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.h
  • framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm
  • framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsinstancemodel.cpp
  • framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsinstancemodel.h
  • framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp
  • framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.h
  • framework/shortcuts_v2/shortcutcontext.h
  • framework/shortcuts_v2/shortcutsmodule.cpp
  • framework/shortcuts_v2/shortcutsmodule.h
  • framework/shortcuts_v2/shortcutstypes.h
💤 Files with no reviewable changes (9)
  • framework/shortcuts/qml/Muse/Shortcuts/internal/ShortcutsList.qml
  • framework/shortcuts/internal/shortcutscontroller.h
  • framework/shortcuts/ishortcutsconfiguration.h
  • framework/shortcuts/CMakeLists.txt
  • framework/shortcuts/shortcutsmodule.cpp
  • framework/shortcuts/internal/shortcutsconfiguration.h
  • framework/shortcuts/shortcutsmodule.h
  • framework/shortcuts/qml/Muse/Shortcuts/shortcutsinstancemodel.h
  • framework/shortcuts/internal/shortcutsregister.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
framework/shortcuts/shortcutstypes.h (1)

54-60: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore the context comparison in operator==.

During the decoupling of the legacy shortcuts module, the comparison for the context field was accidentally removed from operator==, despite context still being an active member of this struct.

Omitting it means two shortcuts with identical actions and sequences but strictly different contexts will evaluate to true when compared. Based on upstream usages (like ShortcutsModel::resetToDefaultSelectedShortcuts), this false positive will incorrectly trigger loop continue statements and silently skip critical shortcut conflict resolutions.

🐛 Proposed fix to restore struct equality logic
     bool operator ==(const Shortcut& sc) const
     {
         return action == sc.action
+               && context == sc.context
                && sequences == sc.sequences
                && standardKey == sc.standardKey
                && autoRepeat == sc.autoRepeat;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/shortcuts/shortcutstypes.h` around lines 54 - 60, Update
Shortcut::operator== to include a comparison of the active context member
alongside action, sequences, standardKey, and autoRepeat, so shortcuts with
different contexts are not considered equal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@framework/shortcuts_v2/internal/commandshortcutsregister.cpp`:
- Around line 344-358: Replace the ReadResourceLockGuard with
WriteResourceLockGuard in both importFromFile paths:
framework/shortcuts_v2/internal/commandshortcutsregister.cpp lines 344-358 and
framework/shortcuts_v2/internal/shortcutsregister.cpp lines 519-533. Keep the
existing file-copy, error handling, and reload behavior unchanged.
- Around line 159-174: Update
CommandShortcutsRegister::filterAndUpdateAdditionalShortcuts so the
additional-shortcut lookup uses shortcut.command rather than shortcut.action
when searching shortcuts. Preserve the existing refresh and removal behavior,
ensuring matching continues to use the command/scope identity expected by the
register.

In `@framework/shortcuts_v2/internal/shortcutscontroller.cpp`:
- Around line 27-33: Disable unconditional debug logging by changing the
SHORTCUTS_DEBUG configuration used by SC_LOG() in shortcutscontroller.cpp so
production builds resolve to LOGN() rather than LOGDA(). Preserve the existing
conditional macro structure and allow debug logging only through an intentional
build-time configuration.

In `@framework/shortcuts_v2/qml/Muse/Shortcuts/editshortcutmodel.cpp`:
- Around line 47-61: Update the shortcut identity handling in the self-skip loop
and trySave() so entries are matched by a stable unique identifier rather than
full QVariantMap equality. Use the identifier introduced by the shortcutToObject
root-cause fix for the comparisons currently using shortcut == originShortcut
and m_allShortcuts.indexOf(m_conflictShortcut), preserving the existing
conflict-selection behavior for distinct shortcuts with identical fields.
- Around line 183-200: Update checkNewSequenceForConflicts() and trySave() to
use newSequenceInNativeFormat() whenever comparing the edited sequence with
stored shortcut sequences. Preserve the existing conflict detection and
no-change guard logic while ensuring both comparisons use native-text
representations consistently.

In
`@framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm`:
- Around line 376-406: Document the rationale for the untranslated-sequence
condition in doLoadShortcuts, specifically explaining the low-byte key check and
the special handling of Qt::Key_A before the recordMapping call. Keep the
existing condition and behavior unchanged, and place the explanation alongside
the condition using the surrounding method’s established documentation style.
- Around line 250-286: Update keyCodeToString so the resolved LMGetKbdType
function pointer is checked for null before invocation, matching the guard
already used in keyboardLayout. Return the existing empty-string fallback when
the symbol cannot be resolved, and preserve the current UCKeyTranslate flow
otherwise.
- Around line 89-111: Update keyboardLayout() to release the TISInputSourceRef
returned by TISCopyCurrentKeyboardLayoutInputSource() after obtaining the
Unicode layout data, while keeping that data valid for the translation scope.
Prefer retaining the source/data ownership through the caller’s translation
operation or change keyboardLayout() to return an owning representation instead
of a raw UCKeyboardLayout*.
- Around line 288-311: Update translateToCurrentKeyboardLayout to validate
sequence is non-empty before accessing sequence[0], and validate
keyStr/fromString produces a non-empty sequence before accessing its first
element. Return {} for either empty case while preserving the existing
keyboard-layout and key-lookup handling.

In `@framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp`:
- Around line 373-382: Update ShortcutsModel::shortcutToObject to include a
stable unique identifier for each shortcut, then update EditShortcutModel::load
to compare and resolve shortcuts using that identifier rather than full
QVariantMap equality, ensuring distinct shortcuts with identical title,
sequence, context, and autoRepeat remain distinguishable.
- Around line 182-216: Make ShortcutsModel::apply() update
commandShortcutsRegister() and shortcutsRegister() atomically: if the second
setShortcuts() fails, restore the command register’s previous shortcuts (or use
an existing transaction mechanism) before returning false. Preserve the current
error logging and success behavior while ensuring neither register remains
partially updated.
- Around line 259-286: Update ShortcutsModel::importShortcutsFromFile(),
exportShortcutsToFile(), and resetToDefaultSelectedShortcuts() to process
command shortcuts through commandShortcutsRegister() alongside
shortcutsRegister(). Ensure command shortcut entries are included in
import/export round-trips and reset using the command register’s defaults
instead of being cleared.

In `@framework/shortcuts_v2/shortcutstypes.h`:
- Around line 95-110: Replace the static std::set lookup in needIgnoreKey with a
switch over the ignored Qt::Key values, returning true for each listed key and
false by default. Preserve the existing ignored-key list and function behavior
while removing the set allocation and lookup.
- Around line 74-84: Update sequencesToString and sequencesFromString to use a
serialization delimiter that cannot occur in QKeySequence text, preserving each
multi-stroke shortcut as one sequence during round-tripping. Apply the same
delimiter consistently for joining and splitting, without changing the
surrounding conversion behavior.

In `@framework/shortcuts/internal/shortcutscontroller.cpp`:
- Around line 72-74: Replace the IF_ASSERT_FAILED guard in the shortcut
activation flow with a standard emptiness check for shortcutsForSequence,
returning ActionCode() without asserting when the sequence is unknown. Add a
warning log if consistent with the surrounding logging conventions, while
preserving normal activation for matched sequences.

---

Outside diff comments:
In `@framework/shortcuts/shortcutstypes.h`:
- Around line 54-60: Update Shortcut::operator== to include a comparison of the
active context member alongside action, sequences, standardKey, and autoRepeat,
so shortcuts with different contexts are not considered equal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 96554f4e-cf1f-4a53-a2e2-c5dbe57dd726

📥 Commits

Reviewing files that changed from the base of the PR and between 7d6e9ca and 96c4aaa.

📒 Files selected for processing (56)
  • framework/CMakeLists.txt
  • framework/cmake/MuseDeclareOptions.cmake
  • framework/cmake/muse_framework_config.h.in
  • framework/shortcuts/CMakeLists.txt
  • framework/shortcuts/internal/shortcutsconfiguration.cpp
  • framework/shortcuts/internal/shortcutsconfiguration.h
  • framework/shortcuts/internal/shortcutscontroller.cpp
  • framework/shortcuts/internal/shortcutscontroller.h
  • framework/shortcuts/internal/shortcutsregister.cpp
  • framework/shortcuts/ishortcutsconfiguration.h
  • framework/shortcuts/qml/Muse/Shortcuts/editshortcutmodel.cpp
  • framework/shortcuts/qml/Muse/Shortcuts/internal/ShortcutsList.qml
  • framework/shortcuts/qml/Muse/Shortcuts/shortcutsinstancemodel.cpp
  • framework/shortcuts/qml/Muse/Shortcuts/shortcutsinstancemodel.h
  • framework/shortcuts/qml/Muse/Shortcuts/shortcutsmodel.cpp
  • framework/shortcuts/qml/Muse/Shortcuts/shortcutsmodel.h
  • framework/shortcuts/shortcutsmodule.cpp
  • framework/shortcuts/shortcutsmodule.h
  • framework/shortcuts/shortcutstypes.h
  • framework/shortcuts_v2/CMakeLists.txt
  • framework/shortcuts_v2/README.md
  • framework/shortcuts_v2/api/shortcutsapi.cpp
  • framework/shortcuts_v2/api/shortcutsapi.h
  • framework/shortcuts_v2/icommandshortcutsregister.h
  • framework/shortcuts_v2/internal/commandshortcutsregister.cpp
  • framework/shortcuts_v2/internal/commandshortcutsregister.h
  • framework/shortcuts_v2/internal/shortcutsconfiguration.cpp
  • framework/shortcuts_v2/internal/shortcutsconfiguration.h
  • framework/shortcuts_v2/internal/shortcutscontroller.cpp
  • framework/shortcuts_v2/internal/shortcutscontroller.h
  • framework/shortcuts_v2/internal/shortcutsregister.cpp
  • framework/shortcuts_v2/internal/shortcutsregister.h
  • framework/shortcuts_v2/ishortcutsconfiguration.h
  • framework/shortcuts_v2/ishortcutscontroller.h
  • framework/shortcuts_v2/ishortcutsregister.h
  • framework/shortcuts_v2/ishortcutsresolver.h
  • framework/shortcuts_v2/qml/Muse/Shortcuts/CMakeLists.txt
  • framework/shortcuts_v2/qml/Muse/Shortcuts/EditShortcutDialogContent.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/Shortcuts.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/ShortcutsPage.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/StandardEditShortcutDialog.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/editshortcutmodel.cpp
  • framework/shortcuts_v2/qml/Muse/Shortcuts/editshortcutmodel.h
  • framework/shortcuts_v2/qml/Muse/Shortcuts/internal/ShortcutsBottomPanel.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/internal/ShortcutsList.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/internal/ShortcutsTopPanel.qml
  • framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.h
  • framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm
  • framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsinstancemodel.cpp
  • framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsinstancemodel.h
  • framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp
  • framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.h
  • framework/shortcuts_v2/shortcutcontext.h
  • framework/shortcuts_v2/shortcutsmodule.cpp
  • framework/shortcuts_v2/shortcutsmodule.h
  • framework/shortcuts_v2/shortcutstypes.h
💤 Files with no reviewable changes (9)
  • framework/shortcuts/qml/Muse/Shortcuts/internal/ShortcutsList.qml
  • framework/shortcuts/internal/shortcutscontroller.h
  • framework/shortcuts/ishortcutsconfiguration.h
  • framework/shortcuts/CMakeLists.txt
  • framework/shortcuts/shortcutsmodule.cpp
  • framework/shortcuts/internal/shortcutsconfiguration.h
  • framework/shortcuts/shortcutsmodule.h
  • framework/shortcuts/qml/Muse/Shortcuts/shortcutsinstancemodel.h
  • framework/shortcuts/internal/shortcutsregister.cpp
🛑 Comments failed to post (15)
framework/shortcuts_v2/internal/commandshortcutsregister.cpp (2)

159-174: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd shortcutstypes.h
cat -n framework/shortcuts_v2/shortcutstypes.h 2>/dev/null
echo "---"
rg -n 'operator==' framework/shortcuts_v2 framework/shortcuts 2>/dev/null
echo "---"
rg -n 'struct Shortcut\b' -A 40 framework/shortcuts_v2/shortcutstypes.h 2>/dev/null

Repository: musescore/muse_framework

Length of output: 6276


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== commandshortcutsregister.cpp =="
cat -n framework/shortcuts_v2/internal/commandshortcutsregister.cpp | sed -n '145,190p'

echo
echo "== action-based counterpart =="
cat -n framework/shortcuts_v2/internal/shortcutsregister.cpp | sed -n '270,315p'

echo
echo "== string comparison overloads / find usage =="
rg -n 'operator==.*std::string|std::string.*operator==|std::find\\(' framework/shortcuts_v2 framework/shortcuts

echo
echo "== command shortcut persistence / loading =="
rg -n 'm_additionalShortcutsMap|additionalShortcuts|filterAndUpdateAdditionalShortcuts|mergeShortcuts|makeUnique|writeToFile|readFromFile' framework/shortcuts_v2/internal

Repository: musescore/muse_framework

Length of output: 3827


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat -n framework/shortcuts_v2/internal/commandshortcutsregister.cpp | sed -n '145,190p'
echo
cat -n framework/shortcuts_v2/internal/shortcutsregister.cpp | sed -n '270,315p'
echo
rg -n 'operator==.*std::string|std::string.*operator==|std::find\(' framework/shortcuts_v2 framework/shortcuts

Repository: musescore/muse_framework

Length of output: 4685


Use shortcut.command in the additional-shortcut lookup. std::find(..., shortcut.action) matches on action via the Shortcut(std::string) constructor, but this register is keyed by command/scope, so additional shortcuts won’t be refreshed or removed correctly.

🧰 Tools
🪛 Clang (14.0.6)

[warning] 159-159: use a trailing return type for this function

(modernize-use-trailing-return-type)


[warning] 161-161: variable 'noAdditionalShortcuts' is not initialized

(cppcoreguidelines-init-variables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/shortcuts_v2/internal/commandshortcutsregister.cpp` around lines
159 - 174, Update CommandShortcutsRegister::filterAndUpdateAdditionalShortcuts
so the additional-shortcut lookup uses shortcut.command rather than
shortcut.action when searching shortcuts. Preserve the existing refresh and
removal behavior, ensuring matching continues to use the command/scope identity
expected by the register.

344-358: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use a write lock in both importFromFile paths. They copy into the shared user-data file while holding mi::ReadResourceLockGuard; this should match the other write paths and use mi::WriteResourceLockGuard instead.

🧰 Tools
🪛 Clang (14.0.6)

[warning] 344-344: use a trailing return type for this function

(modernize-use-trailing-return-type)

📍 Affects 2 files
  • framework/shortcuts_v2/internal/commandshortcutsregister.cpp#L344-L358 (this comment)
  • framework/shortcuts_v2/internal/shortcutsregister.cpp#L519-L533
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/shortcuts_v2/internal/commandshortcutsregister.cpp` around lines
344 - 358, Replace the ReadResourceLockGuard with WriteResourceLockGuard in both
importFromFile paths:
framework/shortcuts_v2/internal/commandshortcutsregister.cpp lines 344-358 and
framework/shortcuts_v2/internal/shortcutsregister.cpp lines 519-533. Keep the
existing file-copy, error handling, and reload behavior unchanged.
framework/shortcuts_v2/internal/shortcutscontroller.cpp (1)

27-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Debug logging hardcoded on for all builds.

SHORTCUTS_DEBUG is hardcoded to 1, so SC_LOG() always resolves to LOGDA() (always-visible debug log) rather than the presumably-quieter LOGN(). This means every activate() call logs sequence/allowed-shortcut counts (lines 57, 64, 76) in production builds. This looks like a debug leftover.

♻️ Proposed fix
-#define SHORTCUTS_DEBUG 1
+#define SHORTCUTS_DEBUG 0
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`#define` SHORTCUTS_DEBUG 0

`#if` SHORTCUTS_DEBUG
`#define` SC_LOG() LOGDA() << "[SC] "
`#else`
`#define` SC_LOG() LOGN()
`#endif`
🧰 Tools
🪛 Clang (14.0.6)

[warning] 27-27: macro 'SHORTCUTS_DEBUG' used to declare a constant; consider using a 'constexpr' constant

(cppcoreguidelines-macro-usage)


[warning] 30-30: function-like macro 'SC_LOG' used; consider a 'constexpr' template function

(cppcoreguidelines-macro-usage)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/shortcuts_v2/internal/shortcutscontroller.cpp` around lines 27 -
33, Disable unconditional debug logging by changing the SHORTCUTS_DEBUG
configuration used by SC_LOG() in shortcutscontroller.cpp so production builds
resolve to LOGN() rather than LOGDA(). Preserve the existing conditional macro
structure and allow debug logging only through an intentional build-time
configuration.
framework/shortcuts_v2/qml/Muse/Shortcuts/editshortcutmodel.cpp (2)

47-61: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Self/conflict matching relies on full-map value equality with no unique identifier.

shortcut == originShortcut (Line 51) and later m_allShortcuts.indexOf(m_conflictShortcut) in trySave() (Line 253) rely on QVariantMap equality across title/sequence/context/autoRepeat. The map built by ShortcutsModel::shortcutToObject carries no unique id, so two distinct shortcuts that happen to share those fields could be indistinguishable, causing the wrong entry to be skipped or cleared. See consolidated comment on shortcutsmodel.cpp's shortcutToObject for the root-cause fix.

🧰 Tools
🪛 Clang (14.0.6)

[warning] 47-47: variable 'originShortcutMap' is not initialized

(cppcoreguidelines-init-variables)


[warning] 48-48: variable 'originCtx' is not initialized

(cppcoreguidelines-init-variables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/shortcuts_v2/qml/Muse/Shortcuts/editshortcutmodel.cpp` around lines
47 - 61, Update the shortcut identity handling in the self-skip loop and
trySave() so entries are matched by a stable unique identifier rather than full
QVariantMap equality. Use the identifier introduced by the shortcutToObject
root-cause fix for the comparisons currently using shortcut == originShortcut
and m_allShortcuts.indexOf(m_conflictShortcut), preserving the existing
conflict-selection behavior for distinct shortcuts with identical fields.

183-200: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

# Locate the relevant files and inspect the exact implementations.
git ls-files framework/shortcuts_v2/qml/Muse/Shortcuts/editshortcutmodel.cpp \
             framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp \
             framework/shortcuts_v2/qml/Muse/Shortcuts/*.h \
             framework/shortcuts/shortcutstypes.h \
             | sed 's#^`#FILE`: #'

echo
echo "=== editshortcutmodel.cpp (selected lines) ==="
sed -n '150,280p' framework/shortcuts_v2/qml/Muse/Shortcuts/editshortcutmodel.cpp

echo
echo "=== shortcutsmodel.cpp (selected sequence serialization) ==="
sed -n '1,240p' framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp

echo
echo "=== shortcutstypes.h (sequence helpers) ==="
sed -n '1,260p' framework/shortcuts/shortcutstypes.h

Repository: musescore/muse_framework

Length of output: 14458


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
p = Path('framework/shortcuts_v2/qml/Muse/Shortcuts/editshortcutmodel.cpp')
text = p.read_text()
for needle in [
    'newSequence()',
    'newSequenceInNativeFormat()',
    'checkNewSequenceForConflicts()',
    'trySave()',
    'm_originSequence',
]:
    print(f'=== {needle} ===')
    for i, line in enumerate(text.splitlines(), 1):
        if needle in line:
            for j in range(max(1, i-4), min(len(text.splitlines()), i+12)+1):
                print(f'{j}: {text.splitlines()[j-1]}')
            break
PY

Repository: musescore/muse_framework

Length of output: 2929


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
p = Path('framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp')
text = p.read_text().splitlines()
for i, line in enumerate(text, 1):
    if 'sequencesToNativeText' in line or 'shortcutToObject' in line or 'sequence' in line and 'NativeText' in line:
        start = max(1, i-6)
        end = min(len(text), i+12)
        print(f'--- around line {i} ---')
        for j in range(start, end+1):
            print(f'{j}: {text[j-1]}')
PY

Repository: musescore/muse_framework

Length of output: 4353


🌐 Web query:

Qt QKeySequence toString default format NativeText PortableText documentation

💡 Result:

In the Qt framework, the QKeySequence::toString function is used to convert a key sequence into a human-readable string [1][2]. The function signature is: QString toString(QKeySequence::SequenceFormat format = PortableText) const [1][3] The format parameter is an enum of type QKeySequence::SequenceFormat, which accepts two primary values: 1. PortableText: This is the default format [1][4]. It is designed to be platform-independent, using standard key names (e.g., "Ctrl", "Shift") that remain consistent across different operating systems [4]. It is recommended for serializing key sequences for storage (e.g., in configuration files) to ensure they can be reliably loaded on any platform [4]. 2. NativeText: This format uses native key names and symbols specific to the current operating system [4]. For example, on macOS, it may use platform-specific symbols like the Command key (⌘) icon rather than text like "Ctrl" or "Cmd" [2][4]. This format is typically used for displaying shortcuts to the user in elements like menus or tooltips to ensure the application feels integrated into the user's OS [2][4]. Summary: Use PortableText when you need a consistent, portable string representation for storage or cross-platform data exchange, and use NativeText when you need to display the shortcut in a manner that matches the user's native environment [4].

Citations:


Use native-text for these comparisons. checkNewSequenceForConflicts() and trySave() compare newSequence() (PortableText) against values stored as NativeText, so the duplicate-shortcut check and the no-change guard can diverge on platforms like macOS. Switch both paths to newSequenceInNativeFormat().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/shortcuts_v2/qml/Muse/Shortcuts/editshortcutmodel.cpp` around lines
183 - 200, Update checkNewSequenceForConflicts() and trySave() to use
newSequenceInNativeFormat() whenever comparing the edited sequence with stored
shortcut sequences. Preserve the existing conflict detection and no-change guard
logic while ensuring both comparisons use native-text representations
consistently.
framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm (4)

89-111: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm"

echo "== line count =="
wc -l "$file"

echo
echo "== relevant excerpt =="
sed -n '80,130p' "$file"

echo
echo "== call sites of keyboardLayout =="
rg -n "keyboardLayout\(" "$file"

echo
echo "== translateToCurrentKeyboardLayout excerpt =="
sed -n '280,320p' "$file"

echo
echo "== constructor / locale change excerpt =="
sed -n '1,80p' "$file"

Repository: musescore/muse_framework

Length of output: 5832


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '89,111p' framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm

Repository: musescore/muse_framework

Length of output: 1225


keyboardLayout() leaks the TISInputSourceRef returned by TISCopyCurrentKeyboardLayoutInputSource(). It’s a Copy API, but currentKeyboard is never released, so every shortcut translation leaks another input-source reference. Keep the source/data owned for the translation scope or change the helper to return an owning object instead of a raw UCKeyboardLayout*.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm`
around lines 89 - 111, Update keyboardLayout() to release the TISInputSourceRef
returned by TISCopyCurrentKeyboardLayoutInputSource() after obtaining the
Unicode layout data, while keeping that data valid for the translation scope.
Prefer retaining the source/data ownership through the caller’s translation
operation or change keyboardLayout() to return an owning representation instead
of a raw UCKeyboardLayout*.

250-286: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -e
file="framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm"

# Show the relevant area around keyCodeToString and keyboardLayout.
nl -ba "$file" | sed -n '80,140p;240,310p'

# Find all references to LMGetKbdType and related Carbon pointer resolution.
rg -n "LMGetKbdType|CFBundleGetFunctionPointerForName|keyboardLayout\(" "$file"

Repository: musescore/muse_framework

Length of output: 750


🏁 Script executed:

set -e
file="framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm"

# Read the relevant ranges with line numbers using awk (since nl is unavailable).
awk 'NR>=85 && NR<=115 { printf "%4d  %s\n", NR, $0 } NR>=248 && NR<=300 { printf "%4d  %s\n", NR, $0 }' "$file"

Repository: musescore/muse_framework

Length of output: 3649


Guard LMGetKbdType() before use.
keyCodeToString() can still crash here if Carbon fails to resolve the symbol. Add the same null-check used in keyboardLayout() before calling it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm`
around lines 250 - 286, Update keyCodeToString so the resolved LMGetKbdType
function pointer is checked for null before invocation, matching the guard
already used in keyboardLayout. Return the existing empty-string fallback when
the symbol cannot be resolved, and preserve the current UCKeyTranslate flow
otherwise.

288-311: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm'

echo '--- file outline ---'
ast-grep outline "$file" --view expanded || true

echo '--- relevant lines 250-340 ---'
sed -n '250,340p' "$file" | cat -n

echo '--- doActivate vicinity 400-430 ---'
sed -n '400,430p' "$file" | cat -n

echo '--- search translateToCurrentKeyboardLayout references ---'
rg -n "translateToCurrentKeyboardLayout|m_macSequenceMap|QKeySequence::fromString|keyCodeToString|specialKeysMap|sequence\[0\]" "$file"

Repository: musescore/muse_framework

Length of output: 6336


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm'

echo '--- specialKeysMap and mapping loop 1-120, 360-390 ---'
sed -n '1,120p' "$file" | cat -n
echo '---'
sed -n '360,390p' "$file" | cat -n

Repository: musescore/muse_framework

Length of output: 6983


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm'
sed -n '388,420p' "$file" | cat -n

Repository: musescore/muse_framework

Length of output: 1802


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
# Probe general Qt semantics from available local knowledge? not possible.
print("Need output from repository read only inspection only.")
PY

Repository: musescore/muse_framework

Length of output: 217


🌐 Web query:

Qt QKeySequence operator[] empty sequence count assert index < 4 semantics

💡 Result:

In Qt, the QKeySequence::operator[] provides read-only access to the individual key combinations (represented as QKeyCombination objects in Qt 6 and later, or integers in earlier versions) that comprise the sequence [1][2]. A QKeySequence can store up to four such elements [2]. Regarding the semantics of accessing an index: 1. Valid Range: The index parameter must be in the range [3] [2][4]. 2. Assertions: Historically and in standard practice, the implementation uses Q_ASSERT (or similar diagnostic mechanisms in debug builds) to enforce the index < 4 constraint [2][4]. Accessing an index outside this range results in undefined behavior, which often manifests as a crash or memory corruption in optimized/release builds where assertions may be disabled [2]. 3. Empty Sequences: Calling operator[] on an empty QKeySequence (where count returns 0) does not fundamentally change the index requirement [1][5]. If you access index 0 on an empty sequence, you are still accessing an invalid position, and the same bounds-checking logic (or lack thereof in release builds) applies. It is considered best practice to check the sequence's size using the count method before attempting to access elements via operator[] to ensure the index is within the valid range [1][2]. For robust application logic, consider using higher-level methods like matches when comparing sequences, as direct indexing is generally intended for serialization or low-level inspection of the sequence contents [2].

Citations:


Guard both QKeySequence::operator[] accesses here sequence[0] and QKeySequence::fromString(...)[0] both assume a non-empty sequence. keyCodeToString() can return "" (for kVK_Help or on UCKeyTranslate failure), so the translated sequence can be empty and the second index is undefined behavior. Return {} before indexing in either case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm`
around lines 288 - 311, Update translateToCurrentKeyboardLayout to validate
sequence is non-empty before accessing sequence[0], and validate
keyStr/fromString produces a non-empty sequence before accessing its first
element. Return {} for either empty case while preserving the existing
keyboard-layout and key-lookup handling.

376-406: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Undocumented magic condition mixing bit-masking and a literal key comparison.

Line 386's !(untranslatedSequence[0].key() & 0xff) || untranslatedSequence[0].key() == Qt::Key_A has no explanation, unlike the rest of doLoadShortcuts() which is unusually well-documented (lines 325-364). It's not obvious why the low byte of the key value or specifically Key_A determine whether the untranslated sequence should also be self-mapped, which makes this fragile to maintain or extend for other special keys.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@framework/shortcuts_v2/qml/Muse/Shortcuts/platform/macos/macosshortcutsinstancemodel.mm`
around lines 376 - 406, Document the rationale for the untranslated-sequence
condition in doLoadShortcuts, specifically explaining the low-byte key check and
the special handling of Qt::Key_A before the recordMapping call. Keep the
existing condition and behavior unchanged, and place the explanation alongside
the condition using the surrounding method’s established documentation style.
framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp (3)

182-216: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file around the reported lines
sed -n '150,260p' framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp

# Locate the register interfaces and implementations used by apply()
rg -n "class .*ShortcutsRegister|setShortcuts\(|commandShortcutsRegister\(|shortcutsRegister\(" framework/shortcuts_v2 -S

# Map candidate files for the register types
fd -a ".*shortcuts.*(register|model).*" framework/shortcuts_v2

Repository: musescore/muse_framework

Length of output: 8924


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the surrounding declarations for the model and the register abstractions
ast-grep outline framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp --view expanded || true

# Show only the files that define the register classes/interfaces
rg -n "setShortcuts\\(" framework/shortcuts_v2 -g'*.h' -g'*.cpp' -g'*.qml' -S

Repository: musescore/muse_framework

Length of output: 3285


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# If available, inspect the register implementations to see whether partial writes can be rolled back
for f in $(fd -a "shortcuts.*(register|model).*" framework/shortcuts_v2 2>/dev/null); do
  echo "===== $f ====="
  sed -n '1,260p' "$f" | sed -n '/setShortcuts\|rollback\|transaction\|apply()/p'
done

Repository: musescore/muse_framework

Length of output: 1977


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect repository tree for the relevant implementation files
git ls-files framework/shortcuts_v2 | sed -n '1,200p'

# Find references to the two registers and their persistence behavior
rg -n "commandShortcutsRegister|shortcutsRegister|class .*Register|setShortcuts" framework/shortcuts_v2 -S

Repository: musescore/muse_framework

Length of output: 8857


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the actual persistence logic in both register implementations
sed -n '285,360p' framework/shortcuts_v2/internal/commandshortcutsregister.cpp
echo '---'
sed -n '380,460p' framework/shortcuts_v2/internal/shortcutsregister.cpp

Repository: musescore/muse_framework

Length of output: 4248


Make apply() atomic across both shortcut registers. If the second setShortcuts() fails, the command shortcuts stay persisted and the two stores end up out of sync; add rollback or commit both updates as one unit.

🧰 Tools
🪛 Clang (14.0.6)

[warning] 182-182: use a trailing return type for this function

(modernize-use-trailing-return-type)


[warning] 182-182: method 'apply' can be made static

(readability-convert-member-functions-to-static)


[warning] 186-186: variable 'shortcuts' is not initialized

(cppcoreguidelines-init-variables)


[warning] 201-201: variable 'shortcuts' is not initialized

(cppcoreguidelines-init-variables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp` around lines
182 - 216, Make ShortcutsModel::apply() update commandShortcutsRegister() and
shortcutsRegister() atomically: if the second setShortcuts() fails, restore the
command register’s previous shortcuts (or use an existing transaction mechanism)
before returning false. Preserve the current error logging and success behavior
while ensuring neither register remains partially updated.

259-286: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and related symbols first.
printf '\n== shortcutsmodel.cpp outline ==\n'
ast-grep outline framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp --view expanded || true

printf '\n== search for commandShortcutsRegister usages ==\n'
rg -n "commandShortcutsRegister|importFromFile|exportToFile|defaultShortcut|resetToDefaultSelectedShortcuts|apply\(|reset\(" framework/shortcuts_v2 -S

printf '\n== file list for shortcuts_v2 ==\n'
git ls-files framework/shortcuts_v2 | sed -n '1,200p'

Repository: musescore/muse_framework

Length of output: 11842


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,260p' framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp
printf '\n---\n'
sed -n '260,420p' framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp
printf '\n---\n'
rg -n "commandShortcutsRegister|defaultShortcut|importFromFile|exportToFile|apply\(|reset\(" framework/shortcuts_v2 -S

Repository: musescore/muse_framework

Length of output: 18506


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== commandshortcutsregister.h ==\n'
sed -n '1,220p' framework/shortcuts_v2/internal/commandshortcutsregister.h

printf '\n== commandshortcutsregister.cpp (relevant sections) ==\n'
sed -n '1,220p' framework/shortcuts_v2/internal/commandshortcutsregister.cpp
printf '\n---\n'
sed -n '220,420p' framework/shortcuts_v2/internal/commandshortcutsregister.cpp

printf '\n== shortcutsregister.cpp default lookup and import/export ==\n'
sed -n '470,560p' framework/shortcuts_v2/internal/shortcutsregister.cpp

Repository: musescore/muse_framework

Length of output: 16019


Handle command shortcuts in import/export and reset-to-default

importShortcutsFromFile()/exportShortcutsToFile() only go through shortcutsRegister(), so command shortcuts never round-trip. resetToDefaultSelectedShortcuts() also uses shortcutsRegister()->defaultShortcut(shortcut.action), which leaves command items without a default and clears their sequences instead of restoring them. Wire these paths through commandShortcutsRegister() as well.

🧰 Tools
🪛 Clang (14.0.6)

[warning] 259-259: method 'importShortcutsFromFile' can be made static

(readability-convert-member-functions-to-static)


[warning] 271-271: method 'exportShortcutsToFile' can be made static

(readability-convert-member-functions-to-static)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp` around lines
259 - 286, Update ShortcutsModel::importShortcutsFromFile(),
exportShortcutsToFile(), and resetToDefaultSelectedShortcuts() to process
command shortcuts through commandShortcutsRegister() alongside
shortcutsRegister(). Ensure command shortcut entries are included in
import/export round-trips and reset using the command register’s defaults
instead of being cleared.

373-382: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Shortcut identity objects lack a unique key.

shortcutToObject exposes only title/sequence/context/autoRepeat. EditShortcutModel (in editshortcutmodel.cpp) uses full-map equality on these objects to detect "is this the origin shortcut" and to resolve m_allShortcuts.indexOf(m_conflictShortcut) for clearing the conflicting entry. Two distinct shortcuts sharing these four fields are indistinguishable, risking the wrong entry being treated as self or cleared.

🛠️ Suggested fix: add a stable identifier
 QVariant ShortcutsModel::shortcutToObject(const Item& item) const
 {
     QVariantMap obj;
+    obj["actionCode"] = QString::fromStdString(item.shortcut.action);
+    obj["command"] = QString::fromStdString(item.shortcut.command);
     obj["title"] = item.title;
     obj["sequence"] = item.sequence;
     obj["context"] = QString::fromStdString(item.shortcut.context);
     obj["autoRepeat"] = item.shortcut.autoRepeat;

     return obj;
 }

Then compare on that identifier in EditShortcutModel::load() instead of full-map equality.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

QVariant ShortcutsModel::shortcutToObject(const Item& item) const
{
    QVariantMap obj;
    obj["actionCode"] = QString::fromStdString(item.shortcut.action);
    obj["command"] = QString::fromStdString(item.shortcut.command);
    obj["title"] = item.title;
    obj["sequence"] = item.sequence;
    obj["context"] = QString::fromStdString(item.shortcut.context);
    obj["autoRepeat"] = item.shortcut.autoRepeat;

    return obj;
}
🧰 Tools
🪛 Clang (14.0.6)

[warning] 373-373: use a trailing return type for this function

(modernize-use-trailing-return-type)


[warning] 375-375: variable 'obj' is not initialized

(cppcoreguidelines-init-variables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp` around lines
373 - 382, Update ShortcutsModel::shortcutToObject to include a stable unique
identifier for each shortcut, then update EditShortcutModel::load to compare and
resolve shortcuts using that identifier rather than full QVariantMap equality,
ensuring distinct shortcuts with identical title, sequence, context, and
autoRepeat remain distinguishable.
framework/shortcuts_v2/shortcutstypes.h (2)

74-84: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1,180p' framework/shortcuts_v2/shortcutstypes.h
printf '\n--- usages ---\n'
rg -n "sequencesToString|sequencesFromString" -S .

Repository: musescore/muse_framework

Length of output: 5951


🌐 Web query:

Qt QKeySequence string format comma space multi-key chord listToString NativeText documentation

💡 Result:

In Qt, QKeySequence supports multi-key chords (sequences of up to four key codes) represented as a string by separating individual key codes with commas [1][2]. When using the toString method, these sequences are joined by a comma followed by a space (e.g., "Alt+X, Ctrl+Y, Z") [2]. The static method QKeySequence::listToString is used to convert a QList of QKeySequence objects into a single string representation based on a specified SequenceFormat [3][4]. Regarding the SequenceFormat enum: 1. NativeText (value 0): Represents the key sequence as a platform-specific string [5][1]. This format is translated and, on macOS, is designed to resemble the key sequences shown in system menu bars [5][1]. It is recommended for displaying shortcuts to the user [5][1]. 2. PortableText (value 1): Represents the key sequence in a "portable" format suitable for reading from and writing to files [5][1]. In many environments, such as Windows and X11, this format often appears similar to the native text [5][1]. For parsing strings back into sequences, QKeySequence provides the fromString method [3][6], and for parsing lists of sequences, QKeySequence::listFromString is available [3][6]. The strings recognized by these methods include standard modifier names like "Ctrl", "Shift", "Alt", and "Meta", along with their translated equivalents [1][2].

Citations:


🏁 Script executed:

sed -n '1,140p' framework/shortcuts/shortcutstypes.h
printf '\n--- v2 editshortcutmodel ---\n'
sed -n '170,220p' framework/shortcuts_v2/qml/Muse/Shortcuts/editshortcutmodel.cpp
printf '\n--- v2 shortcutsmodel ---\n'
sed -n '270,320p' framework/shortcuts_v2/qml/Muse/Shortcuts/shortcutsmodel.cpp
printf '\n--- legacy editshortcutmodel ---\n'
sed -n '180,220p' framework/shortcuts/qml/Muse/Shortcuts/editshortcutmodel.cpp
printf '\n--- legacy shortcutsmodel ---\n'
sed -n '210,240p' framework/shortcuts/qml/Muse/Shortcuts/shortcutsmodel.cpp

Repository: musescore/muse_framework

Length of output: 8980


🏁 Script executed:

python3 - <<'PY'
samples = [
    "Ctrl+K, Ctrl+S",
    "Ctrl+Alt+K",
    "Ctrl+K, Ctrl+S; Alt+X",
]
for s in samples:
    print(s, "->", s.split(", "))
PY

Repository: musescore/muse_framework

Length of output: 283


🏁 Script executed:

python3 - <<'PY'
samples = [
    "Ctrl+K, Ctrl+S",
    "Ctrl+Alt+K",
    "Ctrl+K, Ctrl+S; Alt+X",
]
for s in samples:
    print(repr(s), "->", s.split(", "))
PY

Repository: musescore/muse_framework

Length of output: 289


Use a delimiter that doesn’t conflict with QKeySequence text ", " breaks round-tripping for multi-stroke shortcuts like Ctrl+K, Ctrl+S, because loading will split a single chord into separate entries.

Proposed fix
     static std::string sequencesToString(const std::vector<std::string>& seqs)
     {
-        return muse::strings::join(seqs, ", ");
+        return muse::strings::join(seqs, "; ");
     }
 
     static std::vector<std::string> sequencesFromString(const std::string& str)
     {
         std::vector<std::string> seqs;
-        muse::strings::split(str, seqs, ", ");
+        muse::strings::split(str, seqs, "; ");
         return seqs;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    static std::string sequencesToString(const std::vector<std::string>& seqs)
    {
        return muse::strings::join(seqs, "; ");
    }

    static std::vector<std::string> sequencesFromString(const std::string& str)
    {
        std::vector<std::string> seqs;
        muse::strings::split(str, seqs, "; ");
        return seqs;
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/shortcuts_v2/shortcutstypes.h` around lines 74 - 84, Update
sequencesToString and sequencesFromString to use a serialization delimiter that
cannot occur in QKeySequence text, preserving each multi-stroke shortcut as one
sequence during round-tripping. Apply the same delimiter consistently for
joining and splitting, without changing the surrounding conversion behavior.

95-110: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Use a switch statement instead of std::set for performance.

Creating and searching a static const std::set requires heap allocations upon initialization and tree traversal on lookup. Since Qt::Key is an enum, a switch statement evaluates in O(1) time, is more cache-friendly, and completely avoids the static initialization overhead on the hot path.

⚡ Proposed refactor
 inline bool needIgnoreKey(Qt::Key key)
 {
-    static const std::set<Qt::Key> ignoredKeys {
-        Qt::Key_Shift,
-        Qt::Key_Control,
-        Qt::Key_Meta,
-        Qt::Key_Alt,
-        Qt::Key_AltGr,
-        Qt::Key_CapsLock,
-        Qt::Key_NumLock,
-        Qt::Key_ScrollLock,
-        Qt::Key_unknown
-    };
-
-    return ignoredKeys.find(key) != ignoredKeys.end();
+    switch (key) {
+        case Qt::Key_Shift:
+        case Qt::Key_Control:
+        case Qt::Key_Meta:
+        case Qt::Key_Alt:
+        case Qt::Key_AltGr:
+        case Qt::Key_CapsLock:
+        case Qt::Key_NumLock:
+        case Qt::Key_ScrollLock:
+        case Qt::Key_unknown:
+            return true;
+        default:
+            return false;
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

inline bool needIgnoreKey(Qt::Key key)
{
    switch (key) {
        case Qt::Key_Shift:
        case Qt::Key_Control:
        case Qt::Key_Meta:
        case Qt::Key_Alt:
        case Qt::Key_AltGr:
        case Qt::Key_CapsLock:
        case Qt::Key_NumLock:
        case Qt::Key_ScrollLock:
        case Qt::Key_unknown:
            return true;
        default:
            return false;
    }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/shortcuts_v2/shortcutstypes.h` around lines 95 - 110, Replace the
static std::set lookup in needIgnoreKey with a switch over the ignored Qt::Key
values, returning true for each listed key and false by default. Preserve the
existing ignored-key list and function behavior while removing the set
allocation and lookup.
framework/shortcuts/internal/shortcutscontroller.cpp (1)

72-74: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Avoid asserting on unknown shortcut sequences.

Using IF_ASSERT_FAILED elevates an empty sequence match to an assertion failure. Since activate can potentially be invoked externally (e.g., via the script API or edge cases in QML input handling), passing an unknown sequence will now trigger an assert in debug builds.

Consider restoring a standard if check and a warning log if runtime invocation with arbitrary strings is expected.

🛡️ Proposed fix
-    IF_ASSERT_FAILED(!shortcutsForSequence.empty()) {
+    if (shortcutsForSequence.empty()) {
+        LOGW() << "No shortcuts found for sequence" << sequence;
         return ActionCode();
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (shortcutsForSequence.empty()) {
        LOGW() << "No shortcuts found for sequence" << sequence;
        return ActionCode();
    }
🧰 Tools
🪛 Clang (14.0.6)

[warning] 73-73: avoid repeating the return type from the declaration; use a braced initializer list instead

(modernize-return-braced-init-list)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/shortcuts/internal/shortcutscontroller.cpp` around lines 72 - 74,
Replace the IF_ASSERT_FAILED guard in the shortcut activation flow with a
standard emptiness check for shortcutsForSequence, returning ActionCode()
without asserting when the sequence is unknown. Add a warning log if consistent
with the surrounding logging conventions, while preserving normal activation for
matched sequences.

@igorkorsukov igorkorsukov merged commit aee9f67 into musescore:main Jul 14, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants