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.

415 lines
14KB

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