The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

333 lines
12KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "../core/juce_StandardHeader.h"
  19. BEGIN_JUCE_NAMESPACE
  20. #include "juce_ApplicationCommandManager.h"
  21. #include "juce_Application.h"
  22. #include "../gui/components/windows/juce_ComponentPeer.h"
  23. #include "../gui/components/keyboard/juce_KeyPressMappingSet.h"
  24. #include "../gui/components/windows/juce_ResizableWindow.h"
  25. #include "../gui/components/juce_Desktop.h"
  26. #include "../events/juce_MessageManager.h"
  27. #include "../threads/juce_Process.h"
  28. //==============================================================================
  29. ApplicationCommandManager::ApplicationCommandManager()
  30. : firstTarget (0)
  31. {
  32. keyMappings = new KeyPressMappingSet (this);
  33. Desktop::getInstance().addFocusChangeListener (this);
  34. }
  35. ApplicationCommandManager::~ApplicationCommandManager()
  36. {
  37. Desktop::getInstance().removeFocusChangeListener (this);
  38. keyMappings = 0;
  39. }
  40. //==============================================================================
  41. void ApplicationCommandManager::clearCommands()
  42. {
  43. commands.clear();
  44. keyMappings->clearAllKeyPresses();
  45. triggerAsyncUpdate();
  46. }
  47. void ApplicationCommandManager::registerCommand (const ApplicationCommandInfo& newCommand)
  48. {
  49. // zero isn't a valid command ID!
  50. jassert (newCommand.commandID != 0);
  51. // the name isn't optional!
  52. jassert (newCommand.shortName.isNotEmpty());
  53. if (getCommandForID (newCommand.commandID) == 0)
  54. {
  55. ApplicationCommandInfo* const newInfo = new ApplicationCommandInfo (newCommand);
  56. newInfo->flags &= ~ApplicationCommandInfo::isTicked;
  57. commands.add (newInfo);
  58. keyMappings->resetToDefaultMapping (newCommand.commandID);
  59. triggerAsyncUpdate();
  60. }
  61. else
  62. {
  63. // trying to re-register the same command with different parameters?
  64. jassert (newCommand.shortName == getCommandForID (newCommand.commandID)->shortName
  65. && (newCommand.description == getCommandForID (newCommand.commandID)->description || newCommand.description.isEmpty())
  66. && newCommand.categoryName == getCommandForID (newCommand.commandID)->categoryName
  67. && newCommand.defaultKeypresses == getCommandForID (newCommand.commandID)->defaultKeypresses
  68. && (newCommand.flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor))
  69. == (getCommandForID (newCommand.commandID)->flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor)));
  70. }
  71. }
  72. void ApplicationCommandManager::registerAllCommandsForTarget (ApplicationCommandTarget* target)
  73. {
  74. if (target != 0)
  75. {
  76. Array <CommandID> commandIDs;
  77. target->getAllCommands (commandIDs);
  78. for (int i = 0; i < commandIDs.size(); ++i)
  79. {
  80. ApplicationCommandInfo info (commandIDs.getUnchecked(i));
  81. target->getCommandInfo (info.commandID, info);
  82. registerCommand (info);
  83. }
  84. }
  85. }
  86. void ApplicationCommandManager::removeCommand (const CommandID commandID)
  87. {
  88. for (int i = commands.size(); --i >= 0;)
  89. {
  90. if (commands.getUnchecked (i)->commandID == commandID)
  91. {
  92. commands.remove (i);
  93. triggerAsyncUpdate();
  94. const Array <KeyPress> keys (keyMappings->getKeyPressesAssignedToCommand (commandID));
  95. for (int j = keys.size(); --j >= 0;)
  96. keyMappings->removeKeyPress (keys.getReference (j));
  97. }
  98. }
  99. }
  100. void ApplicationCommandManager::commandStatusChanged()
  101. {
  102. triggerAsyncUpdate();
  103. }
  104. //==============================================================================
  105. const ApplicationCommandInfo* ApplicationCommandManager::getCommandForID (const CommandID commandID) const throw()
  106. {
  107. for (int i = commands.size(); --i >= 0;)
  108. if (commands.getUnchecked(i)->commandID == commandID)
  109. return commands.getUnchecked(i);
  110. return 0;
  111. }
  112. const String ApplicationCommandManager::getNameOfCommand (const CommandID commandID) const throw()
  113. {
  114. const ApplicationCommandInfo* const ci = getCommandForID (commandID);
  115. return (ci != 0) ? ci->shortName : String::empty;
  116. }
  117. const String ApplicationCommandManager::getDescriptionOfCommand (const CommandID commandID) const throw()
  118. {
  119. const ApplicationCommandInfo* const ci = getCommandForID (commandID);
  120. return (ci != 0) ? (ci->description.isNotEmpty() ? ci->description : ci->shortName)
  121. : String::empty;
  122. }
  123. const StringArray ApplicationCommandManager::getCommandCategories() const throw()
  124. {
  125. StringArray s;
  126. for (int i = 0; i < commands.size(); ++i)
  127. s.addIfNotAlreadyThere (commands.getUnchecked(i)->categoryName, false);
  128. return s;
  129. }
  130. const Array <CommandID> ApplicationCommandManager::getCommandsInCategory (const String& categoryName) const throw()
  131. {
  132. Array <CommandID> results;
  133. for (int i = 0; i < commands.size(); ++i)
  134. if (commands.getUnchecked(i)->categoryName == categoryName)
  135. results.add (commands.getUnchecked(i)->commandID);
  136. return results;
  137. }
  138. //==============================================================================
  139. bool ApplicationCommandManager::invokeDirectly (const CommandID commandID, const bool asynchronously)
  140. {
  141. ApplicationCommandTarget::InvocationInfo info (commandID);
  142. info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct;
  143. return invoke (info, asynchronously);
  144. }
  145. bool ApplicationCommandManager::invoke (const ApplicationCommandTarget::InvocationInfo& info_, const bool asynchronously)
  146. {
  147. // This call isn't thread-safe for use from a non-UI thread without locking the message
  148. // manager first..
  149. jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager());
  150. ApplicationCommandTarget* const target = getFirstCommandTarget (info_.commandID);
  151. if (target == 0)
  152. return false;
  153. ApplicationCommandInfo commandInfo (0);
  154. target->getCommandInfo (info_.commandID, commandInfo);
  155. ApplicationCommandTarget::InvocationInfo info (info_);
  156. info.commandFlags = commandInfo.flags;
  157. sendListenerInvokeCallback (info);
  158. const bool ok = target->invoke (info, asynchronously);
  159. commandStatusChanged();
  160. return ok;
  161. }
  162. //==============================================================================
  163. ApplicationCommandTarget* ApplicationCommandManager::getFirstCommandTarget (const CommandID)
  164. {
  165. return firstTarget != 0 ? firstTarget
  166. : findDefaultComponentTarget();
  167. }
  168. void ApplicationCommandManager::setFirstCommandTarget (ApplicationCommandTarget* const newTarget) throw()
  169. {
  170. firstTarget = newTarget;
  171. }
  172. ApplicationCommandTarget* ApplicationCommandManager::getTargetForCommand (const CommandID commandID,
  173. ApplicationCommandInfo& upToDateInfo)
  174. {
  175. ApplicationCommandTarget* target = getFirstCommandTarget (commandID);
  176. if (target == 0)
  177. target = JUCEApplication::getInstance();
  178. if (target != 0)
  179. target = target->getTargetForCommand (commandID);
  180. if (target != 0)
  181. target->getCommandInfo (commandID, upToDateInfo);
  182. return target;
  183. }
  184. //==============================================================================
  185. ApplicationCommandTarget* ApplicationCommandManager::findTargetForComponent (Component* c)
  186. {
  187. ApplicationCommandTarget* target = dynamic_cast <ApplicationCommandTarget*> (c);
  188. if (target == 0 && c != 0)
  189. // (unable to use the syntax findParentComponentOfClass <ApplicationCommandTarget> () because of a VC6 compiler bug)
  190. target = c->findParentComponentOfClass ((ApplicationCommandTarget*) 0);
  191. return target;
  192. }
  193. ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget()
  194. {
  195. Component* c = Component::getCurrentlyFocusedComponent();
  196. if (c == 0)
  197. {
  198. TopLevelWindow* const activeWindow = TopLevelWindow::getActiveTopLevelWindow();
  199. if (activeWindow != 0)
  200. {
  201. c = activeWindow->getPeer()->getLastFocusedSubcomponent();
  202. if (c == 0)
  203. c = activeWindow;
  204. }
  205. }
  206. if (c == 0 && Process::isForegroundProcess())
  207. {
  208. // getting a bit desperate now - try all desktop comps..
  209. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  210. {
  211. ApplicationCommandTarget* const target
  212. = findTargetForComponent (Desktop::getInstance().getComponent (i)
  213. ->getPeer()->getLastFocusedSubcomponent());
  214. if (target != 0)
  215. return target;
  216. }
  217. }
  218. if (c != 0)
  219. {
  220. ResizableWindow* const resizableWindow = dynamic_cast <ResizableWindow*> (c);
  221. // if we're focused on a ResizableWindow, chances are that it's the content
  222. // component that really should get the event. And if not, the event will
  223. // still be passed up to the top level window anyway, so let's send it to the
  224. // content comp.
  225. if (resizableWindow != 0 && resizableWindow->getContentComponent() != 0)
  226. c = resizableWindow->getContentComponent();
  227. ApplicationCommandTarget* const target = findTargetForComponent (c);
  228. if (target != 0)
  229. return target;
  230. }
  231. return JUCEApplication::getInstance();
  232. }
  233. //==============================================================================
  234. void ApplicationCommandManager::addListener (ApplicationCommandManagerListener* const listener) throw()
  235. {
  236. listeners.add (listener);
  237. }
  238. void ApplicationCommandManager::removeListener (ApplicationCommandManagerListener* const listener) throw()
  239. {
  240. listeners.remove (listener);
  241. }
  242. void ApplicationCommandManager::sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo& info)
  243. {
  244. listeners.call (&ApplicationCommandManagerListener::applicationCommandInvoked, info);
  245. }
  246. void ApplicationCommandManager::handleAsyncUpdate()
  247. {
  248. listeners.call (&ApplicationCommandManagerListener::applicationCommandListChanged);
  249. }
  250. void ApplicationCommandManager::globalFocusChanged (Component*)
  251. {
  252. commandStatusChanged();
  253. }
  254. END_JUCE_NAMESPACE