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.

juce_KeyPressMappingSet.cpp 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. KeyPressMappingSet::KeyPressMappingSet (ApplicationCommandManager& cm)
  21. : commandManager (cm)
  22. {
  23. Desktop::getInstance().addFocusChangeListener (this);
  24. }
  25. KeyPressMappingSet::KeyPressMappingSet (const KeyPressMappingSet& other)
  26. : KeyListener(), ChangeBroadcaster(), FocusChangeListener(), commandManager (other.commandManager)
  27. {
  28. Desktop::getInstance().addFocusChangeListener (this);
  29. }
  30. KeyPressMappingSet::~KeyPressMappingSet()
  31. {
  32. Desktop::getInstance().removeFocusChangeListener (this);
  33. }
  34. //==============================================================================
  35. Array<KeyPress> KeyPressMappingSet::getKeyPressesAssignedToCommand (const CommandID commandID) const
  36. {
  37. for (int i = 0; i < mappings.size(); ++i)
  38. if (mappings.getUnchecked(i)->commandID == commandID)
  39. return mappings.getUnchecked (i)->keypresses;
  40. return {};
  41. }
  42. void KeyPressMappingSet::addKeyPress (const CommandID commandID, const KeyPress& newKeyPress, int insertIndex)
  43. {
  44. // If you specify an upper-case letter but no shift key, how is the user supposed to press it!?
  45. // Stick to lower-case letters when defining a keypress, to avoid ambiguity.
  46. jassert (! (CharacterFunctions::isUpperCase (newKeyPress.getTextCharacter())
  47. && ! newKeyPress.getModifiers().isShiftDown()));
  48. if (findCommandForKeyPress (newKeyPress) != commandID)
  49. {
  50. if (newKeyPress.isValid())
  51. {
  52. for (int i = mappings.size(); --i >= 0;)
  53. {
  54. if (mappings.getUnchecked(i)->commandID == commandID)
  55. {
  56. mappings.getUnchecked(i)->keypresses.insert (insertIndex, newKeyPress);
  57. sendChangeMessage();
  58. return;
  59. }
  60. }
  61. if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (commandID))
  62. {
  63. CommandMapping* const cm = new CommandMapping();
  64. cm->commandID = commandID;
  65. cm->keypresses.add (newKeyPress);
  66. cm->wantsKeyUpDownCallbacks = (ci->flags & ApplicationCommandInfo::wantsKeyUpDownCallbacks) != 0;
  67. mappings.add (cm);
  68. sendChangeMessage();
  69. }
  70. else
  71. {
  72. // If you hit this, you're trying to attach a keypress to a command ID that
  73. // doesn't exist, so the key is not being attached.
  74. jassertfalse;
  75. }
  76. }
  77. }
  78. }
  79. static void addKeyPresses (KeyPressMappingSet& set, const ApplicationCommandInfo* const ci)
  80. {
  81. for (int j = 0; j < ci->defaultKeypresses.size(); ++j)
  82. set.addKeyPress (ci->commandID, ci->defaultKeypresses.getReference (j));
  83. }
  84. void KeyPressMappingSet::resetToDefaultMappings()
  85. {
  86. mappings.clear();
  87. for (int i = 0; i < commandManager.getNumCommands(); ++i)
  88. addKeyPresses (*this, commandManager.getCommandForIndex (i));
  89. sendChangeMessage();
  90. }
  91. void KeyPressMappingSet::resetToDefaultMapping (const CommandID commandID)
  92. {
  93. clearAllKeyPresses (commandID);
  94. if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (commandID))
  95. addKeyPresses (*this, ci);
  96. }
  97. void KeyPressMappingSet::clearAllKeyPresses()
  98. {
  99. if (mappings.size() > 0)
  100. {
  101. sendChangeMessage();
  102. mappings.clear();
  103. }
  104. }
  105. void KeyPressMappingSet::clearAllKeyPresses (const CommandID commandID)
  106. {
  107. for (int i = mappings.size(); --i >= 0;)
  108. {
  109. if (mappings.getUnchecked(i)->commandID == commandID)
  110. {
  111. mappings.remove (i);
  112. sendChangeMessage();
  113. }
  114. }
  115. }
  116. void KeyPressMappingSet::removeKeyPress (const KeyPress& keypress)
  117. {
  118. if (keypress.isValid())
  119. {
  120. for (int i = mappings.size(); --i >= 0;)
  121. {
  122. CommandMapping& cm = *mappings.getUnchecked(i);
  123. for (int j = cm.keypresses.size(); --j >= 0;)
  124. {
  125. if (keypress == cm.keypresses [j])
  126. {
  127. cm.keypresses.remove (j);
  128. sendChangeMessage();
  129. }
  130. }
  131. }
  132. }
  133. }
  134. void KeyPressMappingSet::removeKeyPress (const CommandID commandID, const int keyPressIndex)
  135. {
  136. for (int i = mappings.size(); --i >= 0;)
  137. {
  138. if (mappings.getUnchecked(i)->commandID == commandID)
  139. {
  140. mappings.getUnchecked(i)->keypresses.remove (keyPressIndex);
  141. sendChangeMessage();
  142. break;
  143. }
  144. }
  145. }
  146. //==============================================================================
  147. CommandID KeyPressMappingSet::findCommandForKeyPress (const KeyPress& keyPress) const noexcept
  148. {
  149. for (int i = 0; i < mappings.size(); ++i)
  150. if (mappings.getUnchecked(i)->keypresses.contains (keyPress))
  151. return mappings.getUnchecked(i)->commandID;
  152. return 0;
  153. }
  154. bool KeyPressMappingSet::containsMapping (const CommandID commandID, const KeyPress& keyPress) const noexcept
  155. {
  156. for (int i = mappings.size(); --i >= 0;)
  157. if (mappings.getUnchecked(i)->commandID == commandID)
  158. return mappings.getUnchecked(i)->keypresses.contains (keyPress);
  159. return false;
  160. }
  161. void KeyPressMappingSet::invokeCommand (const CommandID commandID,
  162. const KeyPress& key,
  163. const bool isKeyDown,
  164. const int millisecsSinceKeyPressed,
  165. Component* const originatingComponent) const
  166. {
  167. ApplicationCommandTarget::InvocationInfo info (commandID);
  168. info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromKeyPress;
  169. info.isKeyDown = isKeyDown;
  170. info.keyPress = key;
  171. info.millisecsSinceKeyPressed = millisecsSinceKeyPressed;
  172. info.originatingComponent = originatingComponent;
  173. commandManager.invoke (info, false);
  174. }
  175. //==============================================================================
  176. bool KeyPressMappingSet::restoreFromXml (const XmlElement& xmlVersion)
  177. {
  178. if (xmlVersion.hasTagName ("KEYMAPPINGS"))
  179. {
  180. if (xmlVersion.getBoolAttribute ("basedOnDefaults", true))
  181. {
  182. // if the XML was created as a set of differences from the default mappings,
  183. // (i.e. by calling createXml (true)), then we need to first restore the defaults.
  184. resetToDefaultMappings();
  185. }
  186. else
  187. {
  188. // if the XML was created calling createXml (false), then we need to clear all
  189. // the keys and treat the xml as describing the entire set of mappings.
  190. clearAllKeyPresses();
  191. }
  192. for (auto* map : xmlVersion.getChildIterator())
  193. {
  194. const CommandID commandId = map->getStringAttribute ("commandId").getHexValue32();
  195. if (commandId != 0)
  196. {
  197. auto key = KeyPress::createFromDescription (map->getStringAttribute ("key"));
  198. if (map->hasTagName ("MAPPING"))
  199. {
  200. addKeyPress (commandId, key);
  201. }
  202. else if (map->hasTagName ("UNMAPPING"))
  203. {
  204. for (auto& m : mappings)
  205. if (m->commandID == commandId)
  206. m->keypresses.removeAllInstancesOf (key);
  207. }
  208. }
  209. }
  210. return true;
  211. }
  212. return false;
  213. }
  214. std::unique_ptr<XmlElement> KeyPressMappingSet::createXml (const bool saveDifferencesFromDefaultSet) const
  215. {
  216. std::unique_ptr<KeyPressMappingSet> defaultSet;
  217. if (saveDifferencesFromDefaultSet)
  218. {
  219. defaultSet = std::make_unique<KeyPressMappingSet> (commandManager);
  220. defaultSet->resetToDefaultMappings();
  221. }
  222. auto doc = std::make_unique<XmlElement> ("KEYMAPPINGS");
  223. doc->setAttribute ("basedOnDefaults", saveDifferencesFromDefaultSet);
  224. for (int i = 0; i < mappings.size(); ++i)
  225. {
  226. auto& cm = *mappings.getUnchecked(i);
  227. for (int j = 0; j < cm.keypresses.size(); ++j)
  228. {
  229. if (defaultSet == nullptr
  230. || ! defaultSet->containsMapping (cm.commandID, cm.keypresses.getReference (j)))
  231. {
  232. auto map = doc->createNewChildElement ("MAPPING");
  233. map->setAttribute ("commandId", String::toHexString ((int) cm.commandID));
  234. map->setAttribute ("description", commandManager.getDescriptionOfCommand (cm.commandID));
  235. map->setAttribute ("key", cm.keypresses.getReference (j).getTextDescription());
  236. }
  237. }
  238. }
  239. if (defaultSet != nullptr)
  240. {
  241. for (int i = 0; i < defaultSet->mappings.size(); ++i)
  242. {
  243. auto& cm = *defaultSet->mappings.getUnchecked(i);
  244. for (int j = 0; j < cm.keypresses.size(); ++j)
  245. {
  246. if (! containsMapping (cm.commandID, cm.keypresses.getReference (j)))
  247. {
  248. auto map = doc->createNewChildElement ("UNMAPPING");
  249. map->setAttribute ("commandId", String::toHexString ((int) cm.commandID));
  250. map->setAttribute ("description", commandManager.getDescriptionOfCommand (cm.commandID));
  251. map->setAttribute ("key", cm.keypresses.getReference (j).getTextDescription());
  252. }
  253. }
  254. }
  255. }
  256. return doc;
  257. }
  258. //==============================================================================
  259. bool KeyPressMappingSet::keyPressed (const KeyPress& key, Component* const originatingComponent)
  260. {
  261. bool commandWasDisabled = false;
  262. for (int i = 0; i < mappings.size(); ++i)
  263. {
  264. CommandMapping& cm = *mappings.getUnchecked(i);
  265. if (cm.keypresses.contains (key))
  266. {
  267. if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (cm.commandID))
  268. {
  269. if ((ci->flags & ApplicationCommandInfo::wantsKeyUpDownCallbacks) == 0)
  270. {
  271. ApplicationCommandInfo info (0);
  272. if (commandManager.getTargetForCommand (cm.commandID, info) != nullptr)
  273. {
  274. if ((info.flags & ApplicationCommandInfo::isDisabled) == 0)
  275. {
  276. invokeCommand (cm.commandID, key, true, 0, originatingComponent);
  277. return true;
  278. }
  279. commandWasDisabled = true;
  280. }
  281. }
  282. }
  283. }
  284. }
  285. if (originatingComponent != nullptr && commandWasDisabled)
  286. originatingComponent->getLookAndFeel().playAlertSound();
  287. return false;
  288. }
  289. bool KeyPressMappingSet::keyStateChanged (const bool /*isKeyDown*/, Component* originatingComponent)
  290. {
  291. bool used = false;
  292. const uint32 now = Time::getMillisecondCounter();
  293. for (int i = mappings.size(); --i >= 0;)
  294. {
  295. CommandMapping& cm = *mappings.getUnchecked(i);
  296. if (cm.wantsKeyUpDownCallbacks)
  297. {
  298. for (int j = cm.keypresses.size(); --j >= 0;)
  299. {
  300. const KeyPress key (cm.keypresses.getReference (j));
  301. const bool isDown = key.isCurrentlyDown();
  302. int keyPressEntryIndex = 0;
  303. bool wasDown = false;
  304. for (int k = keysDown.size(); --k >= 0;)
  305. {
  306. if (key == keysDown.getUnchecked(k)->key)
  307. {
  308. keyPressEntryIndex = k;
  309. wasDown = true;
  310. used = true;
  311. break;
  312. }
  313. }
  314. if (isDown != wasDown)
  315. {
  316. int millisecs = 0;
  317. if (isDown)
  318. {
  319. KeyPressTime* const k = new KeyPressTime();
  320. k->key = key;
  321. k->timeWhenPressed = now;
  322. keysDown.add (k);
  323. }
  324. else
  325. {
  326. const uint32 pressTime = keysDown.getUnchecked (keyPressEntryIndex)->timeWhenPressed;
  327. if (now > pressTime)
  328. millisecs = (int) (now - pressTime);
  329. keysDown.remove (keyPressEntryIndex);
  330. }
  331. invokeCommand (cm.commandID, key, isDown, millisecs, originatingComponent);
  332. used = true;
  333. }
  334. }
  335. }
  336. }
  337. return used;
  338. }
  339. void KeyPressMappingSet::globalFocusChanged (Component* focusedComponent)
  340. {
  341. if (focusedComponent != nullptr)
  342. focusedComponent->keyStateChanged (false);
  343. }
  344. } // namespace juce