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.

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