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.

566 lines
18KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 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. #include "../../../core/juce_StandardHeader.h"
  19. BEGIN_JUCE_NAMESPACE
  20. // N.B. these two includes are put here deliberately to avoid problems with
  21. // old GCCs failing on long include paths
  22. #include "../../../containers/juce_VoidArray.h"
  23. #include "../../../containers/juce_OwnedArray.h"
  24. #include "juce_KeyMappingEditorComponent.h"
  25. #include "../menus/juce_PopupMenu.h"
  26. #include "../windows/juce_AlertWindow.h"
  27. #include "../../../text/juce_LocalisedStrings.h"
  28. const int maxKeys = 3;
  29. //==============================================================================
  30. class KeyMappingChangeButton : public Button
  31. {
  32. public:
  33. KeyMappingChangeButton (KeyMappingEditorComponent* const owner_,
  34. const CommandID commandID_,
  35. const String& keyName,
  36. const int keyNum_)
  37. : Button (keyName),
  38. owner (owner_),
  39. commandID (commandID_),
  40. keyNum (keyNum_)
  41. {
  42. setWantsKeyboardFocus (false);
  43. setTriggeredOnMouseDown (keyNum >= 0);
  44. if (keyNum_ < 0)
  45. setTooltip (TRANS("adds a new key-mapping"));
  46. else
  47. setTooltip (TRANS("click to change this key-mapping"));
  48. }
  49. ~KeyMappingChangeButton()
  50. {
  51. }
  52. void paintButton (Graphics& g, bool isOver, bool isDown)
  53. {
  54. if (keyNum >= 0)
  55. {
  56. if (isEnabled())
  57. {
  58. const float alpha = isDown ? 0.3f : (isOver ? 0.15f : 0.08f);
  59. g.fillAll (owner->textColour.withAlpha (alpha));
  60. g.setOpacity (0.3f);
  61. g.drawBevel (0, 0, getWidth(), getHeight(), 2);
  62. }
  63. g.setColour (owner->textColour);
  64. g.setFont (getHeight() * 0.6f);
  65. g.drawFittedText (getName(),
  66. 3, 0, getWidth() - 6, getHeight(),
  67. Justification::centred, 1);
  68. }
  69. else
  70. {
  71. const float thickness = 7.0f;
  72. const float indent = 22.0f;
  73. Path p;
  74. p.addEllipse (0.0f, 0.0f, 100.0f, 100.0f);
  75. p.addRectangle (indent, 50.0f - thickness, 100.0f - indent * 2.0f, thickness * 2.0f);
  76. p.addRectangle (50.0f - thickness, indent, thickness * 2.0f, 50.0f - indent - thickness);
  77. p.addRectangle (50.0f - thickness, 50.0f + thickness, thickness * 2.0f, 50.0f - indent - thickness);
  78. p.setUsingNonZeroWinding (false);
  79. g.setColour (owner->textColour.withAlpha (isDown ? 0.7f : (isOver ? 0.5f : 0.3f)));
  80. g.fillPath (p, p.getTransformToScaleToFit (2.0f, 2.0f, getWidth() - 4.0f, getHeight() - 4.0f, true));
  81. }
  82. if (hasKeyboardFocus (false))
  83. {
  84. g.setColour (owner->textColour.withAlpha (0.4f));
  85. g.drawRect (0, 0, getWidth(), getHeight());
  86. }
  87. }
  88. void clicked()
  89. {
  90. if (keyNum >= 0)
  91. {
  92. // existing key clicked..
  93. PopupMenu m;
  94. m.addItem (1, TRANS("change this key-mapping"));
  95. m.addSeparator();
  96. m.addItem (2, TRANS("remove this key-mapping"));
  97. const int res = m.show();
  98. if (res == 1)
  99. {
  100. owner->assignNewKey (commandID, keyNum);
  101. }
  102. else if (res == 2)
  103. {
  104. owner->getMappings()->removeKeyPress (commandID, keyNum);
  105. }
  106. }
  107. else
  108. {
  109. // + button pressed..
  110. owner->assignNewKey (commandID, -1);
  111. }
  112. }
  113. void fitToContent (const int h) throw()
  114. {
  115. if (keyNum < 0)
  116. {
  117. setSize (h, h);
  118. }
  119. else
  120. {
  121. Font f (h * 0.6f);
  122. setSize (jlimit (h * 4, h * 8, 6 + f.getStringWidth (getName())), h);
  123. }
  124. }
  125. juce_UseDebuggingNewOperator
  126. private:
  127. KeyMappingEditorComponent* const owner;
  128. const CommandID commandID;
  129. const int keyNum;
  130. KeyMappingChangeButton (const KeyMappingChangeButton&);
  131. const KeyMappingChangeButton& operator= (const KeyMappingChangeButton&);
  132. };
  133. //==============================================================================
  134. class KeyMappingItemComponent : public Component
  135. {
  136. public:
  137. KeyMappingItemComponent (KeyMappingEditorComponent* const owner_,
  138. const CommandID commandID_)
  139. : owner (owner_),
  140. commandID (commandID_)
  141. {
  142. setInterceptsMouseClicks (false, true);
  143. const bool isReadOnly = owner_->isCommandReadOnly (commandID);
  144. const Array <KeyPress> keyPresses (owner_->getMappings()->getKeyPressesAssignedToCommand (commandID));
  145. for (int i = 0; i < jmin (maxKeys, keyPresses.size()); ++i)
  146. {
  147. KeyMappingChangeButton* const kb
  148. = new KeyMappingChangeButton (owner_, commandID,
  149. owner_->getDescriptionForKeyPress (keyPresses.getReference (i)), i);
  150. kb->setEnabled (! isReadOnly);
  151. addAndMakeVisible (kb);
  152. }
  153. KeyMappingChangeButton* const kb
  154. = new KeyMappingChangeButton (owner_, commandID, String::empty, -1);
  155. addChildComponent (kb);
  156. kb->setVisible (keyPresses.size() < maxKeys && ! isReadOnly);
  157. }
  158. ~KeyMappingItemComponent()
  159. {
  160. deleteAllChildren();
  161. }
  162. void paint (Graphics& g)
  163. {
  164. g.setFont (getHeight() * 0.7f);
  165. g.setColour (owner->textColour);
  166. g.drawFittedText (owner->getMappings()->getCommandManager()->getNameOfCommand (commandID),
  167. 4, 0, jmax (40, getChildComponent (0)->getX() - 5), getHeight(),
  168. Justification::centredLeft, true);
  169. }
  170. void resized()
  171. {
  172. int x = getWidth() - 4;
  173. for (int i = getNumChildComponents(); --i >= 0;)
  174. {
  175. KeyMappingChangeButton* const kb = dynamic_cast <KeyMappingChangeButton*> (getChildComponent (i));
  176. kb->fitToContent (getHeight() - 2);
  177. kb->setTopRightPosition (x, 1);
  178. x -= kb->getWidth() + 5;
  179. }
  180. }
  181. juce_UseDebuggingNewOperator
  182. private:
  183. KeyMappingEditorComponent* const owner;
  184. const CommandID commandID;
  185. KeyMappingItemComponent (const KeyMappingItemComponent&);
  186. const KeyMappingItemComponent& operator= (const KeyMappingItemComponent&);
  187. };
  188. //==============================================================================
  189. class KeyMappingTreeViewItem : public TreeViewItem
  190. {
  191. public:
  192. KeyMappingTreeViewItem (KeyMappingEditorComponent* const owner_,
  193. const CommandID commandID_)
  194. : owner (owner_),
  195. commandID (commandID_)
  196. {
  197. }
  198. ~KeyMappingTreeViewItem()
  199. {
  200. }
  201. const String getUniqueName() const { return String ((int) commandID) + "_id"; }
  202. bool mightContainSubItems() { return false; }
  203. int getItemHeight() const { return 20; }
  204. Component* createItemComponent()
  205. {
  206. return new KeyMappingItemComponent (owner, commandID);
  207. }
  208. juce_UseDebuggingNewOperator
  209. private:
  210. KeyMappingEditorComponent* const owner;
  211. const CommandID commandID;
  212. KeyMappingTreeViewItem (const KeyMappingTreeViewItem&);
  213. const KeyMappingTreeViewItem& operator= (const KeyMappingTreeViewItem&);
  214. };
  215. //==============================================================================
  216. class KeyCategoryTreeViewItem : public TreeViewItem
  217. {
  218. public:
  219. KeyCategoryTreeViewItem (KeyMappingEditorComponent* const owner_,
  220. const String& name)
  221. : owner (owner_),
  222. categoryName (name)
  223. {
  224. }
  225. ~KeyCategoryTreeViewItem()
  226. {
  227. }
  228. const String getUniqueName() const { return categoryName + "_cat"; }
  229. bool mightContainSubItems() { return true; }
  230. int getItemHeight() const { return 28; }
  231. void paintItem (Graphics& g, int width, int height)
  232. {
  233. g.setFont (height * 0.6f, Font::bold);
  234. g.setColour (owner->textColour);
  235. g.drawText (categoryName,
  236. 2, 0, width - 2, height,
  237. Justification::centredLeft, true);
  238. }
  239. void itemOpennessChanged (bool isNowOpen)
  240. {
  241. if (isNowOpen)
  242. {
  243. if (getNumSubItems() == 0)
  244. {
  245. Array <CommandID> commands (owner->getMappings()->getCommandManager()->getCommandsInCategory (categoryName));
  246. for (int i = 0; i < commands.size(); ++i)
  247. {
  248. if (owner->shouldCommandBeIncluded (commands[i]))
  249. addSubItem (new KeyMappingTreeViewItem (owner, commands[i]));
  250. }
  251. }
  252. }
  253. else
  254. {
  255. clearSubItems();
  256. }
  257. }
  258. juce_UseDebuggingNewOperator
  259. private:
  260. KeyMappingEditorComponent* owner;
  261. String categoryName;
  262. KeyCategoryTreeViewItem (const KeyCategoryTreeViewItem&);
  263. const KeyCategoryTreeViewItem& operator= (const KeyCategoryTreeViewItem&);
  264. };
  265. //==============================================================================
  266. KeyMappingEditorComponent::KeyMappingEditorComponent (KeyPressMappingSet* const mappingManager,
  267. const bool showResetToDefaultButton)
  268. : mappings (mappingManager),
  269. textColour (Colours::black)
  270. {
  271. jassert (mappingManager != 0); // can't be null!
  272. mappingManager->addChangeListener (this);
  273. setLinesDrawnForSubItems (false);
  274. resetButton = 0;
  275. if (showResetToDefaultButton)
  276. {
  277. addAndMakeVisible (resetButton = new TextButton (TRANS("reset to defaults")));
  278. resetButton->addButtonListener (this);
  279. }
  280. addAndMakeVisible (tree = new TreeView());
  281. tree->setColour (TreeView::backgroundColourId, backgroundColour);
  282. tree->setRootItemVisible (false);
  283. tree->setDefaultOpenness (true);
  284. tree->setRootItem (this);
  285. }
  286. KeyMappingEditorComponent::~KeyMappingEditorComponent()
  287. {
  288. mappings->removeChangeListener (this);
  289. deleteAllChildren();
  290. }
  291. //==============================================================================
  292. bool KeyMappingEditorComponent::mightContainSubItems()
  293. {
  294. return true;
  295. }
  296. const String KeyMappingEditorComponent::getUniqueName() const
  297. {
  298. return T("keys");
  299. }
  300. void KeyMappingEditorComponent::setColours (const Colour& mainBackground,
  301. const Colour& textColour_)
  302. {
  303. backgroundColour = mainBackground;
  304. textColour = textColour_;
  305. tree->setColour (TreeView::backgroundColourId, backgroundColour);
  306. }
  307. void KeyMappingEditorComponent::parentHierarchyChanged()
  308. {
  309. changeListenerCallback (0);
  310. }
  311. void KeyMappingEditorComponent::resized()
  312. {
  313. int h = getHeight();
  314. if (resetButton != 0)
  315. {
  316. const int buttonHeight = 20;
  317. h -= buttonHeight + 8;
  318. int x = getWidth() - 8;
  319. const int y = h + 6;
  320. resetButton->changeWidthToFitText (buttonHeight);
  321. resetButton->setTopRightPosition (x, y);
  322. }
  323. tree->setBounds (0, 0, getWidth(), h);
  324. }
  325. void KeyMappingEditorComponent::buttonClicked (Button* button)
  326. {
  327. if (button == resetButton)
  328. {
  329. if (AlertWindow::showOkCancelBox (AlertWindow::QuestionIcon,
  330. TRANS("Reset to defaults"),
  331. TRANS("Are you sure you want to reset all the key-mappings to their default state?"),
  332. TRANS("Reset")))
  333. {
  334. mappings->resetToDefaultMappings();
  335. }
  336. }
  337. }
  338. void KeyMappingEditorComponent::changeListenerCallback (void*)
  339. {
  340. XmlElement* openness = tree->getOpennessState (true);
  341. clearSubItems();
  342. const StringArray categories (mappings->getCommandManager()->getCommandCategories());
  343. for (int i = 0; i < categories.size(); ++i)
  344. {
  345. const Array <CommandID> commands (mappings->getCommandManager()->getCommandsInCategory (categories[i]));
  346. int count = 0;
  347. for (int j = 0; j < commands.size(); ++j)
  348. if (shouldCommandBeIncluded (commands[j]))
  349. ++count;
  350. if (count > 0)
  351. addSubItem (new KeyCategoryTreeViewItem (this, categories[i]));
  352. }
  353. if (openness != 0)
  354. {
  355. tree->restoreOpennessState (*openness);
  356. delete openness;
  357. }
  358. }
  359. //==============================================================================
  360. class KeyEntryWindow : public AlertWindow
  361. {
  362. public:
  363. KeyEntryWindow (KeyMappingEditorComponent* const owner_)
  364. : AlertWindow (TRANS("New key-mapping"),
  365. TRANS("Please press a key combination now..."),
  366. AlertWindow::NoIcon),
  367. owner (owner_)
  368. {
  369. addButton (TRANS("ok"), 1);
  370. addButton (TRANS("cancel"), 0);
  371. // (avoid return + escape keys getting processed by the buttons..)
  372. for (int i = getNumChildComponents(); --i >= 0;)
  373. getChildComponent (i)->setWantsKeyboardFocus (false);
  374. setWantsKeyboardFocus (true);
  375. grabKeyboardFocus();
  376. }
  377. ~KeyEntryWindow()
  378. {
  379. }
  380. bool keyPressed (const KeyPress& key)
  381. {
  382. lastPress = key;
  383. String message (TRANS("Key: ") + owner->getDescriptionForKeyPress (key));
  384. const CommandID previousCommand = owner->getMappings()->findCommandForKeyPress (key);
  385. if (previousCommand != 0)
  386. {
  387. message << "\n\n"
  388. << TRANS("(Currently assigned to \"")
  389. << owner->getMappings()->getCommandManager()->getNameOfCommand (previousCommand)
  390. << "\")";
  391. }
  392. setMessage (message);
  393. return true;
  394. }
  395. bool keyStateChanged (const bool)
  396. {
  397. return true;
  398. }
  399. KeyPress lastPress;
  400. juce_UseDebuggingNewOperator
  401. private:
  402. KeyMappingEditorComponent* owner;
  403. KeyEntryWindow (const KeyEntryWindow&);
  404. const KeyEntryWindow& operator= (const KeyEntryWindow&);
  405. };
  406. void KeyMappingEditorComponent::assignNewKey (const CommandID commandID, const int index)
  407. {
  408. KeyEntryWindow entryWindow (this);
  409. if (entryWindow.runModalLoop() != 0)
  410. {
  411. entryWindow.setVisible (false);
  412. if (entryWindow.lastPress.isValid())
  413. {
  414. const CommandID previousCommand = mappings->findCommandForKeyPress (entryWindow.lastPress);
  415. if (previousCommand != 0)
  416. {
  417. if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
  418. TRANS("Change key-mapping"),
  419. TRANS("This key is already assigned to the command \"")
  420. + mappings->getCommandManager()->getNameOfCommand (previousCommand)
  421. + TRANS("\"\n\nDo you want to re-assign it to this new command instead?"),
  422. TRANS("re-assign"),
  423. TRANS("cancel")))
  424. {
  425. return;
  426. }
  427. }
  428. mappings->removeKeyPress (entryWindow.lastPress);
  429. if (index >= 0)
  430. mappings->removeKeyPress (commandID, index);
  431. mappings->addKeyPress (commandID, entryWindow.lastPress, index);
  432. }
  433. }
  434. }
  435. //==============================================================================
  436. bool KeyMappingEditorComponent::shouldCommandBeIncluded (const CommandID commandID)
  437. {
  438. const ApplicationCommandInfo* const ci = mappings->getCommandManager()->getCommandForID (commandID);
  439. return (ci != 0) && ((ci->flags & ApplicationCommandInfo::hiddenFromKeyEditor) == 0);
  440. }
  441. bool KeyMappingEditorComponent::isCommandReadOnly (const CommandID commandID)
  442. {
  443. const ApplicationCommandInfo* const ci = mappings->getCommandManager()->getCommandForID (commandID);
  444. return (ci != 0) && ((ci->flags & ApplicationCommandInfo::readOnlyInKeyEditor) != 0);
  445. }
  446. const String KeyMappingEditorComponent::getDescriptionForKeyPress (const KeyPress& key)
  447. {
  448. return key.getTextDescription();
  449. }
  450. END_JUCE_NAMESPACE