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

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