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.

355 lines
9.8KB

  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. MenuBarComponent::MenuBarComponent (MenuBarModel* m)
  20. : model (nullptr),
  21. itemUnderMouse (-1),
  22. currentPopupIndex (-1),
  23. topLevelIndexClicked (0)
  24. {
  25. setRepaintsOnMouseActivity (true);
  26. setWantsKeyboardFocus (false);
  27. setMouseClickGrabsKeyboardFocus (false);
  28. setModel (m);
  29. }
  30. MenuBarComponent::~MenuBarComponent()
  31. {
  32. setModel (nullptr);
  33. Desktop::getInstance().removeGlobalMouseListener (this);
  34. }
  35. MenuBarModel* MenuBarComponent::getModel() const noexcept
  36. {
  37. return model;
  38. }
  39. void MenuBarComponent::setModel (MenuBarModel* const newModel)
  40. {
  41. if (model != newModel)
  42. {
  43. if (model != nullptr)
  44. model->removeListener (this);
  45. model = newModel;
  46. if (model != nullptr)
  47. model->addListener (this);
  48. repaint();
  49. menuBarItemsChanged (nullptr);
  50. }
  51. }
  52. //==============================================================================
  53. void MenuBarComponent::paint (Graphics& g)
  54. {
  55. const bool isMouseOverBar = currentPopupIndex >= 0 || itemUnderMouse >= 0 || isMouseOver();
  56. getLookAndFeel().drawMenuBarBackground (g,
  57. getWidth(),
  58. getHeight(),
  59. isMouseOverBar,
  60. *this);
  61. if (model != nullptr)
  62. {
  63. for (int i = 0; i < menuNames.size(); ++i)
  64. {
  65. Graphics::ScopedSaveState ss (g);
  66. g.setOrigin (xPositions [i], 0);
  67. g.reduceClipRegion (0, 0, xPositions[i + 1] - xPositions[i], getHeight());
  68. getLookAndFeel().drawMenuBarItem (g,
  69. xPositions[i + 1] - xPositions[i],
  70. getHeight(),
  71. i,
  72. menuNames[i],
  73. i == itemUnderMouse,
  74. i == currentPopupIndex,
  75. isMouseOverBar,
  76. *this);
  77. }
  78. }
  79. }
  80. void MenuBarComponent::resized()
  81. {
  82. xPositions.clear();
  83. int x = 0;
  84. xPositions.add (x);
  85. for (int i = 0; i < menuNames.size(); ++i)
  86. {
  87. x += getLookAndFeel().getMenuBarItemWidth (*this, i, menuNames[i]);
  88. xPositions.add (x);
  89. }
  90. }
  91. int MenuBarComponent::getItemAt (Point<int> p)
  92. {
  93. for (int i = 0; i < xPositions.size(); ++i)
  94. if (p.x >= xPositions[i] && p.x < xPositions[i + 1])
  95. return reallyContains (p, true) ? i : -1;
  96. return -1;
  97. }
  98. void MenuBarComponent::repaintMenuItem (int index)
  99. {
  100. if (isPositiveAndBelow (index, xPositions.size()))
  101. {
  102. const int x1 = xPositions [index];
  103. const int x2 = xPositions [index + 1];
  104. repaint (x1 - 2, 0, x2 - x1 + 4, getHeight());
  105. }
  106. }
  107. void MenuBarComponent::setItemUnderMouse (const int index)
  108. {
  109. if (itemUnderMouse != index)
  110. {
  111. repaintMenuItem (itemUnderMouse);
  112. itemUnderMouse = index;
  113. repaintMenuItem (itemUnderMouse);
  114. }
  115. }
  116. void MenuBarComponent::setOpenItem (int index)
  117. {
  118. if (currentPopupIndex != index)
  119. {
  120. if (currentPopupIndex < 0 && index >= 0)
  121. model->handleMenuBarActivate (true);
  122. else if (currentPopupIndex >= 0 && index < 0)
  123. model->handleMenuBarActivate (false);
  124. repaintMenuItem (currentPopupIndex);
  125. currentPopupIndex = index;
  126. repaintMenuItem (currentPopupIndex);
  127. Desktop& desktop = Desktop::getInstance();
  128. if (index >= 0)
  129. desktop.addGlobalMouseListener (this);
  130. else
  131. desktop.removeGlobalMouseListener (this);
  132. }
  133. }
  134. void MenuBarComponent::updateItemUnderMouse (Point<int> p)
  135. {
  136. setItemUnderMouse (getItemAt (p));
  137. }
  138. void MenuBarComponent::showMenu (int index)
  139. {
  140. if (index != currentPopupIndex)
  141. {
  142. PopupMenu::dismissAllActiveMenus();
  143. menuBarItemsChanged (nullptr);
  144. setOpenItem (index);
  145. setItemUnderMouse (index);
  146. if (index >= 0)
  147. {
  148. PopupMenu m (model->getMenuForIndex (itemUnderMouse,
  149. menuNames [itemUnderMouse]));
  150. if (m.lookAndFeel == nullptr)
  151. m.setLookAndFeel (&getLookAndFeel());
  152. const Rectangle<int> itemPos (xPositions [index], 0, xPositions [index + 1] - xPositions [index], getHeight());
  153. m.showMenuAsync (PopupMenu::Options().withTargetComponent (this)
  154. .withTargetScreenArea (localAreaToGlobal (itemPos))
  155. .withMinimumWidth (itemPos.getWidth()),
  156. ModalCallbackFunction::forComponent (menuBarMenuDismissedCallback, this, index));
  157. }
  158. }
  159. }
  160. void MenuBarComponent::menuBarMenuDismissedCallback (int result, MenuBarComponent* bar, int topLevelIndex)
  161. {
  162. if (bar != nullptr)
  163. bar->menuDismissed (topLevelIndex, result);
  164. }
  165. void MenuBarComponent::menuDismissed (int topLevelIndex, int itemId)
  166. {
  167. topLevelIndexClicked = topLevelIndex;
  168. postCommandMessage (itemId);
  169. }
  170. void MenuBarComponent::handleCommandMessage (int commandId)
  171. {
  172. const Point<int> mousePos (getMouseXYRelative());
  173. updateItemUnderMouse (mousePos);
  174. if (currentPopupIndex == topLevelIndexClicked)
  175. setOpenItem (-1);
  176. if (commandId != 0 && model != nullptr)
  177. model->menuItemSelected (commandId, topLevelIndexClicked);
  178. }
  179. //==============================================================================
  180. void MenuBarComponent::mouseEnter (const MouseEvent& e)
  181. {
  182. if (e.eventComponent == this)
  183. updateItemUnderMouse (e.getPosition());
  184. }
  185. void MenuBarComponent::mouseExit (const MouseEvent& e)
  186. {
  187. if (e.eventComponent == this)
  188. updateItemUnderMouse (e.getPosition());
  189. }
  190. void MenuBarComponent::mouseDown (const MouseEvent& e)
  191. {
  192. if (currentPopupIndex < 0)
  193. {
  194. const MouseEvent e2 (e.getEventRelativeTo (this));
  195. updateItemUnderMouse (e2.getPosition());
  196. currentPopupIndex = -2;
  197. showMenu (itemUnderMouse);
  198. }
  199. }
  200. void MenuBarComponent::mouseDrag (const MouseEvent& e)
  201. {
  202. const MouseEvent e2 (e.getEventRelativeTo (this));
  203. const int item = getItemAt (e2.getPosition());
  204. if (item >= 0)
  205. showMenu (item);
  206. }
  207. void MenuBarComponent::mouseUp (const MouseEvent& e)
  208. {
  209. const MouseEvent e2 (e.getEventRelativeTo (this));
  210. updateItemUnderMouse (e2.getPosition());
  211. if (itemUnderMouse < 0 && getLocalBounds().contains (e2.x, e2.y))
  212. {
  213. setOpenItem (-1);
  214. PopupMenu::dismissAllActiveMenus();
  215. }
  216. }
  217. void MenuBarComponent::mouseMove (const MouseEvent& e)
  218. {
  219. const MouseEvent e2 (e.getEventRelativeTo (this));
  220. if (lastMousePos != e2.getPosition())
  221. {
  222. if (currentPopupIndex >= 0)
  223. {
  224. const int item = getItemAt (e2.getPosition());
  225. if (item >= 0)
  226. showMenu (item);
  227. }
  228. else
  229. {
  230. updateItemUnderMouse (e2.getPosition());
  231. }
  232. lastMousePos = e2.getPosition();
  233. }
  234. }
  235. bool MenuBarComponent::keyPressed (const KeyPress& key)
  236. {
  237. const int numMenus = menuNames.size();
  238. if (numMenus > 0)
  239. {
  240. const int currentIndex = jlimit (0, numMenus - 1, currentPopupIndex);
  241. if (key.isKeyCode (KeyPress::leftKey))
  242. {
  243. showMenu ((currentIndex + numMenus - 1) % numMenus);
  244. return true;
  245. }
  246. if (key.isKeyCode (KeyPress::rightKey))
  247. {
  248. showMenu ((currentIndex + 1) % numMenus);
  249. return true;
  250. }
  251. }
  252. return false;
  253. }
  254. void MenuBarComponent::menuBarItemsChanged (MenuBarModel* /*menuBarModel*/)
  255. {
  256. StringArray newNames;
  257. if (model != nullptr)
  258. newNames = model->getMenuBarNames();
  259. if (newNames != menuNames)
  260. {
  261. menuNames = newNames;
  262. repaint();
  263. resized();
  264. }
  265. }
  266. void MenuBarComponent::menuCommandInvoked (MenuBarModel* /*menuBarModel*/,
  267. const ApplicationCommandTarget::InvocationInfo& info)
  268. {
  269. if (model == nullptr || (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) != 0)
  270. return;
  271. for (int i = 0; i < menuNames.size(); ++i)
  272. {
  273. const PopupMenu menu (model->getMenuForIndex (i, menuNames [i]));
  274. if (menu.containsCommandItem (info.commandID))
  275. {
  276. setItemUnderMouse (i);
  277. startTimer (200);
  278. break;
  279. }
  280. }
  281. }
  282. void MenuBarComponent::timerCallback()
  283. {
  284. stopTimer();
  285. updateItemUnderMouse (getMouseXYRelative());
  286. }