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.

474 lines
16KB

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