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 "actionsdispatcher.h"
23 :
24 : #include <sstream>
25 :
26 : #include "actionable.h"
27 : #include "global/stringutils.h"
28 :
29 : #include "log.h"
30 : #include "rcommand/commandtypes.h"
31 :
32 : using namespace muse::actions;
33 : using namespace muse::async;
34 :
35 32 : ActionsDispatcher::~ActionsDispatcher()
36 : {
37 544 : for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
38 512 : Clients& clients = it->second;
39 1024 : for (auto cit = clients.begin(); cit != clients.end(); ++cit) {
40 512 : Actionable* client = cit->first;
41 512 : client->setDispatcher(nullptr);
42 : }
43 : }
44 32 : }
45 :
46 0 : void ActionsDispatcher::dispatch(const ActionCode& actionCode)
47 : {
48 0 : static ActionData dummy;
49 0 : dispatch(actionCode, dummy);
50 0 : }
51 :
52 0 : void ActionsDispatcher::dispatch(const ActionCode& actionCode, const ActionData& data)
53 : {
54 : // is query?
55 0 : ActionQuery query = ActionQuery(actionCode);
56 0 : if (query.isValid()) {
57 0 : IF_ASSERT_FAILED(data.empty()) {
58 0 : LOGE() << "not supported action data with query";
59 : }
60 0 : dispatch(query);
61 0 : return;
62 : }
63 :
64 0 : if (muse::strings::startsWith(actionCode, rcommand::COMMAND_SCHEME)) {
65 0 : commandDispatcher()->dispatch(rcommand::Command(actionCode));
66 0 : return;
67 : }
68 :
69 : // code
70 0 : auto it = m_clients.find(actionCode);
71 0 : if (it == m_clients.end()) {
72 0 : LOGW() << "not a registered action: " << actionCode;
73 0 : m_notRegistered.send(actionCode);
74 0 : return;
75 : }
76 :
77 0 : doDispatch(it->second, actionCode, data);
78 0 : }
79 :
80 0 : void ActionsDispatcher::dispatch(const ActionQuery& actionQuery)
81 : {
82 0 : if (muse::strings::startsWith(actionQuery.uri().scheme(), rcommand::COMMAND_SCHEME)) {
83 0 : commandDispatcher()->dispatch(actionQuery);
84 0 : return;
85 : }
86 :
87 : //! NOTE Try find full query
88 0 : const std::string full = actionQuery.toString();
89 0 : ActionCode code = full;
90 0 : auto it = m_clients.find(code);
91 0 : if (it == m_clients.end()) {
92 : //! Try find just uri
93 0 : code = actionQuery.uri().toString();
94 0 : it = m_clients.find(code);
95 0 : if (it == m_clients.end()) {
96 0 : LOGW() << "not a registered action: '" << code << "'";
97 0 : m_notRegistered.send(code);
98 : //dump();
99 0 : return;
100 : }
101 : }
102 :
103 0 : ActionData data;
104 0 : data.setArg<std::string>(0, full);
105 0 : doDispatch(it->second, code, data);
106 0 : }
107 :
108 0 : void ActionsDispatcher::dump() const
109 : {
110 0 : std::stringstream s;
111 0 : for (const auto& p : m_clients) {
112 0 : s << "'" << p.first << "'\n";
113 : }
114 :
115 0 : LOGDA() << "\n" << s.str();
116 0 : }
117 :
118 0 : void ActionsDispatcher::doDispatch(const Clients& clients, const ActionCode& actionCode, const ActionData& data)
119 : {
120 0 : int canReceiveCount = 0;
121 0 : for (auto cit = clients.cbegin(); cit != clients.cend(); ++cit) {
122 0 : const Actionable* client = cit->first;
123 0 : if (client->canReceiveAction(actionCode)) {
124 0 : ++canReceiveCount;
125 0 : const CallBacks& callbacks = cit->second;
126 0 : auto cbit = callbacks.find(actionCode);
127 0 : IF_ASSERT_FAILED(cbit != callbacks.end()) {
128 0 : continue;
129 : }
130 :
131 : // Notify that we're about to perform an action.
132 0 : m_preDispatch.send(actionCode);
133 :
134 : // Perform the action.
135 0 : const ActionCallBackWithNameAndData& callback = cbit->second;
136 0 : LOGI() << "try call action: " << actionCode;
137 0 : callback(actionCode, data);
138 : }
139 : }
140 :
141 0 : if (canReceiveCount == 0) {
142 0 : LOGI() << "no one can handle the action: " << actionCode;
143 0 : return;
144 : }
145 :
146 0 : if (canReceiveCount > 1) {
147 0 : LOGW() << "More than one client can handle the action, this is not a typical situation.";
148 : }
149 :
150 : // Notify that we have performed an action.
151 0 : m_postDispatch.send(actionCode);
152 : }
153 :
154 0 : Channel<ActionCode> ActionsDispatcher::preDispatch() const
155 : {
156 0 : return m_preDispatch;
157 : }
158 :
159 0 : Channel<ActionCode> ActionsDispatcher::postDispatch() const
160 : {
161 0 : return m_postDispatch;
162 : }
163 :
164 0 : Channel<ActionCode> ActionsDispatcher::notRegistered() const
165 : {
166 0 : return m_notRegistered;
167 : }
168 :
169 0 : void ActionsDispatcher::unReg(Actionable* client)
170 : {
171 0 : for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
172 0 : Clients& clients = it->second;
173 0 : clients.erase(client);
174 : }
175 0 : client->setDispatcher(nullptr);
176 0 : }
177 :
178 512 : void ActionsDispatcher::reg(Actionable* client, const ActionCode& actionCode, const ActionCallBackWithNameAndData& call)
179 : {
180 512 : client->setDispatcher(this);
181 :
182 512 : Clients& clients = m_clients[actionCode];
183 512 : CallBacks& callbacks = clients[client];
184 512 : callbacks.insert({ actionCode, call });
185 512 : }
186 :
187 0 : void ActionsDispatcher::reg(Actionable* client, const ActionQuery& actionQuery, const ActionCallBackWithQuery& call)
188 : {
189 0 : reg(client, actionQuery.toString(), [call](const ActionCode&, const ActionData& data) {
190 0 : call(ActionQuery(data.arg<std::string>(0)));
191 0 : });
192 0 : }
193 :
194 0 : bool ActionsDispatcher::isReg(Actionable* client) const
195 : {
196 0 : return client->isDispatcher(this);
197 : }
198 :
199 0 : ActionCodeList ActionsDispatcher::actionList() const
200 : {
201 0 : ActionCodeList list;
202 0 : list.reserve(m_clients.size());
203 0 : for (const auto& p : m_clients) {
204 0 : list.push_back(p.first);
205 : }
206 0 : return list;
207 0 : }
|