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.

319 lines
11KB

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