2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../../../core/juce_StandardHeader.h"
30 #include "juce_PopupMenu.h"
31 #include "../windows/juce_ComponentPeer.h"
32 #include "../lookandfeel/juce_LookAndFeel.h"
33 #include "../juce_Desktop.h"
34 #include "../../graphics/imaging/juce_Image.h"
35 #include "../keyboard/juce_KeyPressMappingSet.h"
36 #include "../../../events/juce_Timer.h"
37 #include "../../../threads/juce_Process.h"
38 #include "../../../core/juce_Time.h"
41 //==============================================================================
45 //==============================================================================
47 : itemId (0), active (true), isSeparator (true), isTicked (false),
48 usesColour (false), commandManager (nullptr)
52 Item (const int itemId_
,
57 const Colour
& textColour_
,
58 const bool usesColour_
,
59 CustomComponent
* const customComp_
,
60 const PopupMenu
* const subMenu_
,
61 ApplicationCommandManager
* const commandManager_
)
62 : itemId (itemId_
), text (text_
), textColour (textColour_
),
63 active (active_
), isSeparator (false), isTicked (isTicked_
),
64 usesColour (usesColour_
), image (im
), customComp (customComp_
),
65 commandManager (commandManager_
)
67 if (subMenu_
!= nullptr)
68 subMenu
= new PopupMenu (*subMenu_
);
70 if (commandManager_
!= nullptr && itemId_
!= 0)
74 Array
<KeyPress
> keyPresses (commandManager_
->getKeyMappings()
75 ->getKeyPressesAssignedToCommand (itemId_
));
77 for (int i
= 0; i
< keyPresses
.size(); ++i
)
79 const String
key (keyPresses
.getReference(i
).getTextDescriptionWithIcons());
81 if (shortcutKey
.isNotEmpty())
84 if (key
.length() == 1 && key
[0] < 128)
85 shortcutKey
<< "shortcut: '" << key
<< '\'';
90 shortcutKey
= shortcutKey
.trim();
92 if (shortcutKey
.isNotEmpty())
93 text
<< "<end>" << shortcutKey
;
97 Item (const Item
& other
)
98 : itemId (other
.itemId
),
100 textColour (other
.textColour
),
101 active (other
.active
),
102 isSeparator (other
.isSeparator
),
103 isTicked (other
.isTicked
),
104 usesColour (other
.usesColour
),
106 customComp (other
.customComp
),
107 commandManager (other
.commandManager
)
109 if (other
.subMenu
!= nullptr)
110 subMenu
= new PopupMenu (*(other
.subMenu
));
113 bool canBeTriggered() const noexcept
{ return active
&& ! (isSeparator
|| (subMenu
!= nullptr)); }
114 bool hasActiveSubMenu() const noexcept
{ return active
&& (subMenu
!= nullptr); }
116 //==============================================================================
119 const Colour textColour
;
120 const bool active
, isSeparator
, isTicked
, usesColour
;
122 ReferenceCountedObjectPtr
<CustomComponent
> customComp
;
123 ScopedPointer
<PopupMenu
> subMenu
;
124 ApplicationCommandManager
* const commandManager
;
127 Item
& operator= (const Item
&);
129 JUCE_LEAK_DETECTOR (Item
);
133 //==============================================================================
134 class PopupMenu::ItemComponent
: public Component
137 //==============================================================================
138 ItemComponent (const PopupMenu::Item
& itemInfo_
, int standardItemHeight
, Component
* const parent
)
139 : itemInfo (itemInfo_
),
140 isHighlighted (false)
142 if (itemInfo
.customComp
!= nullptr)
143 addAndMakeVisible (itemInfo
.customComp
);
145 parent
->addAndMakeVisible (this);
149 getIdealSize (itemW
, itemH
, standardItemHeight
);
150 setSize (itemW
, jlimit (2, 600, itemH
));
152 addMouseListener (parent
, false);
157 if (itemInfo
.customComp
!= nullptr)
158 removeChildComponent (itemInfo
.customComp
);
161 void getIdealSize (int& idealWidth
, int& idealHeight
, const int standardItemHeight
)
163 if (itemInfo
.customComp
!= nullptr)
164 itemInfo
.customComp
->getIdealSize (idealWidth
, idealHeight
);
166 getLookAndFeel().getIdealPopupMenuItemSize (itemInfo
.text
,
167 itemInfo
.isSeparator
,
169 idealWidth
, idealHeight
);
172 void paint (Graphics
& g
)
174 if (itemInfo
.customComp
== nullptr)
176 String
mainText (itemInfo
.text
);
178 const int endIndex
= mainText
.indexOf ("<end>");
182 endText
= mainText
.substring (endIndex
+ 5).trim();
183 mainText
= mainText
.substring (0, endIndex
);
187 .drawPopupMenuItem (g
, getWidth(), getHeight(),
188 itemInfo
.isSeparator
,
192 itemInfo
.subMenu
!= 0,
194 itemInfo
.image
.isValid() ? &itemInfo
.image
: 0,
195 itemInfo
.usesColour
? &(itemInfo
.textColour
) : 0);
201 if (getNumChildComponents() > 0)
202 getChildComponent(0)->setBounds (2, 0, getWidth() - 4, getHeight());
205 void setHighlighted (bool shouldBeHighlighted
)
207 shouldBeHighlighted
= shouldBeHighlighted
&& itemInfo
.active
;
209 if (isHighlighted
!= shouldBeHighlighted
)
211 isHighlighted
= shouldBeHighlighted
;
213 if (itemInfo
.customComp
!= nullptr)
214 itemInfo
.customComp
->setHighlighted (shouldBeHighlighted
);
220 PopupMenu::Item itemInfo
;
225 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemComponent
);
229 //==============================================================================
230 namespace PopupMenuSettings
232 const int scrollZone
= 24;
233 const int borderSize
= 2;
234 const int timerInterval
= 50;
235 const int dismissCommandId
= 0x6287345f;
237 static bool menuWasHiddenBecauseOfAppChange
= false;
240 //==============================================================================
241 class PopupMenu::Window
: public Component
,
245 //==============================================================================
246 Window (const PopupMenu
& menu
, Window
* const owner_
, const Rectangle
<int>& target
,
247 const bool alignToRectangle
, const int itemIdThatMustBeVisible
,
248 const int minimumWidth_
, const int maximumNumColumns_
,
249 const int standardItemHeight_
, const bool dismissOnMouseUp_
,
250 ApplicationCommandManager
** const managerOfChosenCommand_
,
251 Component
* const componentAttachedTo_
)
252 : Component ("menu"),
254 activeSubMenu (nullptr),
255 managerOfChosenCommand (managerOfChosenCommand_
),
256 componentAttachedTo (componentAttachedTo_
),
257 componentAttachedToOriginal (componentAttachedTo_
),
258 minimumWidth (minimumWidth_
),
259 maximumNumColumns (maximumNumColumns_
),
260 standardItemHeight (standardItemHeight_
),
264 needsToScroll (false),
265 dismissOnMouseUp (dismissOnMouseUp_
),
267 disableMouseMoves (false),
268 hasAnyJuceCompHadFocus (false),
272 menuCreationTime (Time::getMillisecondCounter()),
273 lastMouseMoveTime (0),
274 timeEnteredCurrentChildComp (0),
275 scrollAcceleration (1.0)
277 lastFocused
= lastScroll
= menuCreationTime
;
278 setWantsKeyboardFocus (false);
279 setMouseClickGrabsKeyboardFocus (false);
280 setAlwaysOnTop (true);
282 setLookAndFeel (menu
.lookAndFeel
);
283 setOpaque (getLookAndFeel().findColour (PopupMenu::backgroundColourId
).isOpaque() || ! Desktop::canUseSemiTransparentWindows());
285 for (int i
= 0; i
< menu
.items
.size(); ++i
)
286 items
.add (new PopupMenu::ItemComponent (*menu
.items
.getUnchecked(i
), standardItemHeight
, this));
288 calculateWindowPos (target
, alignToRectangle
);
289 setTopLeftPosition (windowPos
.getX(), windowPos
.getY());
292 if (itemIdThatMustBeVisible
!= 0)
294 const int y
= target
.getY() - windowPos
.getY();
295 ensureItemIsVisible (itemIdThatMustBeVisible
,
296 isPositiveAndBelow (y
, windowPos
.getHeight()) ? y
: -1);
299 resizeToBestWindowPos();
300 addToDesktop (ComponentPeer::windowIsTemporary
301 | ComponentPeer::windowIgnoresKeyPresses
302 | getLookAndFeel().getMenuWindowFlags());
304 getActiveWindows().add (this);
305 Desktop::getInstance().addGlobalMouseListener (this);
310 getActiveWindows().removeValue (this);
311 Desktop::getInstance().removeGlobalMouseListener (this);
312 activeSubMenu
= nullptr;
316 //==============================================================================
317 static Window
* create (const PopupMenu
& menu
,
318 bool dismissOnMouseUp
,
319 Window
* const owner_
,
320 const Rectangle
<int>& target
,
322 int maximumNumColumns
,
323 int standardItemHeight
,
324 bool alignToRectangle
,
325 int itemIdThatMustBeVisible
,
326 ApplicationCommandManager
** managerOfChosenCommand
,
327 Component
* componentAttachedTo
)
329 if (menu
.items
.size() > 0)
330 return new Window (menu
, owner_
, target
, alignToRectangle
, itemIdThatMustBeVisible
,
331 minimumWidth
, maximumNumColumns
, standardItemHeight
, dismissOnMouseUp
,
332 managerOfChosenCommand
, componentAttachedTo
);
337 //==============================================================================
338 void paint (Graphics
& g
)
341 g
.fillAll (Colours::white
);
343 getLookAndFeel().drawPopupMenuBackground (g
, getWidth(), getHeight());
346 void paintOverChildren (Graphics
& g
)
350 LookAndFeel
& lf
= getLookAndFeel();
352 if (isScrollZoneActive (false))
353 lf
.drawPopupMenuUpDownArrow (g
, getWidth(), PopupMenuSettings::scrollZone
, true);
355 if (isScrollZoneActive (true))
357 g
.setOrigin (0, getHeight() - PopupMenuSettings::scrollZone
);
358 lf
.drawPopupMenuUpDownArrow (g
, getWidth(), PopupMenuSettings::scrollZone
, false);
363 bool isScrollZoneActive (bool bottomOne
) const
366 && (bottomOne
? childYOffset
< contentHeight
- windowPos
.getHeight()
370 //==============================================================================
371 // hide this and all sub-comps
372 void hide (const PopupMenu::Item
* const item
, const bool makeInvisible
)
376 WeakReference
<Component
> deletionChecker (this);
378 activeSubMenu
= nullptr;
379 currentChild
= nullptr;
382 && item
->commandManager
!= nullptr
383 && item
->itemId
!= 0)
385 *managerOfChosenCommand
= item
->commandManager
;
388 exitModalState (item
!= nullptr ? item
->itemId
: 0);
390 if (makeInvisible
&& (deletionChecker
!= nullptr))
395 void dismissMenu (const PopupMenu::Item
* const item
)
397 if (owner
!= nullptr)
399 owner
->dismissMenu (item
);
405 // need a copy of this on the stack as the one passed in will get deleted during this call
406 const PopupMenu::Item
mi (*item
);
416 //==============================================================================
417 void mouseMove (const MouseEvent
&) { timerCallback(); }
418 void mouseDown (const MouseEvent
&) { timerCallback(); }
419 void mouseDrag (const MouseEvent
&) { timerCallback(); }
420 void mouseUp (const MouseEvent
&) { timerCallback(); }
422 void mouseWheelMove (const MouseEvent
&, float /*amountX*/, float amountY
)
424 alterChildYPos (roundToInt (-10.0f
* amountY
* PopupMenuSettings::scrollZone
));
425 lastMouse
= Point
<int> (-1, -1);
428 bool keyPressed (const KeyPress
& key
)
430 if (key
.isKeyCode (KeyPress::downKey
))
434 else if (key
.isKeyCode (KeyPress::upKey
))
438 else if (key
.isKeyCode (KeyPress::leftKey
))
440 if (owner
!= nullptr)
442 Component::SafePointer
<Window
> parentWindow (owner
);
443 PopupMenu::ItemComponent
* currentChildOfParent
= parentWindow
->currentChild
;
447 if (parentWindow
!= nullptr)
448 parentWindow
->setCurrentlyHighlightedChild (currentChildOfParent
);
450 disableTimerUntilMouseMoves();
452 else if (componentAttachedTo
!= nullptr)
454 componentAttachedTo
->keyPressed (key
);
457 else if (key
.isKeyCode (KeyPress::rightKey
))
459 disableTimerUntilMouseMoves();
461 if (showSubMenuFor (currentChild
))
463 if (activeSubMenu
!= nullptr && activeSubMenu
->isVisible())
464 activeSubMenu
->selectNextItem (1);
466 else if (componentAttachedTo
!= nullptr)
468 componentAttachedTo
->keyPressed (key
);
471 else if (key
.isKeyCode (KeyPress::returnKey
))
473 triggerCurrentlyHighlightedItem();
475 else if (key
.isKeyCode (KeyPress::escapeKey
))
487 void inputAttemptWhenModal()
489 WeakReference
<Component
> deletionChecker (this);
493 if (deletionChecker
!= nullptr && ! isOverAnyMenu())
495 if (componentAttachedTo
!= nullptr)
497 // we want to dismiss the menu, but if we do it synchronously, then
498 // the mouse-click will be allowed to pass through. That's good, except
499 // when the user clicks on the button that orginally popped the menu up,
500 // as they'll expect the menu to go away, and in fact it'll just
501 // come back. So only dismiss synchronously if they're not on the original
502 // comp that we're attached to.
503 const Point
<int> mousePos (componentAttachedTo
->getMouseXYRelative());
505 if (componentAttachedTo
->reallyContains (mousePos
, true))
507 postCommandMessage (PopupMenuSettings::dismissCommandId
); // dismiss asynchrounously
516 void handleCommandMessage (int commandId
)
518 Component::handleCommandMessage (commandId
);
520 if (commandId
== PopupMenuSettings::dismissCommandId
)
524 //==============================================================================
530 if (componentAttachedTo
!= componentAttachedToOriginal
)
536 Window
* currentlyModalWindow
= dynamic_cast <Window
*> (Component::getCurrentlyModalComponent());
538 if (currentlyModalWindow
!= nullptr && ! treeContains (currentlyModalWindow
))
541 startTimer (PopupMenuSettings::timerInterval
); // do this in case it was called from a mouse
542 // move rather than a real timer callback
544 const Point
<int> globalMousePos (Desktop::getMousePosition());
545 const Point
<int> localMousePos (getLocalPoint (nullptr, globalMousePos
));
547 const uint32 now
= Time::getMillisecondCounter();
549 if (now
> timeEnteredCurrentChildComp
+ 100
550 && reallyContains (localMousePos
, true)
551 && currentChild
!= nullptr
552 && (! disableMouseMoves
)
553 && ! (activeSubMenu
!= nullptr && activeSubMenu
->isVisible()))
555 showSubMenuFor (currentChild
);
558 if (globalMousePos
!= lastMouse
|| now
> lastMouseMoveTime
+ 350)
560 highlightItemUnderMouse (globalMousePos
, localMousePos
);
563 bool overScrollArea
= false;
566 && (isOver
|| (isDown
&& isPositiveAndBelow (localMousePos
.getX(), getWidth())))
567 && ((isScrollZoneActive (false) && localMousePos
.getY() < PopupMenuSettings::scrollZone
)
568 || (isScrollZoneActive (true) && localMousePos
.getY() > getHeight() - PopupMenuSettings::scrollZone
)))
570 if (now
> lastScroll
+ 20)
572 scrollAcceleration
= jmin (4.0, scrollAcceleration
* 1.04);
575 for (int i
= 0; i
< items
.size() && amount
== 0; ++i
)
576 amount
= ((int) scrollAcceleration
) * items
.getUnchecked(i
)->getHeight();
578 alterChildYPos (localMousePos
.getY() < PopupMenuSettings::scrollZone
? -amount
: amount
);
583 overScrollArea
= true;
584 lastMouse
= Point
<int> (-1, -1); // trigger a mouse-move
588 scrollAcceleration
= 1.0;
591 const bool wasDown
= isDown
;
592 bool isOverAny
= isOverAnyMenu();
594 if (hideOnExit
&& hasBeenOver
&& (! isOverAny
) && activeSubMenu
!= nullptr)
596 activeSubMenu
->updateMouseOverStatus (globalMousePos
);
597 isOverAny
= isOverAnyMenu();
600 if (hideOnExit
&& hasBeenOver
&& ! isOverAny
)
607 && (ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown()
608 || ModifierKeys::getCurrentModifiersRealtime().isAnyMouseButtonDown());
610 bool anyFocused
= Process::isForegroundProcess();
612 if (anyFocused
&& Component::getCurrentlyFocusedComponent() == nullptr)
614 // because no component at all may have focus, our test here will
615 // only be triggered when something has focus and then loses it.
616 anyFocused
= ! hasAnyJuceCompHadFocus
;
618 for (int i
= ComponentPeer::getNumPeers(); --i
>= 0;)
620 if (ComponentPeer::getPeer (i
)->isFocused())
623 hasAnyJuceCompHadFocus
= true;
631 if (now
> lastFocused
+ 10)
633 PopupMenuSettings::menuWasHiddenBecauseOfAppChange
= true;
636 return; // may have been deleted by the previous call..
639 else if (wasDown
&& now
> menuCreationTime
+ 250
640 && ! (isDown
|| overScrollArea
))
642 isOver
= reallyContains (localMousePos
, true);
646 triggerCurrentlyHighlightedItem();
648 else if ((hasBeenOver
|| ! dismissOnMouseUp
) && ! isOverAny
)
653 return; // may have been deleted by the previous calls..
662 static Array
<Window
*>& getActiveWindows()
664 static Array
<Window
*> activeMenuWindows
;
665 return activeMenuWindows
;
668 //==============================================================================
671 OwnedArray
<PopupMenu::ItemComponent
> items
;
672 Component::SafePointer
<PopupMenu::ItemComponent
> currentChild
;
673 ScopedPointer
<Window
> activeSubMenu
;
674 ApplicationCommandManager
** managerOfChosenCommand
;
675 WeakReference
<Component
> componentAttachedTo
;
676 Component
* componentAttachedToOriginal
;
677 Rectangle
<int> windowPos
;
678 Point
<int> lastMouse
;
679 int minimumWidth
, maximumNumColumns
, standardItemHeight
;
680 bool isOver
, hasBeenOver
, isDown
, needsToScroll
;
681 bool dismissOnMouseUp
, hideOnExit
, disableMouseMoves
, hasAnyJuceCompHadFocus
;
682 int numColumns
, contentHeight
, childYOffset
;
683 Array
<int> columnWidths
;
684 uint32 menuCreationTime
, lastFocused
, lastScroll
, lastMouseMoveTime
, timeEnteredCurrentChildComp
;
685 double scrollAcceleration
;
687 //==============================================================================
688 bool overlaps (const Rectangle
<int>& r
) const
690 return r
.intersects (getBounds())
691 || (owner
!= nullptr && owner
->overlaps (r
));
694 bool isOverAnyMenu() const
696 return owner
!= nullptr ? owner
->isOverAnyMenu()
700 bool isOverChildren() const
703 && (isOver
|| (activeSubMenu
!= nullptr && activeSubMenu
->isOverChildren()));
706 void updateMouseOverStatus (const Point
<int>& globalMousePos
)
708 const Point
<int> relPos (getLocalPoint (nullptr, globalMousePos
));
709 isOver
= reallyContains (relPos
, true);
711 if (activeSubMenu
!= nullptr)
712 activeSubMenu
->updateMouseOverStatus (globalMousePos
);
715 bool treeContains (const Window
* const window
) const noexcept
717 const Window
* mw
= this;
719 while (mw
->owner
!= nullptr)
722 while (mw
!= nullptr)
727 mw
= mw
->activeSubMenu
;
733 //==============================================================================
734 void calculateWindowPos (const Rectangle
<int>& target
, const bool alignToRectangle
)
736 const Rectangle
<int> mon (Desktop::getInstance()
737 .getMonitorAreaContaining (target
.getCentre(),
741 false)); // on windows, don't stop the menu overlapping the taskbar
744 const int maxMenuHeight
= mon
.getHeight() - 24;
746 int x
, y
, widthToUse
, heightToUse
;
747 layoutMenuItems (mon
.getWidth() - 24, maxMenuHeight
, widthToUse
, heightToUse
);
749 if (alignToRectangle
)
753 const int spaceUnder
= mon
.getHeight() - (target
.getBottom() - mon
.getY());
754 const int spaceOver
= target
.getY() - mon
.getY();
756 if (heightToUse
< spaceUnder
- 30 || spaceUnder
>= spaceOver
)
757 y
= target
.getBottom();
759 y
= target
.getY() - heightToUse
;
763 bool tendTowardsRight
= target
.getCentreX() < mon
.getCentreX();
765 if (owner
!= nullptr)
767 if (owner
->owner
!= nullptr)
769 const bool ownerGoingRight
= (owner
->getX() + owner
->getWidth() / 2
770 > owner
->owner
->getX() + owner
->owner
->getWidth() / 2);
772 if (ownerGoingRight
&& target
.getRight() + widthToUse
< mon
.getRight() - 4)
773 tendTowardsRight
= true;
774 else if ((! ownerGoingRight
) && target
.getX() > widthToUse
+ 4)
775 tendTowardsRight
= false;
777 else if (target
.getRight() + widthToUse
< mon
.getRight() - 32)
779 tendTowardsRight
= true;
783 const int biggestSpace
= jmax (mon
.getRight() - target
.getRight(),
784 target
.getX() - mon
.getX()) - 32;
786 if (biggestSpace
< widthToUse
)
788 layoutMenuItems (biggestSpace
+ target
.getWidth() / 3, maxMenuHeight
, widthToUse
, heightToUse
);
791 layoutMenuItems (biggestSpace
- 4, maxMenuHeight
, widthToUse
, heightToUse
);
793 tendTowardsRight
= (mon
.getRight() - target
.getRight()) >= (target
.getX() - mon
.getX());
796 if (tendTowardsRight
)
797 x
= jmin (mon
.getRight() - widthToUse
- 4, target
.getRight());
799 x
= jmax (mon
.getX() + 4, target
.getX() - widthToUse
);
802 if (target
.getCentreY() > mon
.getCentreY())
803 y
= jmax (mon
.getY(), target
.getBottom() - heightToUse
);
806 x
= jmax (mon
.getX() + 1, jmin (mon
.getRight() - (widthToUse
+ 6), x
));
807 y
= jmax (mon
.getY() + 1, jmin (mon
.getBottom() - (heightToUse
+ 6), y
));
809 windowPos
.setBounds (x
, y
, widthToUse
, heightToUse
);
811 // sets this flag if it's big enough to obscure any of its parent menus
812 hideOnExit
= owner
!= nullptr
813 && owner
->windowPos
.intersects (windowPos
.expanded (-4, -4));
816 void layoutMenuItems (const int maxMenuW
, const int maxMenuH
, int& width
, int& height
)
825 totalW
= workOutBestSize (maxMenuW
);
827 if (totalW
> maxMenuW
)
829 numColumns
= jmax (1, numColumns
- 1);
830 totalW
= workOutBestSize (maxMenuW
); // to update col widths
833 else if (totalW
> maxMenuW
/ 2 || contentHeight
< maxMenuH
)
838 } while (numColumns
< maximumNumColumns
);
840 const int actualH
= jmin (contentHeight
, maxMenuH
);
842 needsToScroll
= contentHeight
> actualH
;
844 width
= updateYPositions();
845 height
= actualH
+ PopupMenuSettings::borderSize
* 2;
848 int workOutBestSize (const int maxMenuW
)
854 for (int col
= 0; col
< numColumns
; ++col
)
856 int i
, colW
= standardItemHeight
, colH
= 0;
858 const int numChildren
= jmin (items
.size() - childNum
,
859 (items
.size() + numColumns
- 1) / numColumns
);
861 for (i
= numChildren
; --i
>= 0;)
863 colW
= jmax (colW
, items
.getUnchecked (childNum
+ i
)->getWidth());
864 colH
+= items
.getUnchecked (childNum
+ i
)->getHeight();
867 colW
= jmin (maxMenuW
/ jmax (1, numColumns
- 2), colW
+ PopupMenuSettings::borderSize
* 2);
869 columnWidths
.set (col
, colW
);
871 contentHeight
= jmax (contentHeight
, colH
);
873 childNum
+= numChildren
;
876 if (totalW
< minimumWidth
)
878 totalW
= minimumWidth
;
880 for (int col
= 0; col
< numColumns
; ++col
)
881 columnWidths
.set (0, totalW
/ numColumns
);
887 void ensureItemIsVisible (const int itemId
, int wantedY
)
889 jassert (itemId
!= 0)
891 for (int i
= items
.size(); --i
>= 0;)
893 PopupMenu::ItemComponent
* const m
= items
.getUnchecked(i
);
896 && m
->itemInfo
.itemId
== itemId
897 && windowPos
.getHeight() > PopupMenuSettings::scrollZone
* 4)
899 const int currentY
= m
->getY();
901 if (wantedY
> 0 || currentY
< 0 || m
->getBottom() > windowPos
.getHeight())
904 wantedY
= jlimit (PopupMenuSettings::scrollZone
,
905 jmax (PopupMenuSettings::scrollZone
,
906 windowPos
.getHeight() - (PopupMenuSettings::scrollZone
+ m
->getHeight())),
909 const Rectangle
<int> mon (Desktop::getInstance().getMonitorAreaContaining (windowPos
.getPosition(), true));
911 int deltaY
= wantedY
- currentY
;
913 windowPos
.setSize (jmin (windowPos
.getWidth(), mon
.getWidth()),
914 jmin (windowPos
.getHeight(), mon
.getHeight()));
916 const int newY
= jlimit (mon
.getY(),
917 mon
.getBottom() - windowPos
.getHeight(),
918 windowPos
.getY() + deltaY
);
920 deltaY
-= newY
- windowPos
.getY();
922 childYOffset
-= deltaY
;
923 windowPos
.setPosition (windowPos
.getX(), newY
);
933 void resizeToBestWindowPos()
935 Rectangle
<int> r (windowPos
);
937 if (childYOffset
< 0)
939 r
.setBounds (r
.getX(), r
.getY() - childYOffset
,
940 r
.getWidth(), r
.getHeight() + childYOffset
);
942 else if (childYOffset
> 0)
944 const int spaceAtBottom
= r
.getHeight() - (contentHeight
- childYOffset
);
946 if (spaceAtBottom
> 0)
947 r
.setSize (r
.getWidth(), r
.getHeight() - spaceAtBottom
);
954 void alterChildYPos (const int delta
)
958 childYOffset
+= delta
;
962 childYOffset
= jmax (childYOffset
, 0);
966 childYOffset
= jmin (childYOffset
,
967 contentHeight
- windowPos
.getHeight() + PopupMenuSettings::borderSize
);
977 resizeToBestWindowPos();
981 int updateYPositions()
986 for (int col
= 0; col
< numColumns
; ++col
)
988 const int numChildren
= jmin (items
.size() - childNum
,
989 (items
.size() + numColumns
- 1) / numColumns
);
991 const int colW
= columnWidths
[col
];
993 int y
= PopupMenuSettings::borderSize
- (childYOffset
+ (getY() - windowPos
.getY()));
995 for (int i
= 0; i
< numChildren
; ++i
)
997 Component
* const c
= items
.getUnchecked (childNum
+ i
);
998 c
->setBounds (x
, y
, colW
, c
->getHeight());
1003 childNum
+= numChildren
;
1009 bool isScrolling() const noexcept
1011 return childYOffset
!= 0 || needsToScroll
;
1014 void setCurrentlyHighlightedChild (PopupMenu::ItemComponent
* const child
)
1016 if (currentChild
!= nullptr)
1017 currentChild
->setHighlighted (false);
1019 currentChild
= child
;
1021 if (currentChild
!= nullptr)
1023 currentChild
->setHighlighted (true);
1024 timeEnteredCurrentChildComp
= Time::getApproximateMillisecondCounter();
1028 bool showSubMenuFor (PopupMenu::ItemComponent
* const childComp
)
1030 activeSubMenu
= nullptr;
1032 if (childComp
!= nullptr && childComp
->itemInfo
.hasActiveSubMenu())
1034 activeSubMenu
= Window::create (*(childComp
->itemInfo
.subMenu
),
1037 childComp
->getScreenBounds(),
1038 0, maximumNumColumns
,
1040 false, 0, managerOfChosenCommand
,
1041 componentAttachedTo
);
1043 if (activeSubMenu
!= nullptr)
1045 activeSubMenu
->setVisible (true);
1046 activeSubMenu
->enterModalState (false);
1047 activeSubMenu
->toFront (false);
1055 void highlightItemUnderMouse (const Point
<int>& globalMousePos
, const Point
<int>& localMousePos
)
1057 isOver
= reallyContains (localMousePos
, true);
1062 if (lastMouse
.getDistanceFrom (globalMousePos
) > 2)
1064 lastMouseMoveTime
= Time::getApproximateMillisecondCounter();
1066 if (disableMouseMoves
&& isOver
)
1067 disableMouseMoves
= false;
1070 if (disableMouseMoves
|| (activeSubMenu
!= nullptr && activeSubMenu
->isOverChildren()))
1073 bool isMovingTowardsMenu
= false;
1075 if (isOver
&& (activeSubMenu
!= nullptr) && globalMousePos
!= lastMouse
)
1077 // try to intelligently guess whether the user is moving the mouse towards a currently-open
1078 // submenu. To do this, look at whether the mouse stays inside a triangular region that
1079 // extends from the last mouse pos to the submenu's rectangle..
1081 float subX
= (float) activeSubMenu
->getScreenX();
1083 if (activeSubMenu
->getX() > getX())
1085 lastMouse
-= Point
<int> (2, 0); // to enlarge the triangle a bit, in case the mouse only moves a couple of pixels
1089 lastMouse
+= Point
<int> (2, 0);
1090 subX
+= activeSubMenu
->getWidth();
1093 Path areaTowardsSubMenu
;
1094 areaTowardsSubMenu
.addTriangle ((float) lastMouse
.getX(), (float) lastMouse
.getY(),
1095 subX
, (float) activeSubMenu
->getScreenY(),
1096 subX
, (float) (activeSubMenu
->getScreenY() + activeSubMenu
->getHeight()));
1098 isMovingTowardsMenu
= areaTowardsSubMenu
.contains ((float) globalMousePos
.getX(), (float) globalMousePos
.getY());
1101 lastMouse
= globalMousePos
;
1103 if (! isMovingTowardsMenu
)
1105 Component
* c
= getComponentAt (localMousePos
.getX(), localMousePos
.getY());
1109 PopupMenu::ItemComponent
* mic
= dynamic_cast <PopupMenu::ItemComponent
*> (c
);
1111 if (mic
== nullptr && c
!= nullptr)
1112 mic
= c
->findParentComponentOfClass ((PopupMenu::ItemComponent
*) nullptr);
1114 if (mic
!= currentChild
1115 && (isOver
|| (activeSubMenu
== nullptr) || ! activeSubMenu
->isVisible()))
1117 if (isOver
&& (c
!= nullptr) && (activeSubMenu
!= nullptr))
1118 activeSubMenu
->hide (0, true);
1123 setCurrentlyHighlightedChild (mic
);
1128 void triggerCurrentlyHighlightedItem()
1130 if (currentChild
!= nullptr
1131 && currentChild
->itemInfo
.canBeTriggered()
1132 && (currentChild
->itemInfo
.customComp
== nullptr
1133 || currentChild
->itemInfo
.customComp
->isTriggeredAutomatically()))
1135 dismissMenu (¤tChild
->itemInfo
);
1139 void selectNextItem (const int delta
)
1141 disableTimerUntilMouseMoves();
1142 PopupMenu::ItemComponent
* mic
= nullptr;
1143 bool wasLastOne
= (currentChild
== nullptr);
1144 const int numItems
= items
.size();
1146 for (int i
= 0; i
< numItems
+ 1; ++i
)
1148 int index
= (delta
> 0) ? i
: (numItems
- 1 - i
);
1149 index
= (index
+ numItems
) % numItems
;
1151 mic
= items
.getUnchecked (index
);
1153 if (mic
!= nullptr && (mic
->itemInfo
.canBeTriggered() || mic
->itemInfo
.hasActiveSubMenu())
1157 if (mic
== currentChild
)
1161 setCurrentlyHighlightedChild (mic
);
1164 void disableTimerUntilMouseMoves()
1166 disableMouseMoves
= true;
1168 if (owner
!= nullptr)
1169 owner
->disableTimerUntilMouseMoves();
1172 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Window
);
1176 //==============================================================================
1177 PopupMenu::PopupMenu()
1178 : lookAndFeel (nullptr),
1179 separatorPending (false)
1183 PopupMenu::PopupMenu (const PopupMenu
& other
)
1184 : lookAndFeel (other
.lookAndFeel
),
1185 separatorPending (false)
1187 items
.addCopiesOf (other
.items
);
1190 PopupMenu
& PopupMenu::operator= (const PopupMenu
& other
)
1194 lookAndFeel
= other
.lookAndFeel
;
1197 items
.addCopiesOf (other
.items
);
1203 PopupMenu::~PopupMenu()
1208 void PopupMenu::clear()
1211 separatorPending
= false;
1214 void PopupMenu::addSeparatorIfPending()
1216 if (separatorPending
)
1218 separatorPending
= false;
1220 if (items
.size() > 0)
1221 items
.add (new Item());
1225 void PopupMenu::addItem (const int itemResultId
, const String
& itemText
,
1226 const bool isActive
, const bool isTicked
, const Image
& iconToUse
)
1228 jassert (itemResultId
!= 0); // 0 is used as a return value to indicate that the user
1229 // didn't pick anything, so you shouldn't use it as the id
1232 addSeparatorIfPending();
1234 items
.add (new Item (itemResultId
, itemText
, isActive
, isTicked
, iconToUse
,
1235 Colours::black
, false, 0, 0, 0));
1238 void PopupMenu::addCommandItem (ApplicationCommandManager
* commandManager
,
1239 const int commandID
,
1240 const String
& displayName
)
1242 jassert (commandManager
!= nullptr && commandID
!= 0);
1244 const ApplicationCommandInfo
* const registeredInfo
= commandManager
->getCommandForID (commandID
);
1246 if (registeredInfo
!= nullptr)
1248 ApplicationCommandInfo
info (*registeredInfo
);
1249 ApplicationCommandTarget
* const target
= commandManager
->getTargetForCommand (commandID
, info
);
1251 addSeparatorIfPending();
1253 items
.add (new Item (commandID
,
1254 displayName
.isNotEmpty() ? displayName
1256 target
!= nullptr && (info
.flags
& ApplicationCommandInfo::isDisabled
) == 0,
1257 (info
.flags
& ApplicationCommandInfo::isTicked
) != 0,
1266 void PopupMenu::addColouredItem (const int itemResultId
,
1267 const String
& itemText
,
1268 const Colour
& itemTextColour
,
1269 const bool isActive
,
1270 const bool isTicked
,
1271 const Image
& iconToUse
)
1273 jassert (itemResultId
!= 0); // 0 is used as a return value to indicate that the user
1274 // didn't pick anything, so you shouldn't use it as the id
1277 addSeparatorIfPending();
1279 items
.add (new Item (itemResultId
, itemText
, isActive
, isTicked
, iconToUse
,
1280 itemTextColour
, true, 0, 0, 0));
1283 //==============================================================================
1284 void PopupMenu::addCustomItem (const int itemResultId
, CustomComponent
* const customComponent
)
1286 jassert (itemResultId
!= 0); // 0 is used as a return value to indicate that the user
1287 // didn't pick anything, so you shouldn't use it as the id
1290 addSeparatorIfPending();
1292 items
.add (new Item (itemResultId
, String::empty
, true, false, Image::null
,
1293 Colours::black
, false, customComponent
, 0, 0));
1296 class NormalComponentWrapper
: public PopupMenu::CustomComponent
1299 NormalComponentWrapper (Component
* const comp
, const int w
, const int h
,
1300 const bool triggerMenuItemAutomaticallyWhenClicked
)
1301 : PopupMenu::CustomComponent (triggerMenuItemAutomaticallyWhenClicked
),
1302 width (w
), height (h
)
1304 addAndMakeVisible (comp
);
1307 void getIdealSize (int& idealWidth
, int& idealHeight
)
1310 idealHeight
= height
;
1315 if (getChildComponent(0) != nullptr)
1316 getChildComponent(0)->setBounds (getLocalBounds());
1320 const int width
, height
;
1322 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NormalComponentWrapper
);
1325 void PopupMenu::addCustomItem (const int itemResultId
,
1326 Component
* customComponent
,
1327 int idealWidth
, int idealHeight
,
1328 const bool triggerMenuItemAutomaticallyWhenClicked
)
1330 addCustomItem (itemResultId
,
1331 new NormalComponentWrapper (customComponent
, idealWidth
, idealHeight
,
1332 triggerMenuItemAutomaticallyWhenClicked
));
1335 //==============================================================================
1336 void PopupMenu::addSubMenu (const String
& subMenuName
,
1337 const PopupMenu
& subMenu
,
1338 const bool isActive
,
1339 const Image
& iconToUse
,
1340 const bool isTicked
)
1342 addSeparatorIfPending();
1344 items
.add (new Item (0, subMenuName
, isActive
&& (subMenu
.getNumItems() > 0), isTicked
,
1345 iconToUse
, Colours::black
, false, 0, &subMenu
, 0));
1348 void PopupMenu::addSeparator()
1350 separatorPending
= true;
1354 //==============================================================================
1355 class HeaderItemComponent
: public PopupMenu::CustomComponent
1358 HeaderItemComponent (const String
& name
)
1359 : PopupMenu::CustomComponent (false)
1364 void paint (Graphics
& g
)
1366 Font
f (getLookAndFeel().getPopupMenuFont());
1369 g
.setColour (findColour (PopupMenu::headerTextColourId
));
1371 g
.drawFittedText (getName(),
1372 12, 0, getWidth() - 16, proportionOfHeight (0.8f
),
1373 Justification::bottomLeft
, 1);
1376 void getIdealSize (int& idealWidth
, int& idealHeight
)
1378 getLookAndFeel().getIdealPopupMenuItemSize (getName(), false, -1, idealWidth
, idealHeight
);
1379 idealHeight
+= idealHeight
/ 2;
1380 idealWidth
+= idealWidth
/ 4;
1384 JUCE_LEAK_DETECTOR (HeaderItemComponent
);
1387 void PopupMenu::addSectionHeader (const String
& title
)
1389 addCustomItem (0X4734a34f, new HeaderItemComponent (title
));
1392 //==============================================================================
1393 PopupMenu::Options::Options()
1394 : targetComponent (nullptr),
1400 targetArea
.setPosition (Desktop::getMousePosition());
1403 const PopupMenu::Options
PopupMenu::Options::withTargetComponent (Component
* comp
) const
1406 o
.targetComponent
= comp
;
1408 if (comp
!= nullptr)
1409 o
.targetArea
= comp
->getScreenBounds();
1414 const PopupMenu::Options
PopupMenu::Options::withTargetScreenArea (const Rectangle
<int>& area
) const
1417 o
.targetArea
= area
;
1421 const PopupMenu::Options
PopupMenu::Options::withMinimumWidth (int w
) const
1428 const PopupMenu::Options
PopupMenu::Options::withMaximumNumColumns (int cols
) const
1431 o
.maxColumns
= cols
;
1435 const PopupMenu::Options
PopupMenu::Options::withStandardItemHeight (int height
) const
1438 o
.standardHeight
= height
;
1442 const PopupMenu::Options
PopupMenu::Options::withItemThatMustBeVisible (int idOfItemToBeVisible
) const
1445 o
.visibleItemID
= idOfItemToBeVisible
;
1449 Component
* PopupMenu::createWindow (const Options
& options
,
1450 ApplicationCommandManager
** managerOfChosenCommand
) const
1452 return Window::create (*this, ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown(),
1453 0, options
.targetArea
, options
.minWidth
, options
.maxColumns
> 0 ? options
.maxColumns
: 7,
1454 options
.standardHeight
, ! options
.targetArea
.isEmpty(), options
.visibleItemID
,
1455 managerOfChosenCommand
, options
.targetComponent
);
1458 //==============================================================================
1459 // This invokes any command manager commands and deletes the menu window when it is dismissed
1460 class PopupMenuCompletionCallback
: public ModalComponentManager::Callback
1463 PopupMenuCompletionCallback()
1464 : managerOfChosenCommand (nullptr),
1465 prevFocused (Component::getCurrentlyFocusedComponent()),
1466 prevTopLevel (prevFocused
!= nullptr ? prevFocused
->getTopLevelComponent() : 0)
1468 PopupMenuSettings::menuWasHiddenBecauseOfAppChange
= false;
1471 void modalStateFinished (int result
)
1473 if (managerOfChosenCommand
!= nullptr && result
!= 0)
1475 ApplicationCommandTarget::InvocationInfo
info (result
);
1476 info
.invocationMethod
= ApplicationCommandTarget::InvocationInfo::fromMenu
;
1478 managerOfChosenCommand
->invoke (info
, true);
1481 // (this would be the place to fade out the component, if that's what's required)
1482 component
= nullptr;
1484 if (! PopupMenuSettings::menuWasHiddenBecauseOfAppChange
)
1486 if (prevTopLevel
!= nullptr)
1487 prevTopLevel
->toFront (true);
1489 if (prevFocused
!= nullptr)
1490 prevFocused
->grabKeyboardFocus();
1494 ApplicationCommandManager
* managerOfChosenCommand
;
1495 ScopedPointer
<Component
> component
;
1496 WeakReference
<Component
> prevFocused
, prevTopLevel
;
1499 JUCE_DECLARE_NON_COPYABLE (PopupMenuCompletionCallback
);
1502 int PopupMenu::showWithOptionalCallback (const Options
& options
, ModalComponentManager::Callback
* const userCallback
,
1503 const bool canBeModal
)
1505 ScopedPointer
<ModalComponentManager::Callback
> userCallbackDeleter (userCallback
);
1506 ScopedPointer
<PopupMenuCompletionCallback
> callback (new PopupMenuCompletionCallback());
1508 Component
* window
= createWindow (options
, &(callback
->managerOfChosenCommand
));
1509 if (window
== nullptr)
1512 callback
->component
= window
;
1514 window
->enterModalState (false, userCallbackDeleter
.release());
1515 ModalComponentManager::getInstance()->attachCallback (window
, callback
.release());
1517 window
->toFront (false); // need to do this after making it modal, or it could
1518 // be stuck behind other comps that are already modal..
1520 #if JUCE_MODAL_LOOPS_PERMITTED
1521 return (userCallback
== nullptr && canBeModal
) ? window
->runModalLoop() : 0;
1523 jassert (userCallback
!= nullptr && canBeModal
);
1528 //==============================================================================
1529 #if JUCE_MODAL_LOOPS_PERMITTED
1530 int PopupMenu::showMenu (const Options
& options
)
1532 return showWithOptionalCallback (options
, 0, true);
1536 void PopupMenu::showMenuAsync (const Options
& options
, ModalComponentManager::Callback
* userCallback
)
1538 #if ! JUCE_MODAL_LOOPS_PERMITTED
1539 jassert (userCallback
!= nullptr);
1542 showWithOptionalCallback (options
, userCallback
, false);
1545 //==============================================================================
1546 #if JUCE_MODAL_LOOPS_PERMITTED
1547 int PopupMenu::show (const int itemIdThatMustBeVisible
,
1548 const int minimumWidth
, const int maximumNumColumns
,
1549 const int standardItemHeight
,
1550 ModalComponentManager::Callback
* callback
)
1552 return showWithOptionalCallback (Options().withItemThatMustBeVisible (itemIdThatMustBeVisible
)
1553 .withMinimumWidth (minimumWidth
)
1554 .withMaximumNumColumns (maximumNumColumns
)
1555 .withStandardItemHeight (standardItemHeight
),
1559 int PopupMenu::showAt (const Rectangle
<int>& screenAreaToAttachTo
,
1560 const int itemIdThatMustBeVisible
,
1561 const int minimumWidth
, const int maximumNumColumns
,
1562 const int standardItemHeight
,
1563 ModalComponentManager::Callback
* callback
)
1565 return showWithOptionalCallback (Options().withTargetScreenArea (screenAreaToAttachTo
)
1566 .withItemThatMustBeVisible (itemIdThatMustBeVisible
)
1567 .withMinimumWidth (minimumWidth
)
1568 .withMaximumNumColumns (maximumNumColumns
)
1569 .withStandardItemHeight (standardItemHeight
),
1573 int PopupMenu::showAt (Component
* componentToAttachTo
,
1574 const int itemIdThatMustBeVisible
,
1575 const int minimumWidth
, const int maximumNumColumns
,
1576 const int standardItemHeight
,
1577 ModalComponentManager::Callback
* callback
)
1579 Options
options (Options().withItemThatMustBeVisible (itemIdThatMustBeVisible
)
1580 .withMinimumWidth (minimumWidth
)
1581 .withMaximumNumColumns (maximumNumColumns
)
1582 .withStandardItemHeight (standardItemHeight
));
1584 if (componentToAttachTo
!= nullptr)
1585 options
= options
.withTargetComponent (componentToAttachTo
);
1587 return showWithOptionalCallback (options
, callback
, true);
1591 bool JUCE_CALLTYPE
PopupMenu::dismissAllActiveMenus()
1593 const int numWindows
= Window::getActiveWindows().size();
1594 for (int i
= numWindows
; --i
>= 0;)
1596 Window
* const pmw
= Window::getActiveWindows()[i
];
1599 pmw
->dismissMenu (0);
1602 return numWindows
> 0;
1605 //==============================================================================
1606 int PopupMenu::getNumItems() const noexcept
1610 for (int i
= items
.size(); --i
>= 0;)
1611 if (! items
.getUnchecked(i
)->isSeparator
)
1617 bool PopupMenu::containsCommandItem (const int commandID
) const
1619 for (int i
= items
.size(); --i
>= 0;)
1621 const Item
* mi
= items
.getUnchecked (i
);
1623 if ((mi
->itemId
== commandID
&& mi
->commandManager
!= nullptr)
1624 || (mi
->subMenu
!= nullptr && mi
->subMenu
->containsCommandItem (commandID
)))
1633 bool PopupMenu::containsAnyActiveItems() const noexcept
1635 for (int i
= items
.size(); --i
>= 0;)
1637 const Item
* const mi
= items
.getUnchecked (i
);
1639 if (mi
->subMenu
!= nullptr)
1641 if (mi
->subMenu
->containsAnyActiveItems())
1644 else if (mi
->active
)
1653 void PopupMenu::setLookAndFeel (LookAndFeel
* const newLookAndFeel
)
1655 lookAndFeel
= newLookAndFeel
;
1658 //==============================================================================
1659 PopupMenu::CustomComponent::CustomComponent (const bool isTriggeredAutomatically_
)
1660 : isHighlighted (false),
1661 triggeredAutomatically (isTriggeredAutomatically_
)
1665 PopupMenu::CustomComponent::~CustomComponent()
1669 void PopupMenu::CustomComponent::setHighlighted (bool shouldBeHighlighted
)
1671 isHighlighted
= shouldBeHighlighted
;
1675 void PopupMenu::CustomComponent::triggerMenuItem()
1677 PopupMenu::ItemComponent
* const mic
= dynamic_cast <PopupMenu::ItemComponent
*> (getParentComponent());
1681 PopupMenu::Window
* const pmw
= dynamic_cast <PopupMenu::Window
*> (mic
->getParentComponent());
1685 pmw
->dismissMenu (&mic
->itemInfo
);
1689 // something must have gone wrong with the component hierarchy if this happens..
1695 // why isn't this component inside a menu? Not much point triggering the item if
1701 //==============================================================================
1702 PopupMenu::MenuItemIterator::MenuItemIterator (const PopupMenu
& menu_
)
1703 : subMenu (nullptr),
1705 isSeparator (false),
1708 isCustomComponent (false),
1709 isSectionHeader (false),
1716 PopupMenu::MenuItemIterator::~MenuItemIterator()
1720 bool PopupMenu::MenuItemIterator::next()
1722 if (index
>= menu
.items
.size())
1725 const Item
* const item
= menu
.items
.getUnchecked (index
);
1728 itemName
= item
->customComp
!= nullptr ? item
->customComp
->getName() : item
->text
;
1729 subMenu
= item
->subMenu
;
1730 itemId
= item
->itemId
;
1732 isSeparator
= item
->isSeparator
;
1733 isTicked
= item
->isTicked
;
1734 isEnabled
= item
->active
;
1735 isSectionHeader
= dynamic_cast <HeaderItemComponent
*> (static_cast <CustomComponent
*> (item
->customComp
)) != nullptr;
1736 isCustomComponent
= (! isSectionHeader
) && item
->customComp
!= nullptr;
1737 customColour
= item
->usesColour
? &(item
->textColour
) : 0;
1738 customImage
= item
->image
;
1739 commandManager
= item
->commandManager
;