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

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