Audio plugin host https://kx.studio/carla
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.

314 lines
10KB

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