Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2026 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include "shortcutresolver.h"
23 :
24 : #include <functional>
25 :
26 : #include "global/containers.h"
27 :
28 : #include "log.h"
29 :
30 : using namespace muse;
31 : using namespace muse::shortcuts;
32 : using namespace mu::appshell;
33 :
34 : static const std::string NAVIGATION_SCOPE = "NAVIGATION";
35 : static const std::string PLAYBACK_SCOPE = "PLAYBACK";
36 : static const std::string NOTATION_PAGE_SCOPE = "NOTATION_PAGE";
37 : static const std::string NOTATION_SCOPE = "NOTATION";
38 : static const std::string NOTATION_TAB_SCOPE = "NOTATION_TAB";
39 : static const std::string NOTATION_SELECTION_SCOPE = "NOTATION_SELECTION";
40 : static const std::string NOTATION_SINGLE_SELECTION_SCOPE = "NOTATION_SINGLE_SELECTION";
41 : static const std::string NOTATION_RANGE_SELECTION_SCOPE = "NOTATION_RANGE_SELECTION";
42 : static const std::string NOTATION_NOTE_INPUT_SCOPE = "NOTATION_NOTE_INPUT";
43 : static const std::string NOTATION_TEXT_EDITING_SCOPE = "NOTATION_TEXT_EDITING";
44 : static const std::string PROJECT_SCOPE = "PROJECT";
45 : static const std::string APP_SCOPE = "APP";
46 : static const std::string PALETTE_SCOPE = "PALETTE";
47 : static const std::string INSTRUMENTS_SCOPE = "INSTRUMENTS";
48 : static const std::string EXTENSIONS_SCOPE = "EXTENSIONS";
49 : static const std::string AUDIO_SCOPE = "AUDIO";
50 : static const std::string DOCK_SCOPE = "DOCK";
51 : static const std::string WORKSPACE_SCOPE = "WORKSPACE";
52 : static const std::string UPDATE_SCOPE = "UPDATE";
53 : static const std::string DIAGNOSTICS_SCOPE = "DIAGNOSTICS";
54 : static const std::string TESTFLOW_SCOPE = "TESTFLOW";
55 :
56 : static const QString NOTATION_PANEL_NAME("ScoreView");
57 :
58 0 : int ShortcutResolver::scopePriority(const std::string& scope) const
59 : {
60 0 : if (m_scopePriorityMap.empty()) {
61 0 : m_scopePriorityMap = {
62 0 : { NAVIGATION_SCOPE, []() { return 1; } },
63 0 : { PLAYBACK_SCOPE, []() { return 2; } },
64 0 : { NOTATION_PAGE_SCOPE, []() { return 3; } },
65 0 : { NOTATION_SCOPE, [this]() { return isNotationFocused() ? 4 : -1; } },
66 0 : { NOTATION_TAB_SCOPE, [this]() { return isNotationFocusedAndTabStaff() ? 5 : -1; } },
67 0 : { NOTATION_SELECTION_SCOPE, [this]() { return isNotationFocusedAndHasSelection() ? 6 : -1; } },
68 0 : { NOTATION_SINGLE_SELECTION_SCOPE, [this]() { return isNotationFocusedAndSingleSelection() ? 7 : -1; } },
69 0 : { NOTATION_RANGE_SELECTION_SCOPE, [this]() { return isNotationFocusedAndRangeSelection() ? 7 : -1; } },
70 0 : { NOTATION_NOTE_INPUT_SCOPE, [this]() { return isNotationFocusedAndNoteInputMode() ? 8 : -1; } },
71 0 : { NOTATION_TEXT_EDITING_SCOPE, [this]() { return isNotationFocusedAndTextEditing() ? 9 : -1; } },
72 0 : };
73 : }
74 :
75 0 : auto priority = muse::value(m_scopePriorityMap, scope, nullptr);
76 0 : if (priority) {
77 0 : return priority();
78 : }
79 0 : LOGD() << "Unknown scope: " << scope;
80 0 : return 0;
81 0 : }
82 :
83 0 : bool ShortcutResolver::isNotationFocused() const
84 : {
85 0 : auto activePanel = navigationController()->activePanel();
86 0 : return activePanel && activePanel->name() == NOTATION_PANEL_NAME;
87 : }
88 :
89 0 : bool ShortcutResolver::isNotationFocusedAndNoteInputMode() const
90 : {
91 0 : return isNotationFocused() && notationCommandsController()->isNoteInputMode();
92 : }
93 :
94 0 : bool ShortcutResolver::isNotationFocusedAndTabStaff() const
95 : {
96 0 : return isNotationFocused() && notationCommandsController()->isTablatureStaff();
97 : }
98 :
99 0 : bool ShortcutResolver::isNotationFocusedAndHasSelection() const
100 : {
101 0 : return isNotationFocused() && notationCommandsController()->hasSelection();
102 : }
103 :
104 0 : bool ShortcutResolver::isNotationFocusedAndSingleSelection() const
105 : {
106 0 : return isNotationFocused() && notationCommandsController()->isSingleSelection();
107 : }
108 :
109 0 : bool ShortcutResolver::isNotationFocusedAndRangeSelection() const
110 : {
111 0 : return isNotationFocused() && notationCommandsController()->isRangeSelection();
112 : }
113 :
114 0 : bool ShortcutResolver::isNotationFocusedAndTextEditing() const
115 : {
116 0 : return isNotationFocused() && notationCommandsController()->isTextEditing();
117 : }
118 :
119 0 : Shortcut ShortcutResolver::selectActive(const ShortcutList& list) const
120 : {
121 0 : IF_ASSERT_FAILED(list.size() > 0) {
122 0 : return Shortcut();
123 : }
124 :
125 0 : ShortcutList sorted = list;
126 0 : std::stable_sort(sorted.begin(), sorted.end(), [this](const Shortcut& a, const Shortcut& b) {
127 0 : if (a.scope != b.scope) {
128 0 : return scopePriority(a.scope) > scopePriority(b.scope);
129 : }
130 0 : return a.command != rcommand::NONE_COMMAND && b.command == rcommand::NONE_COMMAND;
131 : });
132 :
133 0 : Shortcut probableShortcut = sorted.front();
134 0 : if (scopePriority(probableShortcut.scope) < 0) {
135 : //! NOTE If the probable shortcut is not active, return an invalid shortcut.
136 0 : return Shortcut();
137 : }
138 :
139 0 : return probableShortcut;
140 0 : }
141 :
142 0 : muse::TranslatableString ShortcutResolver::scopeTitle(const std::string& scopeCode) const
143 : {
144 : const static std::map<std::string, TranslatableString> scopeTitles = {
145 0 : { NAVIGATION_SCOPE, TranslatableString("shortcuts", "NAVIGATION") },
146 0 : { PLAYBACK_SCOPE, TranslatableString("shortcuts", "PLAYBACK") },
147 0 : { NOTATION_PAGE_SCOPE, TranslatableString("shortcuts", "NOTATION PAGE") },
148 0 : { NOTATION_SCOPE, TranslatableString("shortcuts", "NOTATION") },
149 0 : { NOTATION_TAB_SCOPE, TranslatableString("shortcuts", "NOTATION: TAB") },
150 0 : { NOTATION_SELECTION_SCOPE, TranslatableString("shortcuts", "NOTATION: SELECTION") },
151 0 : { NOTATION_SINGLE_SELECTION_SCOPE, TranslatableString("shortcuts", "NOTATION: SINGLE SELECTION") },
152 0 : { NOTATION_RANGE_SELECTION_SCOPE, TranslatableString("shortcuts", "NOTATION: RANGE SELECTION") },
153 0 : { NOTATION_NOTE_INPUT_SCOPE, TranslatableString("shortcuts", "NOTATION: NOTE INPUT") },
154 0 : { NOTATION_TEXT_EDITING_SCOPE, TranslatableString("shortcuts", "NOTATION: TEXT EDITING") },
155 0 : { PROJECT_SCOPE, TranslatableString("shortcuts", "PROJECT") },
156 0 : { APP_SCOPE, TranslatableString("shortcuts", "APP") },
157 0 : { PALETTE_SCOPE, TranslatableString("shortcuts", "PALETTE") },
158 0 : { INSTRUMENTS_SCOPE, TranslatableString("shortcuts", "INSTRUMENTS") },
159 0 : { EXTENSIONS_SCOPE, TranslatableString("shortcuts", "EXTENSIONS") },
160 0 : { AUDIO_SCOPE, TranslatableString("shortcuts", "AUDIO") },
161 0 : { DOCK_SCOPE, TranslatableString("shortcuts", "DOCK") },
162 0 : { WORKSPACE_SCOPE, TranslatableString("shortcuts", "WORKSPACE") },
163 0 : { UPDATE_SCOPE, TranslatableString("shortcuts", "UPDATE") },
164 0 : { DIAGNOSTICS_SCOPE, TranslatableString("shortcuts", "DIAGNOSTICS") },
165 0 : { TESTFLOW_SCOPE, TranslatableString("shortcuts", "TESTFLOW") },
166 0 : };
167 :
168 0 : const std::string upperScopeCode = strings::toUpper(scopeCode);
169 :
170 0 : auto it = scopeTitles.find(upperScopeCode);
171 0 : if (it != scopeTitles.end()) {
172 0 : return it->second;
173 : }
174 0 : LOGW() << "Unknown scope: " << scopeCode;
175 0 : return TranslatableString::untranslatable(String::fromStdString(upperScopeCode));
176 0 : }
|