1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2020 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11 Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12
13 End User License Agreement: www.juce.com/juce-6-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24 */
25
26 namespace juce
27 {
28
ApplicationCommandManager()29 ApplicationCommandManager::ApplicationCommandManager()
30 {
31 keyMappings.reset (new KeyPressMappingSet (*this));
32 Desktop::getInstance().addFocusChangeListener (this);
33 }
34
~ApplicationCommandManager()35 ApplicationCommandManager::~ApplicationCommandManager()
36 {
37 Desktop::getInstance().removeFocusChangeListener (this);
38 keyMappings.reset();
39 }
40
41 //==============================================================================
clearCommands()42 void ApplicationCommandManager::clearCommands()
43 {
44 commands.clear();
45 keyMappings->clearAllKeyPresses();
46 triggerAsyncUpdate();
47 }
48
registerCommand(const ApplicationCommandInfo & newCommand)49 void ApplicationCommandManager::registerCommand (const ApplicationCommandInfo& newCommand)
50 {
51 // zero isn't a valid command ID!
52 jassert (newCommand.commandID != 0);
53
54 // the name isn't optional!
55 jassert (newCommand.shortName.isNotEmpty());
56
57 if (auto* command = getMutableCommandForID (newCommand.commandID))
58 {
59 // Trying to re-register the same command ID with different parameters can often indicate a typo.
60 // This assertion is here because I've found it useful catching some mistakes, but it may also cause
61 // false alarms if you're deliberately updating some flags for a command.
62 jassert (newCommand.shortName == getCommandForID (newCommand.commandID)->shortName
63 && newCommand.categoryName == getCommandForID (newCommand.commandID)->categoryName
64 && newCommand.defaultKeypresses == getCommandForID (newCommand.commandID)->defaultKeypresses
65 && (newCommand.flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor))
66 == (getCommandForID (newCommand.commandID)->flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor)));
67
68 *command = newCommand;
69 }
70 else
71 {
72 auto* newInfo = new ApplicationCommandInfo (newCommand);
73 newInfo->flags &= ~ApplicationCommandInfo::isTicked;
74 commands.add (newInfo);
75
76 keyMappings->resetToDefaultMapping (newCommand.commandID);
77
78 triggerAsyncUpdate();
79 }
80 }
81
registerAllCommandsForTarget(ApplicationCommandTarget * target)82 void ApplicationCommandManager::registerAllCommandsForTarget (ApplicationCommandTarget* target)
83 {
84 if (target != nullptr)
85 {
86 Array<CommandID> commandIDs;
87 target->getAllCommands (commandIDs);
88
89 for (int i = 0; i < commandIDs.size(); ++i)
90 {
91 ApplicationCommandInfo info (commandIDs.getUnchecked(i));
92 target->getCommandInfo (info.commandID, info);
93
94 registerCommand (info);
95 }
96 }
97 }
98
removeCommand(const CommandID commandID)99 void ApplicationCommandManager::removeCommand (const CommandID commandID)
100 {
101 for (int i = commands.size(); --i >= 0;)
102 {
103 if (commands.getUnchecked (i)->commandID == commandID)
104 {
105 commands.remove (i);
106 triggerAsyncUpdate();
107
108 const Array<KeyPress> keys (keyMappings->getKeyPressesAssignedToCommand (commandID));
109
110 for (int j = keys.size(); --j >= 0;)
111 keyMappings->removeKeyPress (keys.getReference (j));
112 }
113 }
114 }
115
commandStatusChanged()116 void ApplicationCommandManager::commandStatusChanged()
117 {
118 triggerAsyncUpdate();
119 }
120
121 //==============================================================================
getMutableCommandForID(CommandID commandID) const122 ApplicationCommandInfo* ApplicationCommandManager::getMutableCommandForID (CommandID commandID) const noexcept
123 {
124 for (int i = commands.size(); --i >= 0;)
125 if (commands.getUnchecked(i)->commandID == commandID)
126 return commands.getUnchecked(i);
127
128 return nullptr;
129 }
130
getCommandForID(CommandID commandID) const131 const ApplicationCommandInfo* ApplicationCommandManager::getCommandForID (CommandID commandID) const noexcept
132 {
133 return getMutableCommandForID (commandID);
134 }
135
getNameOfCommand(CommandID commandID) const136 String ApplicationCommandManager::getNameOfCommand (CommandID commandID) const noexcept
137 {
138 if (auto* ci = getCommandForID (commandID))
139 return ci->shortName;
140
141 return {};
142 }
143
getDescriptionOfCommand(CommandID commandID) const144 String ApplicationCommandManager::getDescriptionOfCommand (CommandID commandID) const noexcept
145 {
146 if (auto* ci = getCommandForID (commandID))
147 return ci->description.isNotEmpty() ? ci->description
148 : ci->shortName;
149
150 return {};
151 }
152
getCommandCategories() const153 StringArray ApplicationCommandManager::getCommandCategories() const
154 {
155 StringArray s;
156
157 for (int i = 0; i < commands.size(); ++i)
158 s.addIfNotAlreadyThere (commands.getUnchecked(i)->categoryName, false);
159
160 return s;
161 }
162
getCommandsInCategory(const String & categoryName) const163 Array<CommandID> ApplicationCommandManager::getCommandsInCategory (const String& categoryName) const
164 {
165 Array<CommandID> results;
166
167 for (int i = 0; i < commands.size(); ++i)
168 if (commands.getUnchecked(i)->categoryName == categoryName)
169 results.add (commands.getUnchecked(i)->commandID);
170
171 return results;
172 }
173
174 //==============================================================================
invokeDirectly(CommandID commandID,bool asynchronously)175 bool ApplicationCommandManager::invokeDirectly (CommandID commandID, bool asynchronously)
176 {
177 ApplicationCommandTarget::InvocationInfo info (commandID);
178 info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct;
179
180 return invoke (info, asynchronously);
181 }
182
invoke(const ApplicationCommandTarget::InvocationInfo & inf,bool asynchronously)183 bool ApplicationCommandManager::invoke (const ApplicationCommandTarget::InvocationInfo& inf, bool asynchronously)
184 {
185 // This call isn't thread-safe for use from a non-UI thread without locking the message
186 // manager first..
187 JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
188
189 bool ok = false;
190 ApplicationCommandInfo commandInfo (0);
191
192 if (auto* target = getTargetForCommand (inf.commandID, commandInfo))
193 {
194 ApplicationCommandTarget::InvocationInfo info (inf);
195 info.commandFlags = commandInfo.flags;
196
197 sendListenerInvokeCallback (info);
198 ok = target->invoke (info, asynchronously);
199 commandStatusChanged();
200 }
201
202 return ok;
203 }
204
205 //==============================================================================
getFirstCommandTarget(CommandID)206 ApplicationCommandTarget* ApplicationCommandManager::getFirstCommandTarget (CommandID)
207 {
208 return firstTarget != nullptr ? firstTarget
209 : findDefaultComponentTarget();
210 }
211
setFirstCommandTarget(ApplicationCommandTarget * newTarget)212 void ApplicationCommandManager::setFirstCommandTarget (ApplicationCommandTarget* newTarget) noexcept
213 {
214 firstTarget = newTarget;
215 }
216
getTargetForCommand(CommandID commandID,ApplicationCommandInfo & upToDateInfo)217 ApplicationCommandTarget* ApplicationCommandManager::getTargetForCommand (CommandID commandID,
218 ApplicationCommandInfo& upToDateInfo)
219 {
220 auto* target = getFirstCommandTarget (commandID);
221
222 if (target == nullptr)
223 target = JUCEApplication::getInstance();
224
225 if (target != nullptr)
226 target = target->getTargetForCommand (commandID);
227
228 if (target != nullptr)
229 {
230 upToDateInfo.commandID = commandID;
231 target->getCommandInfo (commandID, upToDateInfo);
232 }
233
234 return target;
235 }
236
237 //==============================================================================
findTargetForComponent(Component * c)238 ApplicationCommandTarget* ApplicationCommandManager::findTargetForComponent (Component* c)
239 {
240 auto* target = dynamic_cast<ApplicationCommandTarget*> (c);
241
242 if (target == nullptr && c != nullptr)
243 target = c->findParentComponentOfClass<ApplicationCommandTarget>();
244
245 return target;
246 }
247
findDefaultComponentTarget()248 ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget()
249 {
250 auto* c = Component::getCurrentlyFocusedComponent();
251
252 if (c == nullptr)
253 {
254 if (auto* activeWindow = TopLevelWindow::getActiveTopLevelWindow())
255 {
256 if (auto* peer = activeWindow->getPeer())
257 {
258 c = peer->getLastFocusedSubcomponent();
259
260 if (c == nullptr)
261 c = activeWindow;
262 }
263 }
264 }
265
266 if (c == nullptr && Process::isForegroundProcess())
267 {
268 auto& desktop = Desktop::getInstance();
269
270 // getting a bit desperate now: try all desktop comps..
271 for (int i = desktop.getNumComponents(); --i >= 0;)
272 if (auto* peer = desktop.getComponent(i)->getPeer())
273 if (auto* target = findTargetForComponent (peer->getLastFocusedSubcomponent()))
274 return target;
275 }
276
277 if (c != nullptr)
278 {
279 // if we're focused on a ResizableWindow, chances are that it's the content
280 // component that really should get the event. And if not, the event will
281 // still be passed up to the top level window anyway, so let's send it to the
282 // content comp.
283 if (auto* resizableWindow = dynamic_cast<ResizableWindow*> (c))
284 if (auto* content = resizableWindow->getContentComponent())
285 c = content;
286
287 if (auto* target = findTargetForComponent (c))
288 return target;
289 }
290
291 return JUCEApplication::getInstance();
292 }
293
294 //==============================================================================
addListener(ApplicationCommandManagerListener * listener)295 void ApplicationCommandManager::addListener (ApplicationCommandManagerListener* listener)
296 {
297 listeners.add (listener);
298 }
299
removeListener(ApplicationCommandManagerListener * listener)300 void ApplicationCommandManager::removeListener (ApplicationCommandManagerListener* listener)
301 {
302 listeners.remove (listener);
303 }
304
sendListenerInvokeCallback(const ApplicationCommandTarget::InvocationInfo & info)305 void ApplicationCommandManager::sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo& info)
306 {
307 listeners.call ([&] (ApplicationCommandManagerListener& l) { l.applicationCommandInvoked (info); });
308 }
309
handleAsyncUpdate()310 void ApplicationCommandManager::handleAsyncUpdate()
311 {
312 listeners.call ([] (ApplicationCommandManagerListener& l) { l.applicationCommandListChanged(); });
313 }
314
globalFocusChanged(Component *)315 void ApplicationCommandManager::globalFocusChanged (Component*)
316 {
317 commandStatusChanged();
318 }
319
320 } // namespace juce
321