Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 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 "uiactionsregister.h"
23 :
24 : #include "global/async/async.h"
25 : #include "global/stringutils.h"
26 :
27 : #include "muse_framework_config.h"
28 :
29 : #include "log.h"
30 :
31 : using namespace muse;
32 : using namespace muse::ui;
33 : using namespace muse::actions;
34 :
35 0 : void UiActionsRegister::init()
36 : {
37 : // init state
38 0 : updateCheckedAll();
39 0 : updateEnabledAll();
40 :
41 : // listen
42 0 : if (uicontextResolver()) {
43 0 : uicontextResolver()->currentUiContextChanged().onNotify(this, [this]() {
44 0 : requestUpdateEnabledAll();
45 0 : });
46 : }
47 :
48 0 : if (commandsState()) {
49 0 : commandsState()->commandStateChanged().onReceive(this, [this](const rcommand::Command& command, const rcommand::CommandState&) {
50 0 : m_actionStateChanged.send({ command.toString() });
51 0 : });
52 : }
53 0 : }
54 :
55 0 : void UiActionsRegister::reg(const IUiActionsModulePtr& module)
56 : {
57 0 : const UiActionList& alist = module->actionsList();
58 0 : for (const UiAction& action : alist) {
59 0 : Info info;
60 0 : info.module = module;
61 0 : info.action = action;
62 0 : m_actions[action.code] = std::move(info);
63 0 : }
64 :
65 0 : module->actionsChanged().onReceive(this, [this](const UiActionList& actions) {
66 0 : updateActions(actions);
67 0 : }, async::Asyncable::Mode::SetReplace); // see IUiActionsModule::actionsChanged()
68 :
69 0 : module->actionEnabledChanged().onReceive(this, [this](const ActionCodeList& codes) {
70 0 : updateEnabled(codes);
71 0 : m_actionStateChanged.send(codes);
72 0 : });
73 :
74 0 : module->actionCheckedChanged().onReceive(this, [this](const ActionCodeList& codes) {
75 0 : updateChecked(codes);
76 0 : m_actionStateChanged.send(codes);
77 0 : });
78 0 : }
79 :
80 0 : void UiActionsRegister::unreg(const IUiActionsModulePtr& module)
81 : {
82 0 : for (auto it = m_actions.begin(); it != m_actions.end();) {
83 0 : if (it->second.module == module) {
84 0 : it = m_actions.erase(it);
85 : } else {
86 0 : ++it;
87 : }
88 : }
89 :
90 0 : module->actionsChanged().disconnect(this);
91 0 : module->actionEnabledChanged().disconnect(this);
92 0 : module->actionCheckedChanged().disconnect(this);
93 0 : }
94 :
95 0 : UiActionsRegister::Info& UiActionsRegister::info(const ActionCode& code)
96 : {
97 0 : auto it = m_actions.find(code);
98 0 : if (it != m_actions.end()) {
99 0 : return it->second;
100 : }
101 :
102 0 : static Info null;
103 0 : return null;
104 : }
105 :
106 0 : const UiActionsRegister::Info& UiActionsRegister::info(const ActionCode& code) const
107 : {
108 0 : auto it = m_actions.find(code);
109 0 : if (it != m_actions.end()) {
110 0 : return it->second;
111 : }
112 :
113 : // is query?
114 0 : ActionQuery q(code);
115 0 : if (q.isValid()) {
116 0 : it = m_actions.find(q.uri().toString());
117 0 : if (it != m_actions.end()) {
118 0 : return it->second;
119 : }
120 : }
121 :
122 0 : static Info null;
123 0 : return null;
124 0 : }
125 :
126 0 : std::vector<UiAction> UiActionsRegister::actionList() const
127 : {
128 0 : std::vector<UiAction> allActions;
129 :
130 0 : for (auto it = m_actions.begin(); it != m_actions.end(); it++) {
131 0 : allActions.push_back(it->second.action);
132 : }
133 :
134 0 : return allActions;
135 0 : }
136 :
137 0 : const UiAction& UiActionsRegister::action(const ActionCode& code) const
138 : {
139 0 : if (muse::strings::startsWith(code, rcommand::COMMAND_SCHEME)) {
140 0 : auto it = m_commandActions.find(code);
141 0 : if (it != m_commandActions.end()) {
142 0 : return it->second;
143 : }
144 :
145 0 : const rcommand::CommandInfo& info = commandsRegister()->commandInfo(rcommand::Command(code));
146 0 : if (!info.isValid()) {
147 0 : LOGE() << "not valid command: " << code;
148 0 : static UiAction null;
149 0 : return null;
150 : }
151 :
152 0 : UiAction action;
153 0 : action.code = code;
154 0 : action.title = info.title;
155 0 : action.description = info.description;
156 0 : action.iconCode = info.decoration.iconCode;
157 0 : if (info.decoration.iconColor.isValid()) {
158 0 : action.iconColor = QString::fromStdString(info.decoration.iconColor.toString());
159 : }
160 0 : action.checkable = static_cast<Checkable>(info.decoration.checkable);
161 :
162 0 : it = m_commandActions.emplace(code, action).first;
163 0 : return it->second;
164 0 : }
165 :
166 0 : return info(code).action;
167 : }
168 :
169 0 : const actions::ActionCode& UiActionsRegister::parentActionCode(const actions::ActionCode& code) const
170 : {
171 0 : for (auto it = m_actions.begin(); it != m_actions.end(); it++) {
172 0 : if (muse::contains(it->second.action.children, code)) {
173 0 : return it->second.action.code;
174 : }
175 : }
176 :
177 0 : static ActionCode null;
178 0 : return null;
179 : }
180 :
181 0 : async::Channel<UiActionList> UiActionsRegister::actionsChanged() const
182 : {
183 0 : return m_actionsChanged;
184 : }
185 :
186 0 : UiActionState UiActionsRegister::actionState(const ActionCode& code) const
187 : {
188 0 : if (muse::strings::startsWith(code, rcommand::COMMAND_SCHEME)) {
189 0 : const rcommand::CommandState& state = commandsState()->commandState(rcommand::Command(code));
190 0 : return UiActionState { state.enabled, state.checked };
191 : }
192 :
193 0 : const Info& inf = info(code);
194 0 : if (!inf.action.isValid()) {
195 0 : LOGE() << "not found action with code: " << code;
196 0 : return UiActionState::make_disabled();
197 : }
198 :
199 0 : return inf.state;
200 : }
201 :
202 0 : void UiActionsRegister::updateActions(const UiActionList& actions)
203 : {
204 0 : ActionCodeList codes;
205 0 : for (const UiAction& act : actions) {
206 0 : Info& inf = info(act.code);
207 0 : IF_ASSERT_FAILED(inf.isValid()) {
208 0 : continue;
209 : }
210 :
211 0 : inf.action = act;
212 :
213 0 : codes.push_back(act.code);
214 : }
215 :
216 0 : m_actionsChanged.send(actions);
217 :
218 0 : updateEnabled(codes);
219 0 : updateChecked(codes);
220 0 : }
221 :
222 0 : void UiActionsRegister::doUpdateEnabled(Info& inf,
223 : const IUiContextResolverPtr& ctxResolver,
224 : const UiContext& currentCtx,
225 : ActionCodeList& changedList)
226 : {
227 0 : bool oldEnabled = inf.state.enabled;
228 0 : inf.state.enabled = false;
229 :
230 0 : if (ctxResolver->match(currentCtx, inf.action.uiCtx)) {
231 0 : inf.state.enabled = inf.module->actionEnabled(inf.action);
232 : }
233 :
234 0 : if (oldEnabled != inf.state.enabled) {
235 0 : changedList.push_back(inf.action.code);
236 : }
237 0 : }
238 :
239 0 : void UiActionsRegister::updateEnabled(const ActionCodeList& codes)
240 : {
241 0 : TRACEFUNC;
242 :
243 0 : if (!uicontextResolver()) {
244 0 : return;
245 : }
246 :
247 0 : ActionCodeList changedList;
248 0 : auto ctxResolver = uicontextResolver();
249 0 : const ui::UiContext& currentCtx = ctxResolver->currentUiContext();
250 0 : for (const ActionCode& code : codes) {
251 0 : Info& inf = info(code);
252 0 : if (!inf.isValid()) {
253 0 : continue;
254 : }
255 :
256 0 : doUpdateEnabled(inf, ctxResolver, currentCtx, changedList);
257 : }
258 :
259 0 : if (!changedList.empty()) {
260 0 : m_actionStateChanged.send(changedList);
261 : }
262 0 : }
263 :
264 0 : void UiActionsRegister::requestUpdateEnabledAll()
265 : {
266 0 : if (m_isUpdateEnabledAllRequested) {
267 0 : return;
268 : }
269 :
270 0 : m_isUpdateEnabledAllRequested = true;
271 0 : muse::async::Async::call(this, [this]() {
272 0 : updateEnabledAll();
273 0 : m_isUpdateEnabledAllRequested = false;
274 0 : });
275 : }
276 :
277 0 : void UiActionsRegister::updateEnabledAll()
278 : {
279 0 : TRACEFUNC;
280 :
281 0 : if (!uicontextResolver()) {
282 0 : return;
283 : }
284 :
285 0 : ActionCodeList changedList;
286 0 : auto ctxResolver = uicontextResolver();
287 0 : const ui::UiContext& currentCtx = ctxResolver->currentUiContext();
288 0 : LOGD() << "currentCtx: " << currentCtx.toString();
289 0 : for (auto it = m_actions.begin(); it != m_actions.end(); ++it) {
290 0 : Info& inf = it->second;
291 0 : doUpdateEnabled(inf, ctxResolver, currentCtx, changedList);
292 : }
293 :
294 0 : if (!changedList.empty()) {
295 0 : m_actionStateChanged.send(changedList);
296 : }
297 0 : }
298 :
299 0 : void UiActionsRegister::updateCheckedAll()
300 : {
301 0 : TRACEFUNC;
302 :
303 0 : ActionCodeList changedList;
304 0 : for (auto it = m_actions.begin(); it != m_actions.end(); ++it) {
305 0 : Info& inf = it->second;
306 :
307 0 : bool oldChecked = inf.state.checked;
308 0 : inf.state.checked = inf.module->actionChecked(inf.action);
309 :
310 0 : if (oldChecked != inf.state.checked) {
311 0 : changedList.push_back(inf.action.code);
312 : }
313 : }
314 :
315 0 : if (!changedList.empty()) {
316 0 : m_actionStateChanged.send(changedList);
317 : }
318 0 : }
319 :
320 0 : void UiActionsRegister::updateChecked(const ActionCodeList& codes)
321 : {
322 0 : TRACEFUNC;
323 :
324 0 : ActionCodeList changedList;
325 0 : for (const ActionCode& code : codes) {
326 0 : Info& inf = info(code);
327 0 : if (!inf.isValid()) {
328 0 : continue;
329 : }
330 :
331 0 : bool oldChecked = inf.state.checked;
332 0 : inf.state.checked = inf.module->actionChecked(inf.action);
333 :
334 0 : if (oldChecked != inf.state.checked) {
335 0 : changedList.push_back(inf.action.code);
336 : }
337 : }
338 :
339 0 : if (!changedList.empty()) {
340 0 : m_actionStateChanged.send(changedList);
341 : }
342 0 : }
343 :
344 0 : async::Channel<ActionCodeList> UiActionsRegister::actionStateChanged() const
345 : {
346 0 : return m_actionStateChanged;
347 : }
|