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.

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