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.

503 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 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. #ifndef __JUCE_POPUPMENU_JUCEHEADER__
  19. #define __JUCE_POPUPMENU_JUCEHEADER__
  20. //==============================================================================
  21. /** Creates and displays a popup-menu.
  22. To show a popup-menu, you create one of these, add some items to it, then
  23. call its show() method, which returns the id of the item the user selects.
  24. E.g. @code
  25. void MyWidget::mouseDown (const MouseEvent& e)
  26. {
  27. PopupMenu m;
  28. m.addItem (1, "item 1");
  29. m.addItem (2, "item 2");
  30. const int result = m.show();
  31. if (result == 0)
  32. {
  33. // user dismissed the menu without picking anything
  34. }
  35. else if (result == 1)
  36. {
  37. // user picked item 1
  38. }
  39. else if (result == 2)
  40. {
  41. // user picked item 2
  42. }
  43. }
  44. @endcode
  45. Submenus are easy too: @code
  46. void MyWidget::mouseDown (const MouseEvent& e)
  47. {
  48. PopupMenu subMenu;
  49. subMenu.addItem (1, "item 1");
  50. subMenu.addItem (2, "item 2");
  51. PopupMenu mainMenu;
  52. mainMenu.addItem (3, "item 3");
  53. mainMenu.addSubMenu ("other choices", subMenu);
  54. const int result = m.show();
  55. ...etc
  56. }
  57. @endcode
  58. */
  59. class JUCE_API PopupMenu
  60. {
  61. private:
  62. class Window;
  63. public:
  64. //==============================================================================
  65. /** Creates an empty popup menu. */
  66. PopupMenu();
  67. /** Creates a copy of another menu. */
  68. PopupMenu (const PopupMenu& other);
  69. /** Destructor. */
  70. ~PopupMenu();
  71. /** Copies this menu from another one. */
  72. PopupMenu& operator= (const PopupMenu& other);
  73. #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
  74. PopupMenu (PopupMenu&& other) noexcept;
  75. PopupMenu& operator= (PopupMenu&& other) noexcept;
  76. #endif
  77. //==============================================================================
  78. /** Resets the menu, removing all its items. */
  79. void clear();
  80. /** Appends a new text item for this menu to show.
  81. @param itemResultId the number that will be returned from the show() method
  82. if the user picks this item. The value should never be
  83. zero, because that's used to indicate that the user didn't
  84. select anything.
  85. @param itemText the text to show.
  86. @param isEnabled if false, the item will be shown 'greyed-out' and can't be picked
  87. @param isTicked if true, the item will be shown with a tick next to it
  88. @param iconToUse if this is non-zero, it should be an image that will be
  89. displayed to the left of the item. This method will take its
  90. own copy of the image passed-in, so there's no need to keep
  91. it hanging around.
  92. @see addSeparator, addColouredItem, addCustomItem, addSubMenu
  93. */
  94. void addItem (int itemResultId,
  95. const String& itemText,
  96. bool isEnabled = true,
  97. bool isTicked = false,
  98. const Image& iconToUse = Image::null);
  99. /** Adds an item that represents one of the commands in a command manager object.
  100. @param commandManager the manager to use to trigger the command and get information
  101. about it
  102. @param commandID the ID of the command
  103. @param displayName if this is non-empty, then this string will be used instead of
  104. the command's registered name
  105. */
  106. void addCommandItem (ApplicationCommandManager* commandManager,
  107. int commandID,
  108. const String& displayName = String::empty);
  109. /** Appends a text item with a special colour.
  110. This is the same as addItem(), but specifies a colour to use for the
  111. text, which will override the default colours that are used by the
  112. current look-and-feel. See addItem() for a description of the parameters.
  113. */
  114. void addColouredItem (int itemResultId,
  115. const String& itemText,
  116. const Colour& itemTextColour,
  117. bool isEnabled = true,
  118. bool isTicked = false,
  119. const Image& iconToUse = Image::null);
  120. /** Appends a custom menu item that can't be used to trigger a result.
  121. This will add a user-defined component to use as a menu item. Unlike the
  122. addCustomItem() method that takes a PopupMenu::CustomComponent, this version
  123. can't trigger a result from it, so doesn't take a menu ID. It also doesn't
  124. delete the component when it's finished, so it's the caller's responsibility
  125. to manage the component that is passed-in.
  126. if triggerMenuItemAutomaticallyWhenClicked is true, the menu itself will handle
  127. detection of a mouse-click on your component, and use that to trigger the
  128. menu ID specified in itemResultId. If this is false, the menu item can't
  129. be triggered, so itemResultId is not used.
  130. @see CustomComponent
  131. */
  132. void addCustomItem (int itemResultId,
  133. Component* customComponent,
  134. int idealWidth, int idealHeight,
  135. bool triggerMenuItemAutomaticallyWhenClicked);
  136. /** Appends a sub-menu.
  137. If the menu that's passed in is empty, it will appear as an inactive item.
  138. */
  139. void addSubMenu (const String& subMenuName,
  140. const PopupMenu& subMenu,
  141. bool isEnabled = true,
  142. const Image& iconToUse = Image::null,
  143. bool isTicked = false);
  144. /** Appends a separator to the menu, to help break it up into sections.
  145. The menu class is smart enough not to display separators at the top or bottom
  146. of the menu, and it will replace mutliple adjacent separators with a single
  147. one, so your code can be quite free and easy about adding these, and it'll
  148. always look ok.
  149. */
  150. void addSeparator();
  151. /** Adds a non-clickable text item to the menu.
  152. This is a bold-font items which can be used as a header to separate the items
  153. into named groups.
  154. */
  155. void addSectionHeader (const String& title);
  156. /** Returns the number of items that the menu currently contains.
  157. (This doesn't count separators).
  158. */
  159. int getNumItems() const noexcept;
  160. /** Returns true if the menu contains a command item that triggers the given command. */
  161. bool containsCommandItem (int commandID) const;
  162. /** Returns true if the menu contains any items that can be used. */
  163. bool containsAnyActiveItems() const noexcept;
  164. //==============================================================================
  165. /** Class used to create a set of options to pass to the show() method.
  166. You can chain together a series of calls to this class's methods to create
  167. a set of whatever options you want to specify.
  168. E.g. @code
  169. PopupMenu menu;
  170. ...
  171. menu.showMenu (PopupMenu::Options().withMinimumWidth (100)
  172. .withMaximumNumColumns (3)
  173. .withTargetComponent (myComp));
  174. @endcode
  175. */
  176. class JUCE_API Options
  177. {
  178. public:
  179. Options();
  180. Options withTargetComponent (Component* targetComponent) const noexcept;
  181. Options withTargetScreenArea (const Rectangle<int>& targetArea) const noexcept;
  182. Options withMinimumWidth (int minWidth) const noexcept;
  183. Options withMaximumNumColumns (int maxNumColumns) const noexcept;
  184. Options withStandardItemHeight (int standardHeight) const noexcept;
  185. Options withItemThatMustBeVisible (int idOfItemToBeVisible) const noexcept;
  186. private:
  187. friend class PopupMenu;
  188. friend class PopupMenu::Window;
  189. Rectangle<int> targetArea;
  190. Component* targetComponent;
  191. int visibleItemID, minWidth, maxColumns, standardHeight;
  192. };
  193. //==============================================================================
  194. #if JUCE_MODAL_LOOPS_PERMITTED
  195. /** Displays the menu and waits for the user to pick something.
  196. This will display the menu modally, and return the ID of the item that the
  197. user picks. If they click somewhere off the menu to get rid of it without
  198. choosing anything, this will return 0.
  199. The current location of the mouse will be used as the position to show the
  200. menu - to explicitly set the menu's position, use showAt() instead. Depending
  201. on where this point is on the screen, the menu will appear above, below or
  202. to the side of the point.
  203. @param itemIdThatMustBeVisible if you set this to the ID of one of the menu items,
  204. then when the menu first appears, it will make sure
  205. that this item is visible. So if the menu has too many
  206. items to fit on the screen, it will be scrolled to a
  207. position where this item is visible.
  208. @param minimumWidth a minimum width for the menu, in pixels. It may be wider
  209. than this if some items are too long to fit.
  210. @param maximumNumColumns if there are too many items to fit on-screen in a single
  211. vertical column, the menu may be laid out as a series of
  212. columns - this is the maximum number allowed. To use the
  213. default value for this (probably about 7), you can pass
  214. in zero.
  215. @param standardItemHeight if this is non-zero, it will be used as the standard
  216. height for menu items (apart from custom items)
  217. @param callback if this is non-zero, the menu will be launched asynchronously,
  218. returning immediately, and the callback will receive a
  219. call when the menu is either dismissed or has an item
  220. selected. This object will be owned and deleted by the
  221. system, so make sure that it works safely and that any
  222. pointers that it uses are safely within scope.
  223. @see showAt
  224. */
  225. int show (int itemIdThatMustBeVisible = 0,
  226. int minimumWidth = 0,
  227. int maximumNumColumns = 0,
  228. int standardItemHeight = 0,
  229. ModalComponentManager::Callback* callback = nullptr);
  230. /** Displays the menu at a specific location.
  231. This is the same as show(), but uses a specific location (in global screen
  232. co-ordinates) rather than the current mouse position.
  233. The screenAreaToAttachTo parameter indicates a screen area to which the menu
  234. will be adjacent. Depending on where this is, the menu will decide which edge to
  235. attach itself to, in order to fit itself fully on-screen. If you just want to
  236. trigger a menu at a specific point, you can pass in a rectangle of size (0, 0)
  237. with the position that you want.
  238. @see show()
  239. */
  240. int showAt (const Rectangle<int>& screenAreaToAttachTo,
  241. int itemIdThatMustBeVisible = 0,
  242. int minimumWidth = 0,
  243. int maximumNumColumns = 0,
  244. int standardItemHeight = 0,
  245. ModalComponentManager::Callback* callback = nullptr);
  246. /** Displays the menu as if it's attached to a component such as a button.
  247. This is similar to showAt(), but will position it next to the given component, e.g.
  248. so that the menu's edge is aligned with that of the component. This is intended for
  249. things like buttons that trigger a pop-up menu.
  250. */
  251. int showAt (Component* componentToAttachTo,
  252. int itemIdThatMustBeVisible = 0,
  253. int minimumWidth = 0,
  254. int maximumNumColumns = 0,
  255. int standardItemHeight = 0,
  256. ModalComponentManager::Callback* callback = nullptr);
  257. /** Displays and runs the menu modally, with a set of options.
  258. */
  259. int showMenu (const Options& options);
  260. #endif
  261. /** Runs the menu asynchronously, with a user-provided callback that will receive the result. */
  262. void showMenuAsync (const Options& options,
  263. ModalComponentManager::Callback* callback);
  264. //==============================================================================
  265. /** Closes any menus that are currently open.
  266. This might be useful if you have a situation where your window is being closed
  267. by some means other than a user action, and you'd like to make sure that menus
  268. aren't left hanging around.
  269. */
  270. static bool JUCE_CALLTYPE dismissAllActiveMenus();
  271. //==============================================================================
  272. /** Specifies a look-and-feel for the menu and any sub-menus that it has.
  273. This can be called before show() if you need a customised menu. Be careful
  274. not to delete the LookAndFeel object before the menu has been deleted.
  275. */
  276. void setLookAndFeel (LookAndFeel* newLookAndFeel);
  277. //==============================================================================
  278. /** A set of colour IDs to use to change the colour of various aspects of the menu.
  279. These constants can be used either via the LookAndFeel::setColour()
  280. method for the look and feel that is set for this menu with setLookAndFeel()
  281. @see setLookAndFeel, LookAndFeel::setColour, LookAndFeel::findColour
  282. */
  283. enum ColourIds
  284. {
  285. backgroundColourId = 0x1000700, /**< The colour to fill the menu's background with. */
  286. textColourId = 0x1000600, /**< The colour for normal menu item text, (unless the
  287. colour is specified when the item is added). */
  288. headerTextColourId = 0x1000601, /**< The colour for section header item text (see the
  289. addSectionHeader() method). */
  290. highlightedBackgroundColourId = 0x1000900, /**< The colour to fill the background of the currently
  291. highlighted menu item. */
  292. highlightedTextColourId = 0x1000800, /**< The colour to use for the text of the currently
  293. highlighted item. */
  294. };
  295. //==============================================================================
  296. /**
  297. Allows you to iterate through the items in a pop-up menu, and examine
  298. their properties.
  299. To use this, just create one and repeatedly call its next() method. When this
  300. returns true, all the member variables of the iterator are filled-out with
  301. information describing the menu item. When it returns false, the end of the
  302. list has been reached.
  303. */
  304. class JUCE_API MenuItemIterator
  305. {
  306. public:
  307. //==============================================================================
  308. /** Creates an iterator that will scan through the items in the specified
  309. menu.
  310. Be careful not to add any items to a menu while it is being iterated,
  311. or things could get out of step.
  312. */
  313. MenuItemIterator (const PopupMenu& menu);
  314. /** Destructor. */
  315. ~MenuItemIterator();
  316. /** Returns true if there is another item, and sets up all this object's
  317. member variables to reflect that item's properties.
  318. */
  319. bool next();
  320. //==============================================================================
  321. String itemName;
  322. const PopupMenu* subMenu;
  323. int itemId;
  324. bool isSeparator;
  325. bool isTicked;
  326. bool isEnabled;
  327. bool isCustomComponent;
  328. bool isSectionHeader;
  329. const Colour* customColour;
  330. Image customImage;
  331. ApplicationCommandManager* commandManager;
  332. private:
  333. //==============================================================================
  334. const PopupMenu& menu;
  335. int index;
  336. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MenuItemIterator);
  337. };
  338. //==============================================================================
  339. /** A user-defined copmonent that can be used as an item in a popup menu.
  340. @see PopupMenu::addCustomItem
  341. */
  342. class JUCE_API CustomComponent : public Component,
  343. public SingleThreadedReferenceCountedObject
  344. {
  345. public:
  346. /** Creates a custom item.
  347. If isTriggeredAutomatically is true, then the menu will automatically detect
  348. a mouse-click on this component and use that to invoke the menu item. If it's
  349. false, then it's up to your class to manually trigger the item when it wants to.
  350. */
  351. CustomComponent (bool isTriggeredAutomatically = true);
  352. /** Destructor. */
  353. ~CustomComponent();
  354. /** Returns a rectangle with the size that this component would like to have.
  355. Note that the size which this method returns isn't necessarily the one that
  356. the menu will give it, as the items will be stretched to have a uniform width.
  357. */
  358. virtual void getIdealSize (int& idealWidth, int& idealHeight) = 0;
  359. /** Dismisses the menu, indicating that this item has been chosen.
  360. This will cause the menu to exit from its modal state, returning
  361. this item's id as the result.
  362. */
  363. void triggerMenuItem();
  364. /** Returns true if this item should be highlighted because the mouse is over it.
  365. You can call this method in your paint() method to find out whether
  366. to draw a highlight.
  367. */
  368. bool isItemHighlighted() const noexcept { return isHighlighted; }
  369. /** @internal */
  370. bool isTriggeredAutomatically() const noexcept { return triggeredAutomatically; }
  371. /** @internal */
  372. void setHighlighted (bool shouldBeHighlighted);
  373. private:
  374. //==============================================================================
  375. bool isHighlighted, triggeredAutomatically;
  376. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomComponent);
  377. };
  378. /** Appends a custom menu item.
  379. This will add a user-defined component to use as a menu item. The component
  380. passed in will be deleted by this menu when it's no longer needed.
  381. @see CustomComponent
  382. */
  383. void addCustomItem (int itemResultId, CustomComponent* customComponent);
  384. private:
  385. //==============================================================================
  386. class Item;
  387. class ItemComponent;
  388. class HeaderItemComponent;
  389. class NormalComponentWrapper;
  390. friend class MenuItemIterator;
  391. friend class ItemComponent;
  392. friend class Window;
  393. friend class CustomComponent;
  394. friend class MenuBarComponent;
  395. friend class OwnedArray <Item>;
  396. friend class OwnedArray <ItemComponent>;
  397. friend class ScopedPointer <Window>;
  398. OwnedArray <Item> items;
  399. LookAndFeel* lookAndFeel;
  400. Component* createWindow (const Options&, ApplicationCommandManager**) const;
  401. int showWithOptionalCallback (const Options&, ModalComponentManager::Callback*, bool);
  402. JUCE_LEAK_DETECTOR (PopupMenu);
  403. };
  404. #endif // __JUCE_POPUPMENU_JUCEHEADER__