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