Audio plugin host https://kx.studio/carla
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.

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