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         /** Sets the isTicked flag (and returns a reference to this item to allow chaining). */
183         Item& setTicked (bool shouldBeTicked = true) & noexcept;
184         /** Sets the isEnabled flag (and returns a reference to this item to allow chaining). */
185         Item& setEnabled (bool shouldBeEnabled) & noexcept;
186         /** Sets the action property (and returns a reference to this item to allow chaining). */
187         Item& setAction (std::function<void()> action) & noexcept;
188         /** Sets the itemID property (and returns a reference to this item to allow chaining). */
189         Item& setID (int newID) & noexcept;
190         /** Sets the colour property (and returns a reference to this item to allow chaining). */
191         Item& setColour (Colour) & noexcept;
192         /** Sets the customComponent property (and returns a reference to this item to allow chaining). */
193         Item& setCustomComponent (ReferenceCountedObjectPtr<CustomComponent> customComponent) & noexcept;
194         /** Sets the image property (and returns a reference to this item to allow chaining). */
195         Item& setImage (std::unique_ptr<Drawable>) & noexcept;
196 
197         /** Sets the isTicked flag (and returns a reference to this item to allow chaining). */
198         Item&& setTicked (bool shouldBeTicked = true) && noexcept;
199         /** Sets the isEnabled flag (and returns a reference to this item to allow chaining). */
200         Item&& setEnabled (bool shouldBeEnabled) && noexcept;
201         /** Sets the action property (and returns a reference to this item to allow chaining). */
202         Item&& setAction (std::function<void()> action) && noexcept;
203         /** Sets the itemID property (and returns a reference to this item to allow chaining). */
204         Item&& setID (int newID) && noexcept;
205         /** Sets the colour property (and returns a reference to this item to allow chaining). */
206         Item&& setColour (Colour) && noexcept;
207         /** Sets the customComponent property (and returns a reference to this item to allow chaining). */
208         Item&& setCustomComponent (ReferenceCountedObjectPtr<CustomComponent> customComponent) && noexcept;
209         /** Sets the image property (and returns a reference to this item to allow chaining). */
210         Item&& setImage (std::unique_ptr<Drawable>) && noexcept;
211     };
212 
213     /** Adds an item to the menu.
214         You can call this method for full control over the item that is added, or use the other
215         addItem helper methods if you want to pass arguments rather than creating an Item object.
216     */
217     void addItem (Item newItem);
218 
219     /** Adds an item to the menu with an action callback. */
220     void addItem (String itemText,
221                   std::function<void()> action);
222 
223     /** Adds an item to the menu with an action callback. */
224     void addItem (String itemText,
225                   bool isEnabled,
226                   bool isTicked,
227                   std::function<void()> action);
228 
229     /** Appends a new text item for this menu to show.
230 
231         @param itemResultID     the number that will be returned from the show() method
232                                 if the user picks this item. The value should never be
233                                 zero, because that's used to indicate that the user didn't
234                                 select anything.
235         @param itemText         the text to show.
236         @param isEnabled        if false, the item will be shown 'greyed-out' and can't be picked
237         @param isTicked         if true, the item will be shown with a tick next to it
238 
239         @see addSeparator, addColouredItem, addCustomItem, addSubMenu
240     */
241     void addItem (int itemResultID,
242                   String itemText,
243                   bool isEnabled = true,
244                   bool isTicked = false);
245 
246     /** Appends a new item with an icon.
247 
248         @param itemResultID     the number that will be returned from the show() method
249                                 if the user picks this item. The value should never be
250                                 zero, because that's used to indicate that the user didn't
251                                 select anything.
252         @param itemText         the text to show.
253         @param isEnabled        if false, the item will be shown 'greyed-out' and can't be picked
254         @param isTicked         if true, the item will be shown with a tick next to it
255         @param iconToUse        if this is a valid image, it will be displayed to the left of the item.
256 
257         @see addSeparator, addColouredItem, addCustomItem, addSubMenu
258     */
259     void addItem (int itemResultID,
260                   String itemText,
261                   bool isEnabled,
262                   bool isTicked,
263                   const Image& iconToUse);
264 
265     /** Appends a new item with an icon.
266 
267         @param itemResultID     the number that will be returned from the show() method
268                                 if the user picks this item. The value should never be
269                                 zero, because that's used to indicate that the user didn't
270                                 select anything.
271         @param itemText         the text to show.
272         @param isEnabled        if false, the item will be shown 'greyed-out' and can't be picked
273         @param isTicked         if true, the item will be shown with a tick next to it
274         @param iconToUse        a Drawable object to use as the icon to the left of the item.
275                                 The menu will take ownership of this drawable object and will
276                                 delete it later when no longer needed
277         @see addSeparator, addColouredItem, addCustomItem, addSubMenu
278     */
279     void addItem (int itemResultID,
280                   String itemText,
281                   bool isEnabled,
282                   bool isTicked,
283                   std::unique_ptr<Drawable> iconToUse);
284 
285     /** Adds an item that represents one of the commands in a command manager object.
286 
287         @param commandManager       the manager to use to trigger the command and get information
288                                     about it
289         @param commandID            the ID of the command
290         @param displayName          if this is non-empty, then this string will be used instead of
291                                     the command's registered name
292         @param iconToUse            an optional Drawable object to use as the icon to the left of the item.
293                                     The menu will take ownership of this drawable object and will
294                                     delete it later when no longer needed
295     */
296     void addCommandItem (ApplicationCommandManager* commandManager,
297                          CommandID commandID,
298                          String displayName = {},
299                          std::unique_ptr<Drawable> iconToUse = {});
300 
301     /** Appends a text item with a special colour.
302 
303         This is the same as addItem(), but specifies a colour to use for the
304         text, which will override the default colours that are used by the
305         current look-and-feel. See addItem() for a description of the parameters.
306     */
307     void addColouredItem (int itemResultID,
308                           String itemText,
309                           Colour itemTextColour,
310                           bool isEnabled = true,
311                           bool isTicked = false,
312                           const Image& iconToUse = {});
313 
314     /** Appends a text item with a special colour.
315 
316         This is the same as addItem(), but specifies a colour to use for the
317         text, which will override the default colours that are used by the
318         current look-and-feel. See addItem() for a description of the parameters.
319     */
320     void addColouredItem (int itemResultID,
321                           String itemText,
322                           Colour itemTextColour,
323                           bool isEnabled,
324                           bool isTicked,
325                           std::unique_ptr<Drawable> iconToUse);
326 
327     /** Appends a custom menu item.
328 
329         This will add a user-defined component to use as a menu item.
330 
331         Note that native macOS menus do not support custom components.
332 
333         @see CustomComponent
334     */
335     void addCustomItem (int itemResultID,
336                         std::unique_ptr<CustomComponent> customComponent,
337                         std::unique_ptr<const PopupMenu> optionalSubMenu = nullptr);
338 
339     /** Appends a custom menu item that can't be used to trigger a result.
340 
341         This will add a user-defined component to use as a menu item.
342         The caller must ensure that the passed-in component stays alive
343         until after the menu has been hidden.
344 
345         If triggerMenuItemAutomaticallyWhenClicked is true, the menu itself will handle
346         detection of a mouse-click on your component, and use that to trigger the
347         menu ID specified in itemResultID. If this is false, the menu item can't
348         be triggered, so itemResultID is not used.
349 
350         Note that native macOS menus do support custom components.
351     */
352     void addCustomItem (int itemResultID,
353                         Component& customComponent,
354                         int idealWidth,
355                         int idealHeight,
356                         bool triggerMenuItemAutomaticallyWhenClicked,
357                         std::unique_ptr<const PopupMenu> optionalSubMenu = nullptr);
358 
359     /** Appends a sub-menu.
360 
361         If the menu that's passed in is empty, it will appear as an inactive item.
362         If the itemResultID argument is non-zero, then the sub-menu item itself can be
363         clicked to trigger it as a command.
364     */
365     void addSubMenu (String subMenuName,
366                      PopupMenu subMenu,
367                      bool isEnabled = true);
368 
369     /** Appends a sub-menu with an icon.
370 
371         If the menu that's passed in is empty, it will appear as an inactive item.
372         If the itemResultID argument is non-zero, then the sub-menu item itself can be
373         clicked to trigger it as a command.
374     */
375     void addSubMenu (String subMenuName,
376                      PopupMenu subMenu,
377                      bool isEnabled,
378                      const Image& iconToUse,
379                      bool isTicked = false,
380                      int itemResultID = 0);
381 
382     /** Appends a sub-menu with an icon.
383 
384         If the menu that's passed in is empty, it will appear as an inactive item.
385         If the itemResultID argument is non-zero, then the sub-menu item itself can be
386         clicked to trigger it as a command.
387 
388         The iconToUse parameter is a Drawable object to use as the icon to the left of
389         the item. The menu will take ownership of this drawable object and will delete it
390         later when no longer needed
391     */
392     void addSubMenu (String subMenuName,
393                      PopupMenu subMenu,
394                      bool isEnabled,
395                      std::unique_ptr<Drawable> iconToUse,
396                      bool isTicked = false,
397                      int itemResultID = 0);
398 
399     /** Appends a separator to the menu, to help break it up into sections.
400         The menu class is smart enough not to display separators at the top or bottom
401         of the menu, and it will replace multiple adjacent separators with a single
402         one, so your code can be quite free and easy about adding these, and it'll
403         always look ok.
404     */
405     void addSeparator();
406 
407     /** Adds a non-clickable text item to the menu.
408         This is a bold-font items which can be used as a header to separate the items
409         into named groups.
410     */
411     void addSectionHeader (String title);
412 
413     /** Returns the number of items that the menu currently contains.
414         (This doesn't count separators).
415     */
416     int getNumItems() const noexcept;
417 
418     /** Returns true if the menu contains a command item that triggers the given command. */
419     bool containsCommandItem (int commandID) const;
420 
421     /** Returns true if the menu contains any items that can be used. */
422     bool containsAnyActiveItems() const noexcept;
423 
424     //==============================================================================
425     /** Class used to create a set of options to pass to the show() method.
426         You can chain together a series of calls to this class's methods to create
427         a set of whatever options you want to specify.
428         E.g. @code
429         PopupMenu menu;
430         ...
431         menu.showMenu (PopupMenu::Options().withMinimumWidth (100)
432                                            .withMaximumNumColumns (3)
433                                            .withTargetComponent (myComp));
434         @endcode
435     */
436     class JUCE_API  Options
437     {
438     public:
439         Options();
440         Options (const Options&) = default;
441         Options& operator= (const Options&) = default;
442 
443         enum class PopupDirection
444         {
445             upwards,
446             downwards
447         };
448 
449         //==============================================================================
450         Options withTargetComponent (Component* targetComponent) const;
451         Options withTargetComponent (Component& targetComponent) const;
452         Options withTargetScreenArea (Rectangle<int> targetArea) const;
453         Options withDeletionCheck (Component& componentToWatchForDeletion) const;
454         Options withMinimumWidth (int minWidth) const;
455         Options withMinimumNumColumns (int minNumColumns) const;
456         Options withMaximumNumColumns (int maxNumColumns) const;
457         Options withStandardItemHeight (int standardHeight) const;
458         Options withItemThatMustBeVisible (int idOfItemToBeVisible) const;
459         Options withParentComponent (Component* parentComponent) const;
460         Options withPreferredPopupDirection (PopupDirection direction) const;
461 
462         //==============================================================================
getParentComponent()463         Component* getParentComponent() const noexcept               { return parentComponent; }
getTargetComponent()464         Component* getTargetComponent() const noexcept               { return targetComponent; }
hasWatchedComponentBeenDeleted()465         bool hasWatchedComponentBeenDeleted() const noexcept         { return isWatchingForDeletion && componentToWatchForDeletion == nullptr; }
getTargetScreenArea()466         Rectangle<int> getTargetScreenArea() const noexcept          { return targetArea; }
getMinimumWidth()467         int getMinimumWidth() const noexcept                         { return minWidth; }
getMaximumNumColumns()468         int getMaximumNumColumns() const noexcept                    { return maxColumns; }
getMinimumNumColumns()469         int getMinimumNumColumns() const noexcept                    { return minColumns; }
getStandardItemHeight()470         int getStandardItemHeight() const noexcept                   { return standardHeight; }
getItemThatMustBeVisible()471         int getItemThatMustBeVisible() const noexcept                { return visibleItemID; }
getPreferredPopupDirection()472         PopupDirection getPreferredPopupDirection() const noexcept   { return preferredPopupDirection; }
473 
474     private:
475         //==============================================================================
476         Rectangle<int> targetArea;
477         Component* targetComponent = nullptr;
478         Component* parentComponent = nullptr;
479         WeakReference<Component> componentToWatchForDeletion;
480         int visibleItemID = 0, minWidth = 0, minColumns = 1, maxColumns = 0, standardHeight = 0;
481         bool isWatchingForDeletion = false;
482         PopupDirection preferredPopupDirection = PopupDirection::downwards;
483     };
484 
485     //==============================================================================
486    #if JUCE_MODAL_LOOPS_PERMITTED
487     /** Displays the menu and waits for the user to pick something.
488 
489         This will display the menu modally, and return the ID of the item that the
490         user picks. If they click somewhere off the menu to get rid of it without
491         choosing anything, this will return 0.
492 
493         The current location of the mouse will be used as the position to show the
494         menu - to explicitly set the menu's position, use showAt() instead. Depending
495         on where this point is on the screen, the menu will appear above, below or
496         to the side of the point.
497 
498         @param itemIDThatMustBeVisible  if you set this to the ID of one of the menu items,
499                                         then when the menu first appears, it will make sure
500                                         that this item is visible. So if the menu has too many
501                                         items to fit on the screen, it will be scrolled to a
502                                         position where this item is visible.
503         @param minimumWidth             a minimum width for the menu, in pixels. It may be wider
504                                         than this if some items are too long to fit.
505         @param maximumNumColumns        if there are too many items to fit on-screen in a single
506                                         vertical column, the menu may be laid out as a series of
507                                         columns - this is the maximum number allowed. To use the
508                                         default value for this (probably about 7), you can pass
509                                         in zero.
510         @param standardItemHeight       if this is non-zero, it will be used as the standard
511                                         height for menu items (apart from custom items)
512         @param callback                 if this is not a nullptr, the menu will be launched
513                                         asynchronously, returning immediately, and the callback
514                                         will receive a call when the menu is either dismissed or
515                                         has an item selected. This object will be owned and
516                                         deleted by the system, so make sure that it works safely
517                                         and that any pointers that it uses are safely within scope.
518         @see showAt
519     */
520     int show (int itemIDThatMustBeVisible = 0,
521               int minimumWidth = 0,
522               int maximumNumColumns = 0,
523               int standardItemHeight = 0,
524               ModalComponentManager::Callback* callback = nullptr);
525 
526 
527     /** Displays the menu at a specific location.
528 
529         This is the same as show(), but uses a specific location (in global screen
530         coordinates) rather than the current mouse position.
531 
532         The screenAreaToAttachTo parameter indicates a screen area to which the menu
533         will be adjacent. Depending on where this is, the menu will decide which edge to
534         attach itself to, in order to fit itself fully on-screen. If you just want to
535         trigger a menu at a specific point, you can pass in a rectangle of size (0, 0)
536         with the position that you want.
537 
538         @see show()
539     */
540     int showAt (Rectangle<int> screenAreaToAttachTo,
541                 int itemIDThatMustBeVisible = 0,
542                 int minimumWidth = 0,
543                 int maximumNumColumns = 0,
544                 int standardItemHeight = 0,
545                 ModalComponentManager::Callback* callback = nullptr);
546 
547     /** Displays the menu as if it's attached to a component such as a button.
548 
549         This is similar to showAt(), but will position it next to the given component, e.g.
550         so that the menu's edge is aligned with that of the component. This is intended for
551         things like buttons that trigger a pop-up menu.
552     */
553     int showAt (Component* componentToAttachTo,
554                 int itemIDThatMustBeVisible = 0,
555                 int minimumWidth = 0,
556                 int maximumNumColumns = 0,
557                 int standardItemHeight = 0,
558                 ModalComponentManager::Callback* callback = nullptr);
559 
560     /** Displays and runs the menu modally, with a set of options.
561     */
562     int showMenu (const Options& options);
563    #endif
564 
565     /** Runs the menu asynchronously. */
566     void showMenuAsync (const Options& options);
567 
568     /** Runs the menu asynchronously, with a user-provided callback that will receive the result. */
569     void showMenuAsync (const Options& options,
570                         ModalComponentManager::Callback* callback);
571 
572     /** Runs the menu asynchronously, with a user-provided callback that will receive the result. */
573     void showMenuAsync (const Options& options,
574                         std::function<void (int)> callback);
575 
576     //==============================================================================
577     /** Closes any menus that are currently open.
578 
579         This might be useful if you have a situation where your window is being closed
580         by some means other than a user action, and you'd like to make sure that menus
581         aren't left hanging around.
582     */
583     static bool JUCE_CALLTYPE dismissAllActiveMenus();
584 
585 
586     //==============================================================================
587     /** Specifies a look-and-feel for the menu and any sub-menus that it has.
588 
589         This can be called before show() if you need a customised menu. Be careful
590         not to delete the LookAndFeel object before the menu has been deleted.
591     */
592     void setLookAndFeel (LookAndFeel* newLookAndFeel);
593 
594     //==============================================================================
595     /** A set of colour IDs to use to change the colour of various aspects of the menu.
596 
597         These constants can be used either via the LookAndFeel::setColour()
598         method for the look and feel that is set for this menu with setLookAndFeel()
599 
600         @see setLookAndFeel, LookAndFeel::setColour, LookAndFeel::findColour
601     */
602     enum ColourIds
603     {
604         backgroundColourId             = 0x1000700,  /**< The colour to fill the menu's background with. */
605         textColourId                   = 0x1000600,  /**< The colour for normal menu item text, (unless the
606                                                           colour is specified when the item is added). */
607         headerTextColourId             = 0x1000601,  /**< The colour for section header item text (see the
608                                                           addSectionHeader() method). */
609         highlightedBackgroundColourId  = 0x1000900,  /**< The colour to fill the background of the currently
610                                                           highlighted menu item. */
611         highlightedTextColourId        = 0x1000800,  /**< The colour to use for the text of the currently
612                                                           highlighted item. */
613     };
614 
615     //==============================================================================
616     /**
617         Allows you to iterate through the items in a pop-up menu, and examine
618         their properties.
619 
620         To use this, just create one and repeatedly call its next() method. When this
621         returns true, all the member variables of the iterator are filled-out with
622         information describing the menu item. When it returns false, the end of the
623         list has been reached.
624     */
625     class JUCE_API  MenuItemIterator
626     {
627     public:
628         //==============================================================================
629         /** Creates an iterator that will scan through the items in the specified
630             menu.
631 
632             Be careful not to add any items to a menu while it is being iterated,
633             or things could get out of step.
634 
635             @param menu                 the menu that needs to be scanned
636 
637             @param searchRecursively    if true, all submenus will be recursed into to
638                                         do an exhaustive search
639         */
640         MenuItemIterator (const PopupMenu& menu, bool searchRecursively = false);
641 
642         /** Destructor. */
643         ~MenuItemIterator();
644 
645         /** Returns true if there is another item, and sets up all this object's
646             member variables to reflect that item's properties.
647         */
648         bool next();
649 
650         /** Returns a reference to the description of the current item.
651             It is only valid to call this after next() has returned true!
652         */
653         Item& getItem() const;
654 
655     private:
656         //==============================================================================
657         bool searchRecursively;
658 
659         Array<int> index;
660         Array<const PopupMenu*> menus;
661         PopupMenu::Item* currentItem = nullptr;
662 
663         MenuItemIterator& operator= (const MenuItemIterator&);
664         JUCE_LEAK_DETECTOR (MenuItemIterator)
665     };
666 
667     //==============================================================================
668     /** A user-defined component that can be used as an item in a popup menu.
669         @see PopupMenu::addCustomItem
670     */
671     class JUCE_API  CustomComponent  : public Component,
672                                        public SingleThreadedReferenceCountedObject
673     {
674     public:
675         /** Creates a custom item.
676             If isTriggeredAutomatically is true, then the menu will automatically detect
677             a mouse-click on this component and use that to invoke the menu item. If it's
678             false, then it's up to your class to manually trigger the item when it wants to.
679         */
680         CustomComponent (bool isTriggeredAutomatically = true);
681 
682         /** Destructor. */
683         ~CustomComponent() override;
684 
685         /** Returns a rectangle with the size that this component would like to have.
686 
687             Note that the size which this method returns isn't necessarily the one that
688             the menu will give it, as the items will be stretched to have a uniform width.
689         */
690         virtual void getIdealSize (int& idealWidth, int& idealHeight) = 0;
691 
692         /** Dismisses the menu, indicating that this item has been chosen.
693 
694             This will cause the menu to exit from its modal state, returning
695             this item's id as the result.
696         */
697         void triggerMenuItem();
698 
699         /** Returns true if this item should be highlighted because the mouse is over it.
700             You can call this method in your paint() method to find out whether
701             to draw a highlight.
702         */
isItemHighlighted()703         bool isItemHighlighted() const noexcept                 { return isHighlighted; }
704 
705         /** Returns a pointer to the Item that holds this custom component, if this
706             component is currently held by an Item.
707             You can query the Item for information that you might want to use
708             in your paint() method, such as the item's enabled and ticked states.
709         */
getItem()710         const PopupMenu::Item* getItem() const noexcept         { return item; }
711 
712         /** @internal */
isTriggeredAutomatically()713         bool isTriggeredAutomatically() const noexcept          { return triggeredAutomatically; }
714         /** @internal */
715         void setHighlighted (bool shouldBeHighlighted);
716 
717     private:
718         //==============================================================================
719         bool isHighlighted = false, triggeredAutomatically;
720         const PopupMenu::Item* item = nullptr;
721 
722         friend PopupMenu;
723 
724         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomComponent)
725     };
726 
727     //==============================================================================
728     /** A user-defined callback that can be used for specific items in a popup menu.
729         @see PopupMenu::Item::customCallback
730     */
731     class JUCE_API  CustomCallback  : public SingleThreadedReferenceCountedObject
732     {
733     public:
734         CustomCallback();
735         ~CustomCallback() override;
736 
737         /** Callback to indicate this item has been triggered.
738             @returns true if the itemID should be sent to the exitModalState method, or
739                      false if it should send 0, indicating no further action should be taken
740         */
741         virtual bool menuItemTriggered() = 0;
742 
743     private:
744         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomCallback)
745     };
746 
747     //==============================================================================
748     /** This abstract base class is implemented by LookAndFeel classes to provide
749         menu drawing functionality.
750     */
751     struct JUCE_API  LookAndFeelMethods
752     {
753         virtual ~LookAndFeelMethods() = default;
754 
755         /** Fills the background of a popup menu component. */
756         virtual void drawPopupMenuBackground (Graphics&, int width, int height) = 0;
757 
758         /** Draws one of the items in a popup menu. */
759         virtual void drawPopupMenuItem (Graphics&, const Rectangle<int>& area,
760                                         bool isSeparator, bool isActive, bool isHighlighted,
761                                         bool isTicked, bool hasSubMenu,
762                                         const String& text,
763                                         const String& shortcutKeyText,
764                                         const Drawable* icon,
765                                         const Colour* textColour) = 0;
766 
767         virtual void drawPopupMenuSectionHeader (Graphics&, const Rectangle<int>& area,
768                                                  const String& sectionName) = 0;
769 
770         /** Returns the size and style of font to use in popup menus. */
771         virtual Font getPopupMenuFont() = 0;
772 
773         virtual void drawPopupMenuUpDownArrow (Graphics&,
774                                                int width, int height,
775                                                bool isScrollUpArrow) = 0;
776 
777         /** Finds the best size for an item in a popup menu. */
778         virtual void getIdealPopupMenuItemSize (const String& text,
779                                                 bool isSeparator,
780                                                 int standardMenuItemHeight,
781                                                 int& idealWidth,
782                                                 int& idealHeight) = 0;
783 
784         virtual int getMenuWindowFlags() = 0;
785 
786         virtual void drawMenuBarBackground (Graphics&, int width, int height,
787                                             bool isMouseOverBar,
788                                             MenuBarComponent&) = 0;
789 
790         virtual int getDefaultMenuBarHeight() = 0;
791 
792         virtual int getMenuBarItemWidth (MenuBarComponent&, int itemIndex, const String& itemText) = 0;
793 
794         virtual Font getMenuBarFont (MenuBarComponent&, int itemIndex, const String& itemText) = 0;
795 
796         virtual void drawMenuBarItem (Graphics&, int width, int height,
797                                       int itemIndex,
798                                       const String& itemText,
799                                       bool isMouseOverItem,
800                                       bool isMenuOpen,
801                                       bool isMouseOverBar,
802                                       MenuBarComponent&) = 0;
803 
804         virtual Component* getParentComponentForMenuOptions (const PopupMenu::Options& options) = 0;
805 
806         virtual void preparePopupMenuWindow (Component& newWindow) = 0;
807 
808         /** Return true if you want your popup menus to scale with the target component's AffineTransform
809             or scale factor */
810         virtual bool shouldPopupMenuScaleWithTargetComponent (const PopupMenu::Options& options) = 0;
811 
812         virtual int getPopupMenuBorderSize() = 0;
813     };
814 
815 private:
816     //==============================================================================
817     JUCE_PUBLIC_IN_DLL_BUILD (struct HelperClasses)
818     class Window;
819     friend struct HelperClasses;
820     friend class MenuBarComponent;
821 
822     Array<Item> items;
823     WeakReference<LookAndFeel> lookAndFeel;
824 
825     Component* createWindow (const Options&, ApplicationCommandManager**) const;
826     int showWithOptionalCallback (const Options&, ModalComponentManager::Callback*, bool);
827 
828     static void setItem (CustomComponent&, const Item*);
829 
830    #if JUCE_CATCH_DEPRECATED_CODE_MISUSE
831     // 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 *)832     int drawPopupMenuItem (Graphics&, int, int, bool, bool, bool, bool, bool, const String&, const String&, Image*, const Colour*) { return 0; }
833    #endif
834 
835     JUCE_LEAK_DETECTOR (PopupMenu)
836 };
837 
838 } // namespace juce
839