1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
29 //==============================================================================
30 /** Creates and displays a popup-menu.
31 
32     To show a popup-menu, you create one of these, add some items to it, then
33     call its show() method, which returns the id of the item the user selects.
34 
35     E.g. @code
36     void MyWidget::mouseDown (const MouseEvent& e)
37     {
38         PopupMenu m;
39         m.addItem (1, "item 1");
40         m.addItem (2, "item 2");
41 
42         const int result = m.show();
43 
44         if (result == 0)
45         {
46             // user dismissed the menu without picking anything
47         }
48         else if (result == 1)
49         {
50             // user picked item 1
51         }
52         else if (result == 2)
53         {
54             // user picked item 2
55         }
56     }
57     @endcode
58 
59     Submenus are easy too: @code
60 
61     void MyWidget::mouseDown (const MouseEvent& e)
62     {
63         PopupMenu subMenu;
64         subMenu.addItem (1, "item 1");
65         subMenu.addItem (2, "item 2");
66 
67         PopupMenu mainMenu;
68         mainMenu.addItem (3, "item 3");
69         mainMenu.addSubMenu ("other choices", subMenu);
70 
71         const int result = m.show();
72 
73         ...etc
74     }
75     @endcode
76 
77     @tags{GUI}
78 */
79 class JUCE_API  PopupMenu
80 {
81 public:
82     //==============================================================================
83     /** Creates an empty popup menu. */
84     PopupMenu() = default;
85 
86     /** Creates a copy of another menu. */
87     PopupMenu (const PopupMenu&);
88 
89     /** Destructor. */
90     ~PopupMenu();
91 
92     /** Copies this menu from another one. */
93     PopupMenu& operator= (const PopupMenu&);
94 
95     /** Move constructor */
96     PopupMenu (PopupMenu&&) noexcept;
97 
98     /** Move assignment operator */
99     PopupMenu& operator= (PopupMenu&&) noexcept;
100 
101     //==============================================================================
102     class CustomComponent;
103     class CustomCallback;
104 
105     //==============================================================================
106     /** Resets the menu, removing all its items. */
107     void clear();
108 
109     /** Describes a popup menu item. */
110     struct JUCE_API  Item
111     {
112         /** Creates a null item.
113             You'll need to set some fields after creating an Item before you
114             can add it to a PopupMenu
115         */
116         Item();
117 
118         /** Creates an item with the given text.
119             This constructor also initialises the itemID to -1, which makes it suitable for
120             creating lambda-based item actions.
121         */
122         Item (String text);
123 
124         Item (const Item&);
125         Item& operator= (const Item&);
126         Item (Item&&);
127         Item& operator= (Item&&);
128 
129         /** The menu item's name. */
130         String text;
131 
132         /** The menu item's ID.
133             This must not be 0 if you want the item to be triggerable, but if you're attaching
134             an action callback to the item, you can set the itemID to -1 to indicate that it
135             isn't actively needed.
136         */
137         int itemID = 0;
138 
139         /** An optional function which should be invoked when this menu item is triggered. */
140         std::function<void()> action;
141 
142         /** A sub-menu, or nullptr if there isn't one. */
143         std::unique_ptr<PopupMenu> subMenu;
144 
145         /** A drawable to use as an icon, or nullptr if there isn't one. */
146         std::unique_ptr<Drawable> image;
147 
148         /** A custom component for the item to display, or nullptr if there isn't one. */
149         ReferenceCountedObjectPtr<CustomComponent> customComponent;
150 
151         /** A custom callback for the item to use, or nullptr if there isn't one. */
152         ReferenceCountedObjectPtr<CustomCallback> customCallback;
153 
154         /** A command manager to use to automatically invoke the command, or nullptr if none is specified. */
155         ApplicationCommandManager* commandManager = nullptr;
156 
157         /** An optional string describing the shortcut key for this item.
158             This is only used for displaying at the right-hand edge of a menu item - the
159             menu won't attempt to actually catch or process the key. If you supply a
160             commandManager parameter then the menu will attempt to fill-in this field
161             automatically.
162         */
163         String shortcutKeyDescription;
164 
165         /** A colour to use to draw the menu text.
166             By default this is transparent black, which means that the LookAndFeel should choose the colour.
167         */
168         Colour colour;
169 
170         /** True if this menu item is enabled. */
171         bool isEnabled = true;
172 
173         /** True if this menu item should have a tick mark next to it. */
174         bool isTicked = false;
175 
176         /** True if this menu item is a separator line. */
177         bool isSeparator = false;
178 
179         /** True if this menu item is a section header. */
180         bool isSectionHeader = false;
181 
182         /** True if this is the final item in the current column. */
183         bool shouldBreakAfter = false;
184 
185         /** Sets the isTicked flag (and returns a reference to this item to allow chaining). */
186         Item& setTicked (bool shouldBeTicked = true) & noexcept;
187         /** Sets the isEnabled flag (and returns a reference to this item to allow chaining). */
188         Item& setEnabled (bool shouldBeEnabled) & noexcept;
189         /** Sets the action property (and returns a reference to this item to allow chaining). */
190         Item& setAction (std::function<void()> action) & noexcept;
191         /** Sets the itemID property (and returns a reference to this item to allow chaining). */
192         Item& setID (int newID) & noexcept;
193         /** Sets the colour property (and returns a reference to this item to allow chaining). */
194         Item& setColour (Colour) & noexcept;
195         /** Sets the customComponent property (and returns a reference to this item to allow chaining). */
196         Item& setCustomComponent (ReferenceCountedObjectPtr<CustomComponent> customComponent) & noexcept;
197         /** Sets the image property (and returns a reference to this item to allow chaining). */
198         Item& setImage (std::unique_ptr<Drawable>) & noexcept;
199 
200         /** Sets the isTicked flag (and returns a reference to this item to allow chaining). */
201         Item&& setTicked (bool shouldBeTicked = true) && noexcept;
202         /** Sets the isEnabled flag (and returns a reference to this item to allow chaining). */
203         Item&& setEnabled (bool shouldBeEnabled) && noexcept;
204         /** Sets the action property (and returns a reference to this item to allow chaining). */
205         Item&& setAction (std::function<void()> action) && noexcept;
206         /** Sets the itemID property (and returns a reference to this item to allow chaining). */
207         Item&& setID (int newID) && noexcept;
208         /** Sets the colour property (and returns a reference to this item to allow chaining). */
209         Item&& setColour (Colour) && noexcept;
210         /** Sets the customComponent property (and returns a reference to this item to allow chaining). */
211         Item&& setCustomComponent (ReferenceCountedObjectPtr<CustomComponent> customComponent) && noexcept;
212         /** Sets the image property (and returns a reference to this item to allow chaining). */
213         Item&& setImage (std::unique_ptr<Drawable>) && noexcept;
214     };
215 
216     /** Adds an item to the menu.
217         You can call this method for full control over the item that is added, or use the other
218         addItem helper methods if you want to pass arguments rather than creating an Item object.
219     */
220     void addItem (Item newItem);
221 
222     /** Adds an item to the menu with an action callback. */
223     void addItem (String itemText,
224                   std::function<void()> action);
225 
226     /** Adds an item to the menu with an action callback. */
227     void addItem (String itemText,
228                   bool isEnabled,
229                   bool isTicked,
230                   std::function<void()> action);
231 
232     /** Appends a new text item for this menu to show.
233 
234         @param itemResultID     the number that will be returned from the show() method
235                                 if the user picks this item. The value should never be
236                                 zero, because that's used to indicate that the user didn't
237                                 select anything.
238         @param itemText         the text to show.
239         @param isEnabled        if false, the item will be shown 'greyed-out' and can't be picked
240         @param isTicked         if true, the item will be shown with a tick next to it
241 
242         @see addSeparator, addColouredItem, addCustomItem, addSubMenu
243     */
244     void addItem (int itemResultID,
245                   String itemText,
246                   bool isEnabled = true,
247                   bool isTicked = false);
248 
249     /** Appends a new item with an icon.
250 
251         @param itemResultID     the number that will be returned from the show() method
252                                 if the user picks this item. The value should never be
253                                 zero, because that's used to indicate that the user didn't
254                                 select anything.
255         @param itemText         the text to show.
256         @param isEnabled        if false, the item will be shown 'greyed-out' and can't be picked
257         @param isTicked         if true, the item will be shown with a tick next to it
258         @param iconToUse        if this is a valid image, it will be displayed to the left of the item.
259 
260         @see addSeparator, addColouredItem, addCustomItem, addSubMenu
261     */
262     void addItem (int itemResultID,
263                   String itemText,
264                   bool isEnabled,
265                   bool isTicked,
266                   const Image& iconToUse);
267 
268     /** Appends a new item with an icon.
269 
270         @param itemResultID     the number that will be returned from the show() method
271                                 if the user picks this item. The value should never be
272                                 zero, because that's used to indicate that the user didn't
273                                 select anything.
274         @param itemText         the text to show.
275         @param isEnabled        if false, the item will be shown 'greyed-out' and can't be picked
276         @param isTicked         if true, the item will be shown with a tick next to it
277         @param iconToUse        a Drawable object to use as the icon to the left of the item.
278                                 The menu will take ownership of this drawable object and will
279                                 delete it later when no longer needed
280         @see addSeparator, addColouredItem, addCustomItem, addSubMenu
281     */
282     void addItem (int itemResultID,
283                   String itemText,
284                   bool isEnabled,
285                   bool isTicked,
286                   std::unique_ptr<Drawable> iconToUse);
287 
288     /** Adds an item that represents one of the commands in a command manager object.
289 
290         @param commandManager       the manager to use to trigger the command and get information
291                                     about it
292         @param commandID            the ID of the command
293         @param displayName          if this is non-empty, then this string will be used instead of
294                                     the command's registered name
295         @param iconToUse            an optional Drawable object to use as the icon to the left of the item.
296                                     The menu will take ownership of this drawable object and will
297                                     delete it later when no longer needed
298     */
299     void addCommandItem (ApplicationCommandManager* commandManager,
300                          CommandID commandID,
301                          String displayName = {},
302                          std::unique_ptr<Drawable> iconToUse = {});
303 
304     /** Appends a text item with a special colour.
305 
306         This is the same as addItem(), but specifies a colour to use for the
307         text, which will override the default colours that are used by the
308         current look-and-feel. See addItem() for a description of the parameters.
309     */
310     void addColouredItem (int itemResultID,
311                           String itemText,
312                           Colour itemTextColour,
313                           bool isEnabled = true,
314                           bool isTicked = false,
315                           const Image& iconToUse = {});
316 
317     /** Appends a text item with a special colour.
318 
319         This is the same as addItem(), but specifies a colour to use for the
320         text, which will override the default colours that are used by the
321         current look-and-feel. See addItem() for a description of the parameters.
322     */
323     void addColouredItem (int itemResultID,
324                           String itemText,
325                           Colour itemTextColour,
326                           bool isEnabled,
327                           bool isTicked,
328                           std::unique_ptr<Drawable> iconToUse);
329 
330     /** Appends a custom menu item.
331 
332         This will add a user-defined component to use as a menu item.
333 
334         Note that native macOS menus do not support custom components.
335 
336         @see CustomComponent
337     */
338     void addCustomItem (int itemResultID,
339                         std::unique_ptr<CustomComponent> customComponent,
340                         std::unique_ptr<const PopupMenu> optionalSubMenu = nullptr);
341 
342     /** Appends a custom menu item that can't be used to trigger a result.
343 
344         This will add a user-defined component to use as a menu item.
345         The caller must ensure that the passed-in component stays alive
346         until after the menu has been hidden.
347 
348         If triggerMenuItemAutomaticallyWhenClicked is true, the menu itself will handle
349         detection of a mouse-click on your component, and use that to trigger the
350         menu ID specified in itemResultID. If this is false, the menu item can't
351         be triggered, so itemResultID is not used.
352 
353         Note that native macOS menus do support custom components.
354     */
355     void addCustomItem (int itemResultID,
356                         Component& customComponent,
357                         int idealWidth,
358                         int idealHeight,
359                         bool triggerMenuItemAutomaticallyWhenClicked,
360                         std::unique_ptr<const PopupMenu> optionalSubMenu = nullptr);
361 
362     /** Appends a sub-menu.
363 
364         If the menu that's passed in is empty, it will appear as an inactive item.
365         If the itemResultID argument is non-zero, then the sub-menu item itself can be
366         clicked to trigger it as a command.
367     */
368     void addSubMenu (String subMenuName,
369                      PopupMenu subMenu,
370                      bool isEnabled = true);
371 
372     /** Appends a sub-menu with an icon.
373 
374         If the menu that's passed in is empty, it will appear as an inactive item.
375         If the itemResultID argument is non-zero, then the sub-menu item itself can be
376         clicked to trigger it as a command.
377     */
378     void addSubMenu (String subMenuName,
379                      PopupMenu subMenu,
380                      bool isEnabled,
381                      const Image& iconToUse,
382                      bool isTicked = false,
383                      int itemResultID = 0);
384 
385     /** Appends a sub-menu with an icon.
386 
387         If the menu that's passed in is empty, it will appear as an inactive item.
388         If the itemResultID argument is non-zero, then the sub-menu item itself can be
389         clicked to trigger it as a command.
390 
391         The iconToUse parameter is a Drawable object to use as the icon to the left of
392         the item. The menu will take ownership of this drawable object and will delete it
393         later when no longer needed
394     */
395     void addSubMenu (String subMenuName,
396                      PopupMenu subMenu,
397                      bool isEnabled,
398                      std::unique_ptr<Drawable> iconToUse,
399                      bool isTicked = false,
400                      int itemResultID = 0);
401 
402     /** Appends a separator to the menu, to help break it up into sections.
403         The menu class is smart enough not to display separators at the top or bottom
404         of the menu, and it will replace multiple adjacent separators with a single
405         one, so your code can be quite free and easy about adding these, and it'll
406         always look ok.
407     */
408     void addSeparator();
409 
410     /** Adds a non-clickable text item to the menu.
411         This is a bold-font items which can be used as a header to separate the items
412         into named groups.
413     */
414     void addSectionHeader (String title);
415 
416     /** Adds a column break to the menu, to help break it up into sections.
417         Subsequent items will be placed in a new column, rather than being appended
418         to the current column.
419 
420         If a menu contains explicit column breaks, the menu will never add additional
421         breaks.
422     */
423     void addColumnBreak();
424 
425     /** Returns the number of items that the menu currently contains.
426         (This doesn't count separators).
427     */
428     int getNumItems() const noexcept;
429 
430     /** Returns true if the menu contains a command item that triggers the given command. */
431     bool containsCommandItem (int commandID) const;
432 
433     /** Returns true if the menu contains any items that can be used. */
434     bool containsAnyActiveItems() const noexcept;
435 
436     //==============================================================================
437     /** Class used to create a set of options to pass to the show() method.
438         You can chain together a series of calls to this class's methods to create
439         a set of whatever options you want to specify.
440         E.g. @code
441         PopupMenu menu;
442         ...
443         menu.showMenu (PopupMenu::Options().withMinimumWidth (100)
444                                            .withMaximumNumColumns (3)
445                                            .withTargetComponent (myComp));
446         @endcode
447     */
448     class JUCE_API  Options
449     {
450     public:
451         Options();
452         Options (const Options&) = default;
453         Options& operator= (const Options&) = default;
454 
455         enum class PopupDirection
456         {
457             upwards,
458             downwards
459         };
460 
461         //==============================================================================
462         Options withTargetComponent (Component* targetComponent) const;
463         Options withTargetComponent (Component& targetComponent) const;
464         Options withTargetScreenArea (Rectangle<int> targetArea) const;
465         Options withDeletionCheck (Component& componentToWatchForDeletion) const;
466         Options withMinimumWidth (int minWidth) const;
467         Options withMinimumNumColumns (int minNumColumns) const;
468         Options withMaximumNumColumns (int maxNumColumns) const;
469         Options withStandardItemHeight (int standardHeight) const;
470         Options withItemThatMustBeVisible (int idOfItemToBeVisible) const;
471         Options withParentComponent (Component* parentComponent) const;
472         Options withPreferredPopupDirection (PopupDirection direction) const;
473         Options withInitiallySelectedItem (int idOfItemToBeSelected) const;
474 
475         //==============================================================================
getParentComponent()476         Component* getParentComponent() const noexcept               { return parentComponent; }
getTargetComponent()477         Component* getTargetComponent() const noexcept               { return targetComponent; }
hasWatchedComponentBeenDeleted()478         bool hasWatchedComponentBeenDeleted() const noexcept         { return isWatchingForDeletion && componentToWatchForDeletion == nullptr; }
getTargetScreenArea()479         Rectangle<int> getTargetScreenArea() const noexcept          { return targetArea; }
getMinimumWidth()480         int getMinimumWidth() const noexcept                         { return minWidth; }
getMaximumNumColumns()481         int getMaximumNumColumns() const noexcept                    { return maxColumns; }
getMinimumNumColumns()482         int getMinimumNumColumns() const noexcept                    { return minColumns; }
getStandardItemHeight()483         int getStandardItemHeight() const noexcept                   { return standardHeight; }
getItemThatMustBeVisible()484         int getItemThatMustBeVisible() const noexcept                { return visibleItemID; }
getPreferredPopupDirection()485         PopupDirection getPreferredPopupDirection() const noexcept   { return preferredPopupDirection; }
getInitiallySelectedItemId()486         int getInitiallySelectedItemId() const noexcept              { return initiallySelectedItemId; }
487 
488     private:
489         //==============================================================================
490         Rectangle<int> targetArea;
491         Component* targetComponent = nullptr;
492         Component* parentComponent = nullptr;
493         WeakReference<Component> componentToWatchForDeletion;
494         int visibleItemID = 0, minWidth = 0, minColumns = 1, maxColumns = 0, standardHeight = 0, initiallySelectedItemId = 0;
495         bool isWatchingForDeletion = false;
496         PopupDirection preferredPopupDirection = PopupDirection::downwards;
497     };
498 
499     //==============================================================================
500    #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN
501     /** Displays the menu and waits for the user to pick something.
502 
503         This will display the menu modally, and return the ID of the item that the
504         user picks. If they click somewhere off the menu to get rid of it without
505         choosing anything, this will return 0.
506 
507         The current location of the mouse will be used as the position to show the
508         menu - to explicitly set the menu's position, use showAt() instead. Depending
509         on where this point is on the screen, the menu will appear above, below or
510         to the side of the point.
511 
512         @param itemIDThatMustBeVisible  if you set this to the ID of one of the menu items,
513                                         then when the menu first appears, it will make sure
514                                         that this item is visible. So if the menu has too many
515                                         items to fit on the screen, it will be scrolled to a
516                                         position where this item is visible.
517         @param minimumWidth             a minimum width for the menu, in pixels. It may be wider
518                                         than this if some items are too long to fit.
519         @param maximumNumColumns        if there are too many items to fit on-screen in a single
520                                         vertical column, the menu may be laid out as a series of
521                                         columns - this is the maximum number allowed. To use the
522                                         default value for this (probably about 7), you can pass
523                                         in zero.
524         @param standardItemHeight       if this is non-zero, it will be used as the standard
525                                         height for menu items (apart from custom items)
526         @param callback                 if this is not a nullptr, the menu will be launched
527                                         asynchronously, returning immediately, and the callback
528                                         will receive a call when the menu is either dismissed or
529                                         has an item selected. This object will be owned and
530                                         deleted by the system, so make sure that it works safely
531                                         and that any pointers that it uses are safely within scope.
532         @see showAt
533     */
534     int show (int itemIDThatMustBeVisible = 0,
535               int minimumWidth = 0,
536               int maximumNumColumns = 0,
537               int standardItemHeight = 0,
538               ModalComponentManager::Callback* callback = nullptr);
539 
540 
541     /** Displays the menu at a specific location.
542 
543         This is the same as show(), but uses a specific location (in global screen
544         coordinates) rather than the current mouse position.
545 
546         The screenAreaToAttachTo parameter indicates a screen area to which the menu
547         will be adjacent. Depending on where this is, the menu will decide which edge to
548         attach itself to, in order to fit itself fully on-screen. If you just want to
549         trigger a menu at a specific point, you can pass in a rectangle of size (0, 0)
550         with the position that you want.
551 
552         @see show()
553     */
554     int showAt (Rectangle<int> screenAreaToAttachTo,
555                 int itemIDThatMustBeVisible = 0,
556                 int minimumWidth = 0,
557                 int maximumNumColumns = 0,
558                 int standardItemHeight = 0,
559                 ModalComponentManager::Callback* callback = nullptr);
560 
561     /** Displays the menu as if it's attached to a component such as a button.
562 
563         This is similar to showAt(), but will position it next to the given component, e.g.
564         so that the menu's edge is aligned with that of the component. This is intended for
565         things like buttons that trigger a pop-up menu.
566     */
567     int showAt (Component* componentToAttachTo,
568                 int itemIDThatMustBeVisible = 0,
569                 int minimumWidth = 0,
570                 int maximumNumColumns = 0,
571                 int standardItemHeight = 0,
572                 ModalComponentManager::Callback* callback = nullptr);
573 
574     /** Displays and runs the menu modally, with a set of options.
575     */
576     int showMenu (const Options& options);
577    #endif
578 
579     /** Runs the menu asynchronously. */
580     void showMenuAsync (const Options& options);
581 
582     /** Runs the menu asynchronously, with a user-provided callback that will receive the result. */
583     void showMenuAsync (const Options& options,
584                         ModalComponentManager::Callback* callback);
585 
586     /** Runs the menu asynchronously, with a user-provided callback that will receive the result. */
587     void showMenuAsync (const Options& options,
588                         std::function<void (int)> callback);
589 
590     //==============================================================================
591     /** Closes any menus that are currently open.
592 
593         This might be useful if you have a situation where your window is being closed
594         by some means other than a user action, and you'd like to make sure that menus
595         aren't left hanging around.
596     */
597     static bool JUCE_CALLTYPE dismissAllActiveMenus();
598 
599 
600     //==============================================================================
601     /** Specifies a look-and-feel for the menu and any sub-menus that it has.
602 
603         This can be called before show() if you need a customised menu. Be careful
604         not to delete the LookAndFeel object before the menu has been deleted.
605     */
606     void setLookAndFeel (LookAndFeel* newLookAndFeel);
607 
608     //==============================================================================
609     /** A set of colour IDs to use to change the colour of various aspects of the menu.
610 
611         These constants can be used either via the LookAndFeel::setColour()
612         method for the look and feel that is set for this menu with setLookAndFeel()
613 
614         @see setLookAndFeel, LookAndFeel::setColour, LookAndFeel::findColour
615     */
616     enum ColourIds
617     {
618         backgroundColourId             = 0x1000700,  /**< The colour to fill the menu's background with. */
619         textColourId                   = 0x1000600,  /**< The colour for normal menu item text, (unless the
620                                                           colour is specified when the item is added). */
621         headerTextColourId             = 0x1000601,  /**< The colour for section header item text (see the
622                                                           addSectionHeader() method). */
623         highlightedBackgroundColourId  = 0x1000900,  /**< The colour to fill the background of the currently
624                                                           highlighted menu item. */
625         highlightedTextColourId        = 0x1000800,  /**< The colour to use for the text of the currently
626                                                           highlighted item. */
627     };
628 
629     //==============================================================================
630     /**
631         Allows you to iterate through the items in a pop-up menu, and examine
632         their properties.
633 
634         To use this, just create one and repeatedly call its next() method. When this
635         returns true, all the member variables of the iterator are filled-out with
636         information describing the menu item. When it returns false, the end of the
637         list has been reached.
638     */
639     class JUCE_API  MenuItemIterator
640     {
641     public:
642         //==============================================================================
643         /** Creates an iterator that will scan through the items in the specified
644             menu.
645 
646             Be careful not to add any items to a menu while it is being iterated,
647             or things could get out of step.
648 
649             @param menu                 the menu that needs to be scanned
650 
651             @param searchRecursively    if true, all submenus will be recursed into to
652                                         do an exhaustive search
653         */
654         MenuItemIterator (const PopupMenu& menu, bool searchRecursively = false);
655 
656         /** Destructor. */
657         ~MenuItemIterator();
658 
659         /** Returns true if there is another item, and sets up all this object's
660             member variables to reflect that item's properties.
661         */
662         bool next();
663 
664         /** Returns a reference to the description of the current item.
665             It is only valid to call this after next() has returned true!
666         */
667         Item& getItem() const;
668 
669     private:
670         //==============================================================================
671         bool searchRecursively;
672 
673         Array<int> index;
674         Array<const PopupMenu*> menus;
675         PopupMenu::Item* currentItem = nullptr;
676 
677         MenuItemIterator& operator= (const MenuItemIterator&);
678         JUCE_LEAK_DETECTOR (MenuItemIterator)
679     };
680 
681     //==============================================================================
682     /** A user-defined component that can be used as an item in a popup menu.
683         @see PopupMenu::addCustomItem
684     */
685     class JUCE_API  CustomComponent  : public Component,
686                                        public SingleThreadedReferenceCountedObject
687     {
688     public:
689         /** Creates a custom item.
690             If isTriggeredAutomatically is true, then the menu will automatically detect
691             a mouse-click on this component and use that to invoke the menu item. If it's
692             false, then it's up to your class to manually trigger the item when it wants to.
693         */
694         CustomComponent (bool isTriggeredAutomatically = true);
695 
696         /** Destructor. */
697         ~CustomComponent() override;
698 
699         /** Returns a rectangle with the size that this component would like to have.
700 
701             Note that the size which this method returns isn't necessarily the one that
702             the menu will give it, as the items will be stretched to have a uniform width.
703         */
704         virtual void getIdealSize (int& idealWidth, int& idealHeight) = 0;
705 
706         /** Dismisses the menu, indicating that this item has been chosen.
707 
708             This will cause the menu to exit from its modal state, returning
709             this item's id as the result.
710         */
711         void triggerMenuItem();
712 
713         /** Returns true if this item should be highlighted because the mouse is over it.
714             You can call this method in your paint() method to find out whether
715             to draw a highlight.
716         */
isItemHighlighted()717         bool isItemHighlighted() const noexcept                 { return isHighlighted; }
718 
719         /** Returns a pointer to the Item that holds this custom component, if this
720             component is currently held by an Item.
721             You can query the Item for information that you might want to use
722             in your paint() method, such as the item's enabled and ticked states.
723         */
getItem()724         const PopupMenu::Item* getItem() const noexcept         { return item; }
725 
726         /** @internal */
isTriggeredAutomatically()727         bool isTriggeredAutomatically() const noexcept          { return triggeredAutomatically; }
728         /** @internal */
729         void setHighlighted (bool shouldBeHighlighted);
730 
731     private:
732         //==============================================================================
733         bool isHighlighted = false, triggeredAutomatically;
734         const PopupMenu::Item* item = nullptr;
735 
736         friend PopupMenu;
737 
738         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomComponent)
739     };
740 
741     //==============================================================================
742     /** A user-defined callback that can be used for specific items in a popup menu.
743         @see PopupMenu::Item::customCallback
744     */
745     class JUCE_API  CustomCallback  : public SingleThreadedReferenceCountedObject
746     {
747     public:
748         CustomCallback();
749         ~CustomCallback() override;
750 
751         /** Callback to indicate this item has been triggered.
752             @returns true if the itemID should be sent to the exitModalState method, or
753                      false if it should send 0, indicating no further action should be taken
754         */
755         virtual bool menuItemTriggered() = 0;
756 
757     private:
758         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomCallback)
759     };
760 
761     //==============================================================================
762     /** This abstract base class is implemented by LookAndFeel classes to provide
763         menu drawing functionality.
764     */
765     struct JUCE_API  LookAndFeelMethods
766     {
767         virtual ~LookAndFeelMethods() = default;
768 
769         /** Fills the background of a popup menu component. */
770         virtual void drawPopupMenuBackground (Graphics&, int width, int height);
771 
772         /** Fills the background of a popup menu component. */
773         virtual void drawPopupMenuBackgroundWithOptions (Graphics&,
774                                                          int width,
775                                                          int height,
776                                                          const Options&) = 0;
777 
778         /** Draws one of the items in a popup menu. */
779         virtual void drawPopupMenuItem (Graphics&, const Rectangle<int>& area,
780                                         bool isSeparator, bool isActive, bool isHighlighted,
781                                         bool isTicked, bool hasSubMenu,
782                                         const String& text,
783                                         const String& shortcutKeyText,
784                                         const Drawable* icon,
785                                         const Colour* textColour);
786 
787         /** Draws one of the items in a popup menu. */
788         virtual void drawPopupMenuItemWithOptions (Graphics&, const Rectangle<int>& area,
789                                                    bool isHighlighted,
790                                                    const Item& item,
791                                                    const Options&) = 0;
792 
793         virtual void drawPopupMenuSectionHeader (Graphics&, const Rectangle<int>&,
794                                                  const String&);
795 
796         virtual void drawPopupMenuSectionHeaderWithOptions (Graphics&, const Rectangle<int>& area,
797                                                             const String& sectionName,
798                                                             const Options&) = 0;
799 
800         /** Returns the size and style of font to use in popup menus. */
801         virtual Font getPopupMenuFont() = 0;
802 
803         virtual void drawPopupMenuUpDownArrow (Graphics&,
804                                                int width, int height,
805                                                bool isScrollUpArrow);
806 
807         virtual void drawPopupMenuUpDownArrowWithOptions (Graphics&,
808                                                           int width, int height,
809                                                           bool isScrollUpArrow,
810                                                           const Options&) = 0;
811 
812         /** Finds the best size for an item in a popup menu. */
813         virtual void getIdealPopupMenuItemSize (const String& text,
814                                                 bool isSeparator,
815                                                 int standardMenuItemHeight,
816                                                 int& idealWidth,
817                                                 int& idealHeight);
818 
819         /** Finds the best size for an item in a popup menu. */
820         virtual void getIdealPopupMenuItemSizeWithOptions (const String& text,
821                                                            bool isSeparator,
822                                                            int standardMenuItemHeight,
823                                                            int& idealWidth,
824                                                            int& idealHeight,
825                                                            const Options&) = 0;
826 
827         virtual int getMenuWindowFlags() = 0;
828 
829         virtual void drawMenuBarBackground (Graphics&, int width, int height,
830                                             bool isMouseOverBar,
831                                             MenuBarComponent&) = 0;
832 
833         virtual int getDefaultMenuBarHeight() = 0;
834 
835         virtual int getMenuBarItemWidth (MenuBarComponent&, int itemIndex, const String& itemText) = 0;
836 
837         virtual Font getMenuBarFont (MenuBarComponent&, int itemIndex, const String& itemText) = 0;
838 
839         virtual void drawMenuBarItem (Graphics&, int width, int height,
840                                       int itemIndex,
841                                       const String& itemText,
842                                       bool isMouseOverItem,
843                                       bool isMenuOpen,
844                                       bool isMouseOverBar,
845                                       MenuBarComponent&) = 0;
846 
847         virtual Component* getParentComponentForMenuOptions (const Options& options) = 0;
848 
849         virtual void preparePopupMenuWindow (Component& newWindow) = 0;
850 
851         /** Return true if you want your popup menus to scale with the target component's AffineTransform
852             or scale factor
853         */
854         virtual bool shouldPopupMenuScaleWithTargetComponent (const Options& options) = 0;
855 
856         virtual int getPopupMenuBorderSize();
857 
858         virtual int getPopupMenuBorderSizeWithOptions (const Options&) = 0;
859 
860         /** Implement this to draw some custom decoration between the columns of the popup menu.
861 
862             `getPopupMenuColumnSeparatorWidthWithOptions` must return a positive value in order
863             to display the separator.
864         */
865         virtual void drawPopupMenuColumnSeparatorWithOptions (Graphics& g,
866                                                               const Rectangle<int>& bounds,
867                                                               const Options&) = 0;
868 
869         /** Return the amount of space that should be left between popup menu columns. */
870         virtual int getPopupMenuColumnSeparatorWidthWithOptions (const Options&) = 0;
871     };
872 
873 private:
874     //==============================================================================
875     JUCE_PUBLIC_IN_DLL_BUILD (struct HelperClasses)
876     class Window;
877     friend struct HelperClasses;
878     friend class MenuBarComponent;
879 
880     Array<Item> items;
881     WeakReference<LookAndFeel> lookAndFeel;
882 
883     Component* createWindow (const Options&, ApplicationCommandManager**) const;
884     int showWithOptionalCallback (const Options&, ModalComponentManager::Callback*, bool);
885 
886     static void setItem (CustomComponent&, const Item*);
887 
888    #if JUCE_CATCH_DEPRECATED_CODE_MISUSE
889     // These methods have new implementations now - see its new definition
drawPopupMenuItem(Graphics &,int,int,bool,bool,bool,bool,bool,const String &,const String &,Image *,const Colour *)890     int drawPopupMenuItem (Graphics&, int, int, bool, bool, bool, bool, bool, const String&, const String&, Image*, const Colour*) { return 0; }
891    #endif
892 
893     JUCE_LEAK_DETECTOR (PopupMenu)
894 };
895 
896 } // namespace juce
897