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.

425 lines
14KB

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