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.

407 lines
14KB

  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. }
  68. }
  69. }
  70. static void addKeyPresses (KeyPressMappingSet& set, const ApplicationCommandInfo* const ci)
  71. {
  72. for (int j = 0; j < ci->defaultKeypresses.size(); ++j)
  73. set.addKeyPress (ci->commandID, ci->defaultKeypresses.getReference (j));
  74. }
  75. void KeyPressMappingSet::resetToDefaultMappings()
  76. {
  77. mappings.clear();
  78. for (int i = 0; i < commandManager.getNumCommands(); ++i)
  79. addKeyPresses (*this, commandManager.getCommandForIndex (i));
  80. sendChangeMessage();
  81. }
  82. void KeyPressMappingSet::resetToDefaultMapping (const CommandID commandID)
  83. {
  84. clearAllKeyPresses (commandID);
  85. if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (commandID))
  86. addKeyPresses (*this, ci);
  87. }
  88. void KeyPressMappingSet::clearAllKeyPresses()
  89. {
  90. if (mappings.size() > 0)
  91. {
  92. sendChangeMessage();
  93. mappings.clear();
  94. }
  95. }
  96. void KeyPressMappingSet::clearAllKeyPresses (const CommandID commandID)
  97. {
  98. for (int i = mappings.size(); --i >= 0;)
  99. {
  100. if (mappings.getUnchecked(i)->commandID == commandID)
  101. {
  102. mappings.remove (i);
  103. sendChangeMessage();
  104. }
  105. }
  106. }
  107. void KeyPressMappingSet::removeKeyPress (const KeyPress& keypress)
  108. {
  109. if (keypress.isValid())
  110. {
  111. for (int i = mappings.size(); --i >= 0;)
  112. {
  113. CommandMapping& cm = *mappings.getUnchecked(i);
  114. for (int j = cm.keypresses.size(); --j >= 0;)
  115. {
  116. if (keypress == cm.keypresses [j])
  117. {
  118. cm.keypresses.remove (j);
  119. sendChangeMessage();
  120. }
  121. }
  122. }
  123. }
  124. }
  125. void KeyPressMappingSet::removeKeyPress (const CommandID commandID, const int keyPressIndex)
  126. {
  127. for (int i = mappings.size(); --i >= 0;)
  128. {
  129. if (mappings.getUnchecked(i)->commandID == commandID)
  130. {
  131. mappings.getUnchecked(i)->keypresses.remove (keyPressIndex);
  132. sendChangeMessage();
  133. break;
  134. }
  135. }
  136. }
  137. //==============================================================================
  138. CommandID KeyPressMappingSet::findCommandForKeyPress (const KeyPress& keyPress) const noexcept
  139. {
  140. for (int i = 0; i < mappings.size(); ++i)
  141. if (mappings.getUnchecked(i)->keypresses.contains (keyPress))
  142. return mappings.getUnchecked(i)->commandID;
  143. return 0;
  144. }
  145. bool KeyPressMappingSet::containsMapping (const CommandID commandID, const KeyPress& keyPress) const noexcept
  146. {
  147. for (int i = mappings.size(); --i >= 0;)
  148. if (mappings.getUnchecked(i)->commandID == commandID)
  149. return mappings.getUnchecked(i)->keypresses.contains (keyPress);
  150. return false;
  151. }
  152. void KeyPressMappingSet::invokeCommand (const CommandID commandID,
  153. const KeyPress& key,
  154. const bool isKeyDown,
  155. const int millisecsSinceKeyPressed,
  156. Component* const originatingComponent) const
  157. {
  158. ApplicationCommandTarget::InvocationInfo info (commandID);
  159. info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromKeyPress;
  160. info.isKeyDown = isKeyDown;
  161. info.keyPress = key;
  162. info.millisecsSinceKeyPressed = millisecsSinceKeyPressed;
  163. info.originatingComponent = originatingComponent;
  164. commandManager.invoke (info, false);
  165. }
  166. //==============================================================================
  167. bool KeyPressMappingSet::restoreFromXml (const XmlElement& xmlVersion)
  168. {
  169. if (xmlVersion.hasTagName ("KEYMAPPINGS"))
  170. {
  171. if (xmlVersion.getBoolAttribute ("basedOnDefaults", true))
  172. {
  173. // if the XML was created as a set of differences from the default mappings,
  174. // (i.e. by calling createXml (true)), then we need to first restore the defaults.
  175. resetToDefaultMappings();
  176. }
  177. else
  178. {
  179. // if the XML was created calling createXml (false), then we need to clear all
  180. // the keys and treat the xml as describing the entire set of mappings.
  181. clearAllKeyPresses();
  182. }
  183. forEachXmlChildElement (xmlVersion, map)
  184. {
  185. const CommandID commandId = map->getStringAttribute ("commandId").getHexValue32();
  186. if (commandId != 0)
  187. {
  188. const KeyPress key (KeyPress::createFromDescription (map->getStringAttribute ("key")));
  189. if (map->hasTagName ("MAPPING"))
  190. {
  191. addKeyPress (commandId, key);
  192. }
  193. else if (map->hasTagName ("UNMAPPING"))
  194. {
  195. for (int i = mappings.size(); --i >= 0;)
  196. if (mappings.getUnchecked(i)->commandID == commandId)
  197. mappings.getUnchecked(i)->keypresses.removeAllInstancesOf (key);
  198. }
  199. }
  200. }
  201. return true;
  202. }
  203. return false;
  204. }
  205. XmlElement* KeyPressMappingSet::createXml (const bool saveDifferencesFromDefaultSet) const
  206. {
  207. ScopedPointer <KeyPressMappingSet> defaultSet;
  208. if (saveDifferencesFromDefaultSet)
  209. {
  210. defaultSet = new KeyPressMappingSet (commandManager);
  211. defaultSet->resetToDefaultMappings();
  212. }
  213. XmlElement* const doc = new XmlElement ("KEYMAPPINGS");
  214. doc->setAttribute ("basedOnDefaults", saveDifferencesFromDefaultSet);
  215. for (int i = 0; i < mappings.size(); ++i)
  216. {
  217. const CommandMapping& cm = *mappings.getUnchecked(i);
  218. for (int j = 0; j < cm.keypresses.size(); ++j)
  219. {
  220. if (defaultSet == nullptr
  221. || ! defaultSet->containsMapping (cm.commandID, cm.keypresses.getReference (j)))
  222. {
  223. XmlElement* const map = doc->createNewChildElement ("MAPPING");
  224. map->setAttribute ("commandId", String::toHexString ((int) cm.commandID));
  225. map->setAttribute ("description", commandManager.getDescriptionOfCommand (cm.commandID));
  226. map->setAttribute ("key", cm.keypresses.getReference (j).getTextDescription());
  227. }
  228. }
  229. }
  230. if (defaultSet != nullptr)
  231. {
  232. for (int i = 0; i < defaultSet->mappings.size(); ++i)
  233. {
  234. const CommandMapping& cm = *defaultSet->mappings.getUnchecked(i);
  235. for (int j = 0; j < cm.keypresses.size(); ++j)
  236. {
  237. if (! containsMapping (cm.commandID, cm.keypresses.getReference (j)))
  238. {
  239. XmlElement* const map = doc->createNewChildElement ("UNMAPPING");
  240. map->setAttribute ("commandId", String::toHexString ((int) cm.commandID));
  241. map->setAttribute ("description", commandManager.getDescriptionOfCommand (cm.commandID));
  242. map->setAttribute ("key", cm.keypresses.getReference (j).getTextDescription());
  243. }
  244. }
  245. }
  246. }
  247. return doc;
  248. }
  249. //==============================================================================
  250. bool KeyPressMappingSet::keyPressed (const KeyPress& key, Component* const originatingComponent)
  251. {
  252. bool commandWasDisabled = false;
  253. for (int i = 0; i < mappings.size(); ++i)
  254. {
  255. CommandMapping& cm = *mappings.getUnchecked(i);
  256. if (cm.keypresses.contains (key))
  257. {
  258. if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (cm.commandID))
  259. {
  260. if ((ci->flags & ApplicationCommandInfo::wantsKeyUpDownCallbacks) == 0)
  261. {
  262. ApplicationCommandInfo info (0);
  263. if (commandManager.getTargetForCommand (cm.commandID, info) != nullptr)
  264. {
  265. if ((info.flags & ApplicationCommandInfo::isDisabled) == 0)
  266. {
  267. invokeCommand (cm.commandID, key, true, 0, originatingComponent);
  268. return true;
  269. }
  270. commandWasDisabled = true;
  271. }
  272. }
  273. }
  274. }
  275. }
  276. if (originatingComponent != nullptr && commandWasDisabled)
  277. originatingComponent->getLookAndFeel().playAlertSound();
  278. return false;
  279. }
  280. bool KeyPressMappingSet::keyStateChanged (const bool /*isKeyDown*/, Component* originatingComponent)
  281. {
  282. bool used = false;
  283. const uint32 now = Time::getMillisecondCounter();
  284. for (int i = mappings.size(); --i >= 0;)
  285. {
  286. CommandMapping& cm = *mappings.getUnchecked(i);
  287. if (cm.wantsKeyUpDownCallbacks)
  288. {
  289. for (int j = cm.keypresses.size(); --j >= 0;)
  290. {
  291. const KeyPress key (cm.keypresses.getReference (j));
  292. const bool isDown = key.isCurrentlyDown();
  293. int keyPressEntryIndex = 0;
  294. bool wasDown = false;
  295. for (int k = keysDown.size(); --k >= 0;)
  296. {
  297. if (key == keysDown.getUnchecked(k)->key)
  298. {
  299. keyPressEntryIndex = k;
  300. wasDown = true;
  301. used = true;
  302. break;
  303. }
  304. }
  305. if (isDown != wasDown)
  306. {
  307. int millisecs = 0;
  308. if (isDown)
  309. {
  310. KeyPressTime* const k = new KeyPressTime();
  311. k->key = key;
  312. k->timeWhenPressed = now;
  313. keysDown.add (k);
  314. }
  315. else
  316. {
  317. const uint32 pressTime = keysDown.getUnchecked (keyPressEntryIndex)->timeWhenPressed;
  318. if (now > pressTime)
  319. millisecs = (int) (now - pressTime);
  320. keysDown.remove (keyPressEntryIndex);
  321. }
  322. invokeCommand (cm.commandID, key, isDown, millisecs, originatingComponent);
  323. used = true;
  324. }
  325. }
  326. }
  327. }
  328. return used;
  329. }
  330. void KeyPressMappingSet::globalFocusChanged (Component* focusedComponent)
  331. {
  332. if (focusedComponent != nullptr)
  333. focusedComponent->keyStateChanged (false);
  334. }