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.

352 lines
9.6KB

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