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.

486 lines
16KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-10 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. #include "../../../core/juce_StandardHeader.h"
  19. BEGIN_JUCE_NAMESPACE
  20. // N.B. these two includes are put here deliberately to avoid problems with
  21. // old GCCs failing on long include paths
  22. #include "../../../containers/juce_Array.h"
  23. #include "../../../containers/juce_OwnedArray.h"
  24. #include "juce_KeyMappingEditorComponent.h"
  25. #include "../menus/juce_PopupMenu.h"
  26. #include "../windows/juce_AlertWindow.h"
  27. #include "../lookandfeel/juce_LookAndFeel.h"
  28. #include "../../../text/juce_LocalisedStrings.h"
  29. //==============================================================================
  30. class KeyMappingEditorComponent::ChangeKeyButton : public Button
  31. {
  32. public:
  33. ChangeKeyButton (KeyMappingEditorComponent& owner_,
  34. const CommandID commandID_,
  35. const String& keyName,
  36. const int keyNum_)
  37. : Button (keyName),
  38. owner (owner_),
  39. commandID (commandID_),
  40. keyNum (keyNum_)
  41. {
  42. setWantsKeyboardFocus (false);
  43. setTriggeredOnMouseDown (keyNum >= 0);
  44. setTooltip (keyNum_ < 0 ? TRANS("adds a new key-mapping")
  45. : TRANS("click to change this key-mapping"));
  46. }
  47. void paintButton (Graphics& g, bool /*isOver*/, bool /*isDown*/)
  48. {
  49. getLookAndFeel().drawKeymapChangeButton (g, getWidth(), getHeight(), *this,
  50. keyNum >= 0 ? getName() : String::empty);
  51. }
  52. void clicked()
  53. {
  54. if (keyNum >= 0)
  55. {
  56. // existing key clicked..
  57. PopupMenu m;
  58. m.addItem (1, TRANS("change this key-mapping"));
  59. m.addSeparator();
  60. m.addItem (2, TRANS("remove this key-mapping"));
  61. switch (m.show())
  62. {
  63. case 1: assignNewKey(); break;
  64. case 2: owner.getMappings().removeKeyPress (commandID, keyNum); break;
  65. default: break;
  66. }
  67. }
  68. else
  69. {
  70. assignNewKey(); // + button pressed..
  71. }
  72. }
  73. void fitToContent (const int h) throw()
  74. {
  75. if (keyNum < 0)
  76. {
  77. setSize (h, h);
  78. }
  79. else
  80. {
  81. Font f (h * 0.6f);
  82. setSize (jlimit (h * 4, h * 8, 6 + f.getStringWidth (getName())), h);
  83. }
  84. }
  85. //==============================================================================
  86. class KeyEntryWindow : public AlertWindow
  87. {
  88. public:
  89. KeyEntryWindow (KeyMappingEditorComponent& owner_)
  90. : AlertWindow (TRANS("New key-mapping"),
  91. TRANS("Please press a key combination now..."),
  92. AlertWindow::NoIcon),
  93. owner (owner_)
  94. {
  95. addButton (TRANS("Ok"), 1);
  96. addButton (TRANS("Cancel"), 0);
  97. // (avoid return + escape keys getting processed by the buttons..)
  98. for (int i = getNumChildComponents(); --i >= 0;)
  99. getChildComponent (i)->setWantsKeyboardFocus (false);
  100. setWantsKeyboardFocus (true);
  101. grabKeyboardFocus();
  102. }
  103. bool keyPressed (const KeyPress& key)
  104. {
  105. lastPress = key;
  106. String message (TRANS("Key: ") + owner.getDescriptionForKeyPress (key));
  107. const CommandID previousCommand = owner.getMappings().findCommandForKeyPress (key);
  108. if (previousCommand != 0)
  109. message << "\n\n" << TRANS("(Currently assigned to \"")
  110. << owner.getMappings().getCommandManager()->getNameOfCommand (previousCommand) << "\")";
  111. setMessage (message);
  112. return true;
  113. }
  114. bool keyStateChanged (bool)
  115. {
  116. return true;
  117. }
  118. KeyPress lastPress;
  119. private:
  120. KeyMappingEditorComponent& owner;
  121. KeyEntryWindow (const KeyEntryWindow&);
  122. KeyEntryWindow& operator= (const KeyEntryWindow&);
  123. };
  124. void assignNewKey()
  125. {
  126. KeyEntryWindow entryWindow (owner);
  127. if (entryWindow.runModalLoop() != 0)
  128. {
  129. entryWindow.setVisible (false);
  130. if (entryWindow.lastPress.isValid())
  131. {
  132. const CommandID previousCommand = owner.getMappings().findCommandForKeyPress (entryWindow.lastPress);
  133. if (previousCommand == 0
  134. || AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
  135. TRANS("Change key-mapping"),
  136. TRANS("This key is already assigned to the command \"")
  137. + owner.getMappings().getCommandManager()->getNameOfCommand (previousCommand)
  138. + TRANS("\"\n\nDo you want to re-assign it to this new command instead?"),
  139. TRANS("Re-assign"),
  140. TRANS("Cancel")))
  141. {
  142. owner.getMappings().removeKeyPress (entryWindow.lastPress);
  143. if (keyNum >= 0)
  144. owner.getMappings().removeKeyPress (commandID, keyNum);
  145. owner.getMappings().addKeyPress (commandID, entryWindow.lastPress, keyNum);
  146. }
  147. }
  148. }
  149. }
  150. juce_UseDebuggingNewOperator
  151. private:
  152. KeyMappingEditorComponent& owner;
  153. const CommandID commandID;
  154. const int keyNum;
  155. ChangeKeyButton (const ChangeKeyButton&);
  156. ChangeKeyButton& operator= (const ChangeKeyButton&);
  157. };
  158. //==============================================================================
  159. class KeyMappingEditorComponent::ItemComponent : public Component
  160. {
  161. public:
  162. ItemComponent (KeyMappingEditorComponent& owner_, const CommandID commandID_)
  163. : owner (owner_), commandID (commandID_)
  164. {
  165. setInterceptsMouseClicks (false, true);
  166. const bool isReadOnly = owner.isCommandReadOnly (commandID);
  167. const Array <KeyPress> keyPresses (owner.getMappings().getKeyPressesAssignedToCommand (commandID));
  168. for (int i = 0; i < jmin ((int) maxNumAssignments, keyPresses.size()); ++i)
  169. addKeyPressButton (owner.getDescriptionForKeyPress (keyPresses.getReference (i)), i, isReadOnly);
  170. addKeyPressButton (String::empty, -1, isReadOnly);
  171. }
  172. void addKeyPressButton (const String& desc, const int index, const bool isReadOnly)
  173. {
  174. ChangeKeyButton* const b = new ChangeKeyButton (owner, commandID, desc, index);
  175. keyChangeButtons.add (b);
  176. b->setEnabled (! isReadOnly);
  177. b->setVisible (keyChangeButtons.size() <= (int) maxNumAssignments);
  178. addChildComponent (b);
  179. }
  180. void paint (Graphics& g)
  181. {
  182. g.setFont (getHeight() * 0.7f);
  183. g.setColour (findColour (KeyMappingEditorComponent::textColourId));
  184. g.drawFittedText (owner.getMappings().getCommandManager()->getNameOfCommand (commandID),
  185. 4, 0, jmax (40, getChildComponent (0)->getX() - 5), getHeight(),
  186. Justification::centredLeft, true);
  187. }
  188. void resized()
  189. {
  190. int x = getWidth() - 4;
  191. for (int i = keyChangeButtons.size(); --i >= 0;)
  192. {
  193. ChangeKeyButton* const b = keyChangeButtons.getUnchecked(i);
  194. b->fitToContent (getHeight() - 2);
  195. b->setTopRightPosition (x, 1);
  196. x = b->getX() - 5;
  197. }
  198. }
  199. juce_UseDebuggingNewOperator
  200. private:
  201. KeyMappingEditorComponent& owner;
  202. OwnedArray<ChangeKeyButton> keyChangeButtons;
  203. const CommandID commandID;
  204. enum { maxNumAssignments = 3 };
  205. ItemComponent (const ItemComponent&);
  206. ItemComponent& operator= (const ItemComponent&);
  207. };
  208. //==============================================================================
  209. class KeyMappingEditorComponent::MappingItem : public TreeViewItem
  210. {
  211. public:
  212. MappingItem (KeyMappingEditorComponent& owner_, const CommandID commandID_)
  213. : owner (owner_), commandID (commandID_)
  214. {
  215. }
  216. const String getUniqueName() const { return String ((int) commandID) + "_id"; }
  217. bool mightContainSubItems() { return false; }
  218. int getItemHeight() const { return 20; }
  219. Component* createItemComponent()
  220. {
  221. return new ItemComponent (owner, commandID);
  222. }
  223. juce_UseDebuggingNewOperator
  224. private:
  225. KeyMappingEditorComponent& owner;
  226. const CommandID commandID;
  227. MappingItem (const MappingItem&);
  228. MappingItem& operator= (const MappingItem&);
  229. };
  230. //==============================================================================
  231. class KeyMappingEditorComponent::CategoryItem : public TreeViewItem
  232. {
  233. public:
  234. CategoryItem (KeyMappingEditorComponent& owner_, const String& name)
  235. : owner (owner_), categoryName (name)
  236. {
  237. }
  238. const String getUniqueName() const { return categoryName + "_cat"; }
  239. bool mightContainSubItems() { return true; }
  240. int getItemHeight() const { return 28; }
  241. void paintItem (Graphics& g, int width, int height)
  242. {
  243. g.setFont (height * 0.6f, Font::bold);
  244. g.setColour (owner.findColour (KeyMappingEditorComponent::textColourId));
  245. g.drawText (categoryName,
  246. 2, 0, width - 2, height,
  247. Justification::centredLeft, true);
  248. }
  249. void itemOpennessChanged (bool isNowOpen)
  250. {
  251. if (isNowOpen)
  252. {
  253. if (getNumSubItems() == 0)
  254. {
  255. Array <CommandID> commands (owner.getMappings().getCommandManager()->getCommandsInCategory (categoryName));
  256. for (int i = 0; i < commands.size(); ++i)
  257. {
  258. if (owner.shouldCommandBeIncluded (commands[i]))
  259. addSubItem (new MappingItem (owner, commands[i]));
  260. }
  261. }
  262. }
  263. else
  264. {
  265. clearSubItems();
  266. }
  267. }
  268. juce_UseDebuggingNewOperator
  269. private:
  270. KeyMappingEditorComponent& owner;
  271. String categoryName;
  272. CategoryItem (const CategoryItem&);
  273. CategoryItem& operator= (const CategoryItem&);
  274. };
  275. //==============================================================================
  276. class KeyMappingEditorComponent::TopLevelItem : public TreeViewItem,
  277. public ChangeListener,
  278. public ButtonListener
  279. {
  280. public:
  281. TopLevelItem (KeyMappingEditorComponent& owner_)
  282. : owner (owner_)
  283. {
  284. setLinesDrawnForSubItems (false);
  285. owner.getMappings().addChangeListener (this);
  286. }
  287. ~TopLevelItem()
  288. {
  289. owner.getMappings().removeChangeListener (this);
  290. }
  291. bool mightContainSubItems() { return true; }
  292. const String getUniqueName() const { return "keys"; }
  293. void changeListenerCallback (ChangeBroadcaster*)
  294. {
  295. const ScopedPointer <XmlElement> oldOpenness (owner.tree.getOpennessState (true));
  296. clearSubItems();
  297. const StringArray categories (owner.getMappings().getCommandManager()->getCommandCategories());
  298. for (int i = 0; i < categories.size(); ++i)
  299. {
  300. const Array <CommandID> commands (owner.getMappings().getCommandManager()->getCommandsInCategory (categories[i]));
  301. int count = 0;
  302. for (int j = 0; j < commands.size(); ++j)
  303. if (owner.shouldCommandBeIncluded (commands[j]))
  304. ++count;
  305. if (count > 0)
  306. addSubItem (new CategoryItem (owner, categories[i]));
  307. }
  308. if (oldOpenness != 0)
  309. owner.tree.restoreOpennessState (*oldOpenness);
  310. }
  311. void buttonClicked (Button*)
  312. {
  313. if (AlertWindow::showOkCancelBox (AlertWindow::QuestionIcon,
  314. TRANS("Reset to defaults"),
  315. TRANS("Are you sure you want to reset all the key-mappings to their default state?"),
  316. TRANS("Reset")))
  317. {
  318. owner.getMappings().resetToDefaultMappings();
  319. }
  320. }
  321. private:
  322. KeyMappingEditorComponent& owner;
  323. };
  324. //==============================================================================
  325. KeyMappingEditorComponent::KeyMappingEditorComponent (KeyPressMappingSet& mappingManager,
  326. const bool showResetToDefaultButton)
  327. : mappings (mappingManager),
  328. resetButton (TRANS ("reset to defaults"))
  329. {
  330. treeItem = new TopLevelItem (*this);
  331. if (showResetToDefaultButton)
  332. {
  333. addAndMakeVisible (&resetButton);
  334. resetButton.addButtonListener (treeItem);
  335. }
  336. addAndMakeVisible (&tree);
  337. tree.setColour (TreeView::backgroundColourId, findColour (backgroundColourId));
  338. tree.setRootItemVisible (false);
  339. tree.setDefaultOpenness (true);
  340. tree.setRootItem (treeItem);
  341. }
  342. KeyMappingEditorComponent::~KeyMappingEditorComponent()
  343. {
  344. tree.setRootItem (0);
  345. }
  346. //==============================================================================
  347. void KeyMappingEditorComponent::setColours (const Colour& mainBackground,
  348. const Colour& textColour)
  349. {
  350. setColour (backgroundColourId, mainBackground);
  351. setColour (textColourId, textColour);
  352. tree.setColour (TreeView::backgroundColourId, mainBackground);
  353. }
  354. void KeyMappingEditorComponent::parentHierarchyChanged()
  355. {
  356. treeItem->changeListenerCallback (0);
  357. }
  358. void KeyMappingEditorComponent::resized()
  359. {
  360. int h = getHeight();
  361. if (resetButton.isVisible())
  362. {
  363. const int buttonHeight = 20;
  364. h -= buttonHeight + 8;
  365. int x = getWidth() - 8;
  366. resetButton.changeWidthToFitText (buttonHeight);
  367. resetButton.setTopRightPosition (x, h + 6);
  368. }
  369. tree.setBounds (0, 0, getWidth(), h);
  370. }
  371. //==============================================================================
  372. bool KeyMappingEditorComponent::shouldCommandBeIncluded (const CommandID commandID)
  373. {
  374. const ApplicationCommandInfo* const ci = mappings.getCommandManager()->getCommandForID (commandID);
  375. return ci != 0 && (ci->flags & ApplicationCommandInfo::hiddenFromKeyEditor) == 0;
  376. }
  377. bool KeyMappingEditorComponent::isCommandReadOnly (const CommandID commandID)
  378. {
  379. const ApplicationCommandInfo* const ci = mappings.getCommandManager()->getCommandForID (commandID);
  380. return ci != 0 && (ci->flags & ApplicationCommandInfo::readOnlyInKeyEditor) != 0;
  381. }
  382. const String KeyMappingEditorComponent::getDescriptionForKeyPress (const KeyPress& key)
  383. {
  384. return key.getTextDescription();
  385. }
  386. END_JUCE_NAMESPACE