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.

295 lines
8.3KB

  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. namespace juce
  20. {
  21. //==============================================================================
  22. struct CustomMenuBarItemHolder : public Component
  23. {
  24. CustomMenuBarItemHolder (const ReferenceCountedObjectPtr<PopupMenu::CustomComponent>& customComponent)
  25. {
  26. setInterceptsMouseClicks (false, true);
  27. update (customComponent);
  28. }
  29. void update (const ReferenceCountedObjectPtr<PopupMenu::CustomComponent>& newComponent)
  30. {
  31. jassert (newComponent != nullptr);
  32. if (newComponent != custom)
  33. {
  34. if (custom != nullptr)
  35. removeChildComponent (custom);
  36. custom = newComponent;
  37. addAndMakeVisible (*custom);
  38. resized();
  39. }
  40. }
  41. void resized() override
  42. {
  43. custom->setBounds (getLocalBounds());
  44. }
  45. ReferenceCountedObjectPtr<PopupMenu::CustomComponent> custom;
  46. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomMenuBarItemHolder)
  47. };
  48. //==============================================================================
  49. BurgerMenuComponent::BurgerMenuComponent (MenuBarModel* modelToUse)
  50. {
  51. auto& lf = getLookAndFeel();
  52. listBox.setRowHeight (roundToInt (lf.getPopupMenuFont().getHeight() * 2.0f));
  53. listBox.addMouseListener (this, true);
  54. setModel (modelToUse);
  55. addAndMakeVisible (listBox);
  56. }
  57. BurgerMenuComponent::~BurgerMenuComponent()
  58. {
  59. if (model != nullptr)
  60. model->removeListener (this);
  61. }
  62. void BurgerMenuComponent::setModel (MenuBarModel* newModel)
  63. {
  64. if (newModel != model)
  65. {
  66. if (model != nullptr)
  67. model->removeListener (this);
  68. model = newModel;
  69. if (model != nullptr)
  70. model->addListener (this);
  71. refresh();
  72. listBox.updateContent();
  73. }
  74. }
  75. MenuBarModel* BurgerMenuComponent::getModel() const noexcept
  76. {
  77. return model;
  78. }
  79. void BurgerMenuComponent::refresh()
  80. {
  81. lastRowClicked = inputSourceIndexOfLastClick = -1;
  82. rows.clear();
  83. if (model != nullptr)
  84. {
  85. auto menuBarNames = model->getMenuBarNames();
  86. for (auto menuIdx = 0; menuIdx < menuBarNames.size(); ++menuIdx)
  87. {
  88. PopupMenu::Item menuItem;
  89. menuItem.text = menuBarNames[menuIdx];
  90. String ignore;
  91. auto menu = model->getMenuForIndex (menuIdx, ignore);
  92. rows.add (Row { true, menuIdx, menuItem });
  93. addMenuBarItemsForMenu (menu, menuIdx);
  94. }
  95. }
  96. }
  97. void BurgerMenuComponent::addMenuBarItemsForMenu (PopupMenu& menu, int menuIdx)
  98. {
  99. for (PopupMenu::MenuItemIterator it (menu); it.next();)
  100. {
  101. auto& item = it.getItem();
  102. if (item.isSeparator)
  103. continue;
  104. if (hasSubMenu (item))
  105. addMenuBarItemsForMenu (*item.subMenu, menuIdx);
  106. else
  107. rows.add (Row {false, menuIdx, it.getItem()});
  108. }
  109. }
  110. int BurgerMenuComponent::getNumRows()
  111. {
  112. return rows.size();
  113. }
  114. void BurgerMenuComponent::paint (Graphics& g)
  115. {
  116. getLookAndFeel().drawPopupMenuBackground (g, getWidth(), getHeight());
  117. }
  118. void BurgerMenuComponent::paintListBoxItem (int rowIndex, Graphics& g, int w, int h, bool highlight)
  119. {
  120. auto& lf = getLookAndFeel();
  121. Rectangle<int> r (w, h);
  122. auto row = (rowIndex < rows.size() ? rows.getReference (rowIndex)
  123. : Row { true, 0, {} });
  124. g.fillAll (findColour (PopupMenu::backgroundColourId));
  125. if (row.isMenuHeader)
  126. {
  127. lf.drawPopupMenuSectionHeader (g, r.reduced (20, 0), row.item.text);
  128. g.setColour (Colours::grey);
  129. g.fillRect (r.withHeight (1));
  130. }
  131. else
  132. {
  133. auto& item = row.item;
  134. auto* colour = item.colour != Colour() ? &item.colour : nullptr;
  135. if (item.customComponent == nullptr)
  136. lf.drawPopupMenuItem (g, r.reduced (20, 0),
  137. item.isSeparator,
  138. item.isEnabled,
  139. highlight,
  140. item.isTicked,
  141. hasSubMenu (item),
  142. item.text,
  143. item.shortcutKeyDescription,
  144. item.image.get(),
  145. colour);
  146. }
  147. }
  148. bool BurgerMenuComponent::hasSubMenu (const PopupMenu::Item& item)
  149. {
  150. return item.subMenu != nullptr && (item.itemID == 0 || item.subMenu->getNumItems() > 0);
  151. }
  152. void BurgerMenuComponent::listBoxItemClicked (int rowIndex, const MouseEvent& e)
  153. {
  154. auto row = rowIndex < rows.size() ? rows.getReference (rowIndex)
  155. : Row { true, 0, {} };
  156. if (! row.isMenuHeader)
  157. {
  158. lastRowClicked = rowIndex;
  159. inputSourceIndexOfLastClick = e.source.getIndex();
  160. }
  161. }
  162. Component* BurgerMenuComponent::refreshComponentForRow (int rowIndex, bool isRowSelected, Component* existing)
  163. {
  164. auto row = rowIndex < rows.size() ? rows.getReference (rowIndex)
  165. : Row { true, 0, {} };
  166. auto hasCustomComponent = (row.item.customComponent != nullptr);
  167. if (existing == nullptr && hasCustomComponent)
  168. return new CustomMenuBarItemHolder (row.item.customComponent);
  169. if (existing != nullptr)
  170. {
  171. auto* componentToUpdate = dynamic_cast<CustomMenuBarItemHolder*> (existing);
  172. jassert (componentToUpdate != nullptr);
  173. if (hasCustomComponent && componentToUpdate != nullptr)
  174. {
  175. row.item.customComponent->setHighlighted (isRowSelected);
  176. componentToUpdate->update (row.item.customComponent);
  177. }
  178. else
  179. {
  180. delete existing;
  181. existing = nullptr;
  182. }
  183. }
  184. return existing;
  185. }
  186. void BurgerMenuComponent::resized()
  187. {
  188. listBox.setBounds (getLocalBounds());
  189. }
  190. void BurgerMenuComponent::menuBarItemsChanged (MenuBarModel* menuBarModel)
  191. {
  192. setModel (menuBarModel);
  193. }
  194. void BurgerMenuComponent::menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo&)
  195. {
  196. }
  197. void BurgerMenuComponent::mouseUp (const MouseEvent& event)
  198. {
  199. auto rowIndex = listBox.getSelectedRow();
  200. if (rowIndex == lastRowClicked && rowIndex < rows.size()
  201. && event.source.getIndex() == inputSourceIndexOfLastClick)
  202. {
  203. auto& row = rows.getReference (rowIndex);
  204. if (! row.isMenuHeader)
  205. {
  206. listBox.selectRow (-1);
  207. lastRowClicked = -1;
  208. inputSourceIndexOfLastClick = -1;
  209. topLevelIndexClicked = row.topLevelMenuIndex;
  210. auto& item = row.item;
  211. if (auto* managerOfChosenCommand = item.commandManager)
  212. {
  213. ApplicationCommandTarget::InvocationInfo info (item.itemID);
  214. info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;
  215. managerOfChosenCommand->invoke (info, true);
  216. }
  217. postCommandMessage (item.itemID);
  218. }
  219. }
  220. }
  221. void BurgerMenuComponent::handleCommandMessage (int commandID)
  222. {
  223. if (model != nullptr)
  224. {
  225. model->menuItemSelected (commandID, topLevelIndexClicked);
  226. topLevelIndexClicked = -1;
  227. refresh();
  228. listBox.updateContent();
  229. }
  230. }
  231. } // namespace juce