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.

473 lines
16KB

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