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.

488 lines
17KB

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