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