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.

451 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 7 technical preview.
  4. Copyright (c) 2022 - 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 the technical preview this file cannot be licensed commercially.
  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. class MenuBarComponent::AccessibleItemComponent : public Component
  16. {
  17. public:
  18. AccessibleItemComponent (MenuBarComponent& comp, const String& menuItemName)
  19. : owner (comp),
  20. name (menuItemName)
  21. {
  22. setInterceptsMouseClicks (false, false);
  23. }
  24. const String& getName() const noexcept { return name; }
  25. private:
  26. std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
  27. {
  28. class ComponentHandler : public AccessibilityHandler
  29. {
  30. public:
  31. explicit ComponentHandler (AccessibleItemComponent& item)
  32. : AccessibilityHandler (item,
  33. AccessibilityRole::menuItem,
  34. getAccessibilityActions (item)),
  35. itemComponent (item)
  36. {
  37. }
  38. String getTitle() const override { return itemComponent.name; }
  39. private:
  40. static AccessibilityActions getAccessibilityActions (AccessibleItemComponent& item)
  41. {
  42. auto showMenu = [&item] { item.owner.showMenu (item.owner.indexOfItemComponent (&item)); };
  43. return AccessibilityActions().addAction (AccessibilityActionType::focus,
  44. [&item] { item.owner.setItemUnderMouse (item.owner.indexOfItemComponent (&item)); })
  45. .addAction (AccessibilityActionType::press, showMenu)
  46. .addAction (AccessibilityActionType::showMenu, showMenu);
  47. }
  48. AccessibleItemComponent& itemComponent;
  49. };
  50. return std::make_unique<ComponentHandler> (*this);
  51. }
  52. MenuBarComponent& owner;
  53. const String name;
  54. };
  55. MenuBarComponent::MenuBarComponent (MenuBarModel* m)
  56. {
  57. setRepaintsOnMouseActivity (true);
  58. setWantsKeyboardFocus (false);
  59. setMouseClickGrabsKeyboardFocus (false);
  60. setModel (m);
  61. }
  62. MenuBarComponent::~MenuBarComponent()
  63. {
  64. setModel (nullptr);
  65. Desktop::getInstance().removeGlobalMouseListener (this);
  66. }
  67. MenuBarModel* MenuBarComponent::getModel() const noexcept
  68. {
  69. return model;
  70. }
  71. void MenuBarComponent::setModel (MenuBarModel* const newModel)
  72. {
  73. if (model != newModel)
  74. {
  75. if (model != nullptr)
  76. model->removeListener (this);
  77. model = newModel;
  78. if (model != nullptr)
  79. model->addListener (this);
  80. repaint();
  81. menuBarItemsChanged (nullptr);
  82. }
  83. }
  84. //==============================================================================
  85. void MenuBarComponent::paint (Graphics& g)
  86. {
  87. const auto isMouseOverBar = (currentPopupIndex >= 0 || itemUnderMouse >= 0 || isMouseOver());
  88. getLookAndFeel().drawMenuBarBackground (g, getWidth(), getHeight(), isMouseOverBar, *this);
  89. if (model == nullptr)
  90. return;
  91. for (size_t i = 0; i < itemComponents.size(); ++i)
  92. {
  93. const auto& itemComponent = itemComponents[i];
  94. const auto itemBounds = itemComponent->getBounds();
  95. Graphics::ScopedSaveState ss (g);
  96. g.setOrigin (itemBounds.getX(), 0);
  97. g.reduceClipRegion (0, 0, itemBounds.getWidth(), itemBounds.getHeight());
  98. getLookAndFeel().drawMenuBarItem (g,
  99. itemBounds.getWidth(),
  100. itemBounds.getHeight(),
  101. (int) i,
  102. itemComponent->getName(),
  103. (int) i == itemUnderMouse,
  104. (int) i == currentPopupIndex,
  105. isMouseOverBar,
  106. *this);
  107. }
  108. }
  109. void MenuBarComponent::resized()
  110. {
  111. int x = 0;
  112. for (size_t i = 0; i < itemComponents.size(); ++i)
  113. {
  114. auto& itemComponent = itemComponents[i];
  115. auto w = getLookAndFeel().getMenuBarItemWidth (*this, (int) i, itemComponent->getName());
  116. itemComponent->setBounds (x, 0, w, getHeight());
  117. x += w;
  118. }
  119. }
  120. int MenuBarComponent::getItemAt (Point<int> p)
  121. {
  122. for (size_t i = 0; i < itemComponents.size(); ++i)
  123. if (itemComponents[i]->getBounds().contains (p) && reallyContains (p, true))
  124. return (int) i;
  125. return -1;
  126. }
  127. void MenuBarComponent::repaintMenuItem (int index)
  128. {
  129. if (isPositiveAndBelow (index, (int) itemComponents.size()))
  130. {
  131. auto itemBounds = itemComponents[(size_t) index]->getBounds();
  132. repaint (itemBounds.getX() - 2,
  133. 0,
  134. itemBounds.getWidth() + 4,
  135. itemBounds.getHeight());
  136. }
  137. }
  138. void MenuBarComponent::setItemUnderMouse (int index)
  139. {
  140. if (itemUnderMouse == index)
  141. return;
  142. repaintMenuItem (itemUnderMouse);
  143. itemUnderMouse = index;
  144. repaintMenuItem (itemUnderMouse);
  145. if (isPositiveAndBelow (itemUnderMouse, (int) itemComponents.size()))
  146. if (auto* handler = itemComponents[(size_t) itemUnderMouse]->getAccessibilityHandler())
  147. handler->grabFocus();
  148. }
  149. void MenuBarComponent::setOpenItem (int index)
  150. {
  151. if (currentPopupIndex != index)
  152. {
  153. if (currentPopupIndex < 0 && index >= 0)
  154. model->handleMenuBarActivate (true);
  155. else if (currentPopupIndex >= 0 && index < 0)
  156. model->handleMenuBarActivate (false);
  157. repaintMenuItem (currentPopupIndex);
  158. currentPopupIndex = index;
  159. repaintMenuItem (currentPopupIndex);
  160. auto& desktop = Desktop::getInstance();
  161. if (index >= 0)
  162. desktop.addGlobalMouseListener (this);
  163. else
  164. desktop.removeGlobalMouseListener (this);
  165. }
  166. }
  167. void MenuBarComponent::updateItemUnderMouse (Point<int> p)
  168. {
  169. setItemUnderMouse (getItemAt (p));
  170. }
  171. void MenuBarComponent::showMenu (int index)
  172. {
  173. if (index != currentPopupIndex)
  174. {
  175. PopupMenu::dismissAllActiveMenus();
  176. menuBarItemsChanged (nullptr);
  177. setOpenItem (index);
  178. setItemUnderMouse (index);
  179. if (isPositiveAndBelow (index, (int) itemComponents.size()))
  180. {
  181. const auto& itemComponent = itemComponents[(size_t) index];
  182. auto m = model->getMenuForIndex (itemUnderMouse, itemComponent->getName());
  183. if (m.lookAndFeel == nullptr)
  184. m.setLookAndFeel (&getLookAndFeel());
  185. auto itemBounds = itemComponent->getBounds();
  186. const auto callback = [ref = SafePointer<MenuBarComponent> (this), index] (int result)
  187. {
  188. if (ref != nullptr)
  189. ref->menuDismissed (index, result);
  190. };
  191. m.showMenuAsync (PopupMenu::Options().withTargetComponent (this)
  192. .withTargetScreenArea (localAreaToGlobal (itemBounds))
  193. .withMinimumWidth (itemBounds.getWidth()),
  194. callback);
  195. }
  196. }
  197. }
  198. void MenuBarComponent::menuDismissed (int topLevelIndex, int itemId)
  199. {
  200. topLevelIndexClicked = topLevelIndex;
  201. postCommandMessage (itemId);
  202. }
  203. void MenuBarComponent::handleCommandMessage (int commandId)
  204. {
  205. updateItemUnderMouse (getMouseXYRelative());
  206. if (currentPopupIndex == topLevelIndexClicked)
  207. setOpenItem (-1);
  208. if (commandId != 0 && model != nullptr)
  209. model->menuItemSelected (commandId, topLevelIndexClicked);
  210. }
  211. //==============================================================================
  212. void MenuBarComponent::mouseEnter (const MouseEvent& e)
  213. {
  214. if (e.eventComponent == this)
  215. updateItemUnderMouse (e.getPosition());
  216. }
  217. void MenuBarComponent::mouseExit (const MouseEvent& e)
  218. {
  219. if (e.eventComponent == this)
  220. updateItemUnderMouse (e.getPosition());
  221. }
  222. void MenuBarComponent::mouseDown (const MouseEvent& e)
  223. {
  224. if (currentPopupIndex < 0)
  225. {
  226. updateItemUnderMouse (e.getEventRelativeTo (this).getPosition());
  227. currentPopupIndex = -2;
  228. showMenu (itemUnderMouse);
  229. }
  230. }
  231. void MenuBarComponent::mouseDrag (const MouseEvent& e)
  232. {
  233. const auto item = getItemAt (e.getEventRelativeTo (this).getPosition());
  234. if (item >= 0)
  235. showMenu (item);
  236. }
  237. void MenuBarComponent::mouseUp (const MouseEvent& e)
  238. {
  239. const auto e2 = e.getEventRelativeTo (this);
  240. updateItemUnderMouse (e2.getPosition());
  241. if (itemUnderMouse < 0 && getLocalBounds().contains (e2.x, e2.y))
  242. {
  243. setOpenItem (-1);
  244. PopupMenu::dismissAllActiveMenus();
  245. }
  246. }
  247. void MenuBarComponent::mouseMove (const MouseEvent& e)
  248. {
  249. const auto e2 = e.getEventRelativeTo (this);
  250. if (lastMousePos != e2.getPosition())
  251. {
  252. if (currentPopupIndex >= 0)
  253. {
  254. const auto item = getItemAt (e2.getPosition());
  255. if (item >= 0)
  256. showMenu (item);
  257. }
  258. else
  259. {
  260. updateItemUnderMouse (e2.getPosition());
  261. }
  262. lastMousePos = e2.getPosition();
  263. }
  264. }
  265. bool MenuBarComponent::keyPressed (const KeyPress& key)
  266. {
  267. const auto numMenus = (int) itemComponents.size();
  268. if (numMenus > 0)
  269. {
  270. const auto currentIndex = jlimit (0, numMenus - 1, currentPopupIndex);
  271. if (key.isKeyCode (KeyPress::leftKey))
  272. {
  273. showMenu ((currentIndex + numMenus - 1) % numMenus);
  274. return true;
  275. }
  276. if (key.isKeyCode (KeyPress::rightKey))
  277. {
  278. showMenu ((currentIndex + 1) % numMenus);
  279. return true;
  280. }
  281. }
  282. return false;
  283. }
  284. void MenuBarComponent::menuBarItemsChanged (MenuBarModel*)
  285. {
  286. StringArray newNames;
  287. if (model != nullptr)
  288. newNames = model->getMenuBarNames();
  289. auto itemsHaveChanged = [this, &newNames]
  290. {
  291. if ((int) itemComponents.size() != newNames.size())
  292. return true;
  293. for (size_t i = 0; i < itemComponents.size(); ++i)
  294. if (itemComponents[i]->getName() != newNames[(int) i])
  295. return true;
  296. return false;
  297. }();
  298. if (itemsHaveChanged)
  299. {
  300. updateItemComponents (newNames);
  301. repaint();
  302. resized();
  303. }
  304. }
  305. void MenuBarComponent::updateItemComponents (const StringArray& menuNames)
  306. {
  307. itemComponents.clear();
  308. for (const auto& name : menuNames)
  309. {
  310. itemComponents.push_back (std::make_unique<AccessibleItemComponent> (*this, name));
  311. addAndMakeVisible (*itemComponents.back());
  312. }
  313. }
  314. int MenuBarComponent::indexOfItemComponent (AccessibleItemComponent* itemComponent) const
  315. {
  316. const auto iter = std::find_if (itemComponents.cbegin(), itemComponents.cend(),
  317. [itemComponent] (const std::unique_ptr<AccessibleItemComponent>& c) { return c.get() == itemComponent; });
  318. if (iter != itemComponents.cend())
  319. return (int) std::distance (itemComponents.cbegin(), iter);
  320. jassertfalse;
  321. return -1;
  322. }
  323. void MenuBarComponent::menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info)
  324. {
  325. if (model == nullptr || (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) != 0)
  326. return;
  327. for (size_t i = 0; i < itemComponents.size(); ++i)
  328. {
  329. const auto menu = model->getMenuForIndex ((int) i, itemComponents[i]->getName());
  330. if (menu.containsCommandItem (info.commandID))
  331. {
  332. setItemUnderMouse ((int) i);
  333. startTimer (200);
  334. break;
  335. }
  336. }
  337. }
  338. void MenuBarComponent::timerCallback()
  339. {
  340. stopTimer();
  341. updateItemUnderMouse (getMouseXYRelative());
  342. }
  343. //==============================================================================
  344. std::unique_ptr<AccessibilityHandler> MenuBarComponent::createAccessibilityHandler()
  345. {
  346. struct MenuBarComponentAccessibilityHandler : public AccessibilityHandler
  347. {
  348. explicit MenuBarComponentAccessibilityHandler (MenuBarComponent& menuBarComponent)
  349. : AccessibilityHandler (menuBarComponent, AccessibilityRole::menuBar)
  350. {
  351. }
  352. AccessibleState getCurrentState() const override { return AccessibleState().withIgnored(); }
  353. };
  354. return std::make_unique<MenuBarComponentAccessibilityHandler> (*this);
  355. }
  356. } // namespace juce