Add remaining files
[juce-lv2.git] / juce / source / src / gui / components / menus / juce_PopupMenu.cpp
blob595005d861f6b9993f73d84da6094c51abe08785
1 /*
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"
28 BEGIN_JUCE_NAMESPACE
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 //==============================================================================
42 class PopupMenu::Item
44 public:
45 //==============================================================================
46 Item()
47 : itemId (0), active (true), isSeparator (true), isTicked (false),
48 usesColour (false), commandManager (nullptr)
52 Item (const int itemId_,
53 const String& text_,
54 const bool active_,
55 const bool isTicked_,
56 const Image& im,
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)
72 String shortcutKey;
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())
82 shortcutKey << ", ";
84 if (key.length() == 1 && key[0] < 128)
85 shortcutKey << "shortcut: '" << key << '\'';
86 else
87 shortcutKey << key;
90 shortcutKey = shortcutKey.trim();
92 if (shortcutKey.isNotEmpty())
93 text << "<end>" << shortcutKey;
97 Item (const Item& other)
98 : itemId (other.itemId),
99 text (other.text),
100 textColour (other.textColour),
101 active (other.active),
102 isSeparator (other.isSeparator),
103 isTicked (other.isTicked),
104 usesColour (other.usesColour),
105 image (other.image),
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 //==============================================================================
117 const int itemId;
118 String text;
119 const Colour textColour;
120 const bool active, isSeparator, isTicked, usesColour;
121 Image image;
122 ReferenceCountedObjectPtr <CustomComponent> customComp;
123 ScopedPointer <PopupMenu> subMenu;
124 ApplicationCommandManager* const commandManager;
126 private:
127 Item& operator= (const Item&);
129 JUCE_LEAK_DETECTOR (Item);
133 //==============================================================================
134 class PopupMenu::ItemComponent : public Component
136 public:
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);
147 int itemW = 80;
148 int itemH = 16;
149 getIdealSize (itemW, itemH, standardItemHeight);
150 setSize (itemW, jlimit (2, 600, itemH));
152 addMouseListener (parent, false);
155 ~ItemComponent()
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);
165 else
166 getLookAndFeel().getIdealPopupMenuItemSize (itemInfo.text,
167 itemInfo.isSeparator,
168 standardItemHeight,
169 idealWidth, idealHeight);
172 void paint (Graphics& g)
174 if (itemInfo.customComp == nullptr)
176 String mainText (itemInfo.text);
177 String endText;
178 const int endIndex = mainText.indexOf ("<end>");
180 if (endIndex >= 0)
182 endText = mainText.substring (endIndex + 5).trim();
183 mainText = mainText.substring (0, endIndex);
186 getLookAndFeel()
187 .drawPopupMenuItem (g, getWidth(), getHeight(),
188 itemInfo.isSeparator,
189 itemInfo.active,
190 isHighlighted,
191 itemInfo.isTicked,
192 itemInfo.subMenu != 0,
193 mainText, endText,
194 itemInfo.image.isValid() ? &itemInfo.image : 0,
195 itemInfo.usesColour ? &(itemInfo.textColour) : 0);
199 void resized()
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);
216 repaint();
220 PopupMenu::Item itemInfo;
222 private:
223 bool isHighlighted;
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,
242 private Timer
244 public:
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"),
253 owner (owner_),
254 activeSubMenu (nullptr),
255 managerOfChosenCommand (managerOfChosenCommand_),
256 componentAttachedTo (componentAttachedTo_),
257 componentAttachedToOriginal (componentAttachedTo_),
258 minimumWidth (minimumWidth_),
259 maximumNumColumns (maximumNumColumns_),
260 standardItemHeight (standardItemHeight_),
261 isOver (false),
262 hasBeenOver (false),
263 isDown (false),
264 needsToScroll (false),
265 dismissOnMouseUp (dismissOnMouseUp_),
266 hideOnExit (false),
267 disableMouseMoves (false),
268 hasAnyJuceCompHadFocus (false),
269 numColumns (0),
270 contentHeight (0),
271 childYOffset (0),
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());
290 updateYPositions();
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);
308 ~Window()
310 getActiveWindows().removeValue (this);
311 Desktop::getInstance().removeGlobalMouseListener (this);
312 activeSubMenu = nullptr;
313 items.clear();
316 //==============================================================================
317 static Window* create (const PopupMenu& menu,
318 bool dismissOnMouseUp,
319 Window* const owner_,
320 const Rectangle<int>& target,
321 int minimumWidth,
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);
334 return nullptr;
337 //==============================================================================
338 void paint (Graphics& g)
340 if (isOpaque())
341 g.fillAll (Colours::white);
343 getLookAndFeel().drawPopupMenuBackground (g, getWidth(), getHeight());
346 void paintOverChildren (Graphics& g)
348 if (isScrolling())
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
365 return isScrolling()
366 && (bottomOne ? childYOffset < contentHeight - windowPos.getHeight()
367 : childYOffset > 0);
370 //==============================================================================
371 // hide this and all sub-comps
372 void hide (const PopupMenu::Item* const item, const bool makeInvisible)
374 if (isVisible())
376 WeakReference<Component> deletionChecker (this);
378 activeSubMenu = nullptr;
379 currentChild = nullptr;
381 if (item != 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))
391 setVisible (false);
395 void dismissMenu (const PopupMenu::Item* const item)
397 if (owner != nullptr)
399 owner->dismissMenu (item);
401 else
403 if (item != nullptr)
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);
407 hide (&mi, false);
409 else
411 hide (0, false);
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))
432 selectNextItem (1);
434 else if (key.isKeyCode (KeyPress::upKey))
436 selectNextItem (-1);
438 else if (key.isKeyCode (KeyPress::leftKey))
440 if (owner != nullptr)
442 Component::SafePointer<Window> parentWindow (owner);
443 PopupMenu::ItemComponent* currentChildOfParent = parentWindow->currentChild;
445 hide (0, true);
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))
477 dismissMenu (0);
479 else
481 return false;
484 return true;
487 void inputAttemptWhenModal()
489 WeakReference<Component> deletionChecker (this);
491 timerCallback();
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
508 return;
512 dismissMenu (0);
516 void handleCommandMessage (int commandId)
518 Component::handleCommandMessage (commandId);
520 if (commandId == PopupMenuSettings::dismissCommandId)
521 dismissMenu (0);
524 //==============================================================================
525 void timerCallback()
527 if (! isVisible())
528 return;
530 if (componentAttachedTo != componentAttachedToOriginal)
532 dismissMenu (0);
533 return;
536 Window* currentlyModalWindow = dynamic_cast <Window*> (Component::getCurrentlyModalComponent());
538 if (currentlyModalWindow != nullptr && ! treeContains (currentlyModalWindow))
539 return;
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;
565 if (isScrolling()
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);
573 int amount = 0;
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);
580 lastScroll = now;
583 overScrollArea = true;
584 lastMouse = Point<int> (-1, -1); // trigger a mouse-move
586 else
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)
602 hide (0, true);
604 else
606 isDown = hasBeenOver
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())
622 anyFocused = true;
623 hasAnyJuceCompHadFocus = true;
624 break;
629 if (! anyFocused)
631 if (now > lastFocused + 10)
633 PopupMenuSettings::menuWasHiddenBecauseOfAppChange = true;
634 dismissMenu (0);
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);
644 if (isOver)
646 triggerCurrentlyHighlightedItem();
648 else if ((hasBeenOver || ! dismissOnMouseUp) && ! isOverAny)
650 dismissMenu (0);
653 return; // may have been deleted by the previous calls..
655 else
657 lastFocused = now;
662 static Array<Window*>& getActiveWindows()
664 static Array<Window*> activeMenuWindows;
665 return activeMenuWindows;
668 //==============================================================================
669 private:
670 Window* owner;
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()
697 : isOverChildren();
700 bool isOverChildren() const
702 return isVisible()
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)
720 mw = mw->owner;
722 while (mw != nullptr)
724 if (mw == window)
725 return true;
727 mw = mw->activeSubMenu;
730 return false;
733 //==============================================================================
734 void calculateWindowPos (const Rectangle<int>& target, const bool alignToRectangle)
736 const Rectangle<int> mon (Desktop::getInstance()
737 .getMonitorAreaContaining (target.getCentre(),
738 #if JUCE_MAC
739 true));
740 #else
741 false)); // on windows, don't stop the menu overlapping the taskbar
742 #endif
744 const int maxMenuHeight = mon.getHeight() - 24;
746 int x, y, widthToUse, heightToUse;
747 layoutMenuItems (mon.getWidth() - 24, maxMenuHeight, widthToUse, heightToUse);
749 if (alignToRectangle)
751 x = target.getX();
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();
758 else
759 y = target.getY() - heightToUse;
761 else
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);
790 if (numColumns > 1)
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());
798 else
799 x = jmax (mon.getX() + 4, target.getX() - widthToUse);
801 y = target.getY();
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)
818 numColumns = 0;
819 contentHeight = 0;
820 int totalW;
824 ++numColumns;
825 totalW = workOutBestSize (maxMenuW);
827 if (totalW > maxMenuW)
829 numColumns = jmax (1, numColumns - 1);
830 totalW = workOutBestSize (maxMenuW); // to update col widths
831 break;
833 else if (totalW > maxMenuW / 2 || contentHeight < maxMenuH)
835 break;
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)
850 int totalW = 0;
851 contentHeight = 0;
852 int childNum = 0;
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);
870 totalW += 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);
884 return totalW;
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);
895 if (m != nullptr
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())
903 if (wantedY < 0)
904 wantedY = jlimit (PopupMenuSettings::scrollZone,
905 jmax (PopupMenuSettings::scrollZone,
906 windowPos.getHeight() - (PopupMenuSettings::scrollZone + m->getHeight())),
907 currentY);
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);
925 updateYPositions();
928 break;
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);
950 setBounds (r);
951 updateYPositions();
954 void alterChildYPos (const int delta)
956 if (isScrolling())
958 childYOffset += delta;
960 if (delta < 0)
962 childYOffset = jmax (childYOffset, 0);
964 else if (delta > 0)
966 childYOffset = jmin (childYOffset,
967 contentHeight - windowPos.getHeight() + PopupMenuSettings::borderSize);
970 updateYPositions();
972 else
974 childYOffset = 0;
977 resizeToBestWindowPos();
978 repaint();
981 int updateYPositions()
983 int x = 0;
984 int childNum = 0;
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());
999 y += c->getHeight();
1002 x += colW;
1003 childNum += numChildren;
1006 return x;
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),
1035 dismissOnMouseUp,
1036 this,
1037 childComp->getScreenBounds(),
1038 0, maximumNumColumns,
1039 standardItemHeight,
1040 false, 0, managerOfChosenCommand,
1041 componentAttachedTo);
1043 if (activeSubMenu != nullptr)
1045 activeSubMenu->setVisible (true);
1046 activeSubMenu->enterModalState (false);
1047 activeSubMenu->toFront (false);
1048 return true;
1052 return false;
1055 void highlightItemUnderMouse (const Point<int>& globalMousePos, const Point<int>& localMousePos)
1057 isOver = reallyContains (localMousePos, true);
1059 if (isOver)
1060 hasBeenOver = 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()))
1071 return;
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
1087 else
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());
1106 if (c == this)
1107 c = nullptr;
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);
1120 if (! isOver)
1121 mic = nullptr;
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 (&currentChild->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())
1154 && wasLastOne)
1155 break;
1157 if (mic == currentChild)
1158 wasLastOne = true;
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)
1192 if (this != &other)
1194 lookAndFeel = other.lookAndFeel;
1196 clear();
1197 items.addCopiesOf (other.items);
1200 return *this;
1203 PopupMenu::~PopupMenu()
1205 clear();
1208 void PopupMenu::clear()
1210 items.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
1230 // for an item..
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
1255 : info.shortName,
1256 target != nullptr && (info.flags & ApplicationCommandInfo::isDisabled) == 0,
1257 (info.flags & ApplicationCommandInfo::isTicked) != 0,
1258 Image::null,
1259 Colours::black,
1260 false,
1261 0, 0,
1262 commandManager));
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
1275 // for an item..
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
1288 // for an item..
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
1298 public:
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)
1309 idealWidth = width;
1310 idealHeight = height;
1313 void resized()
1315 if (getChildComponent(0) != nullptr)
1316 getChildComponent(0)->setBounds (getLocalBounds());
1319 private:
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
1357 public:
1358 HeaderItemComponent (const String& name)
1359 : PopupMenu::CustomComponent (false)
1361 setName (name);
1364 void paint (Graphics& g)
1366 Font f (getLookAndFeel().getPopupMenuFont());
1367 f.setBold (true);
1368 g.setFont (f);
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;
1383 private:
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),
1395 visibleItemID (0),
1396 minWidth (0),
1397 maxColumns (0),
1398 standardHeight (0)
1400 targetArea.setPosition (Desktop::getMousePosition());
1403 const PopupMenu::Options PopupMenu::Options::withTargetComponent (Component* comp) const
1405 Options o (*this);
1406 o.targetComponent = comp;
1408 if (comp != nullptr)
1409 o.targetArea = comp->getScreenBounds();
1411 return o;
1414 const PopupMenu::Options PopupMenu::Options::withTargetScreenArea (const Rectangle<int>& area) const
1416 Options o (*this);
1417 o.targetArea = area;
1418 return o;
1421 const PopupMenu::Options PopupMenu::Options::withMinimumWidth (int w) const
1423 Options o (*this);
1424 o.minWidth = w;
1425 return o;
1428 const PopupMenu::Options PopupMenu::Options::withMaximumNumColumns (int cols) const
1430 Options o (*this);
1431 o.maxColumns = cols;
1432 return o;
1435 const PopupMenu::Options PopupMenu::Options::withStandardItemHeight (int height) const
1437 Options o (*this);
1438 o.standardHeight = height;
1439 return o;
1442 const PopupMenu::Options PopupMenu::Options::withItemThatMustBeVisible (int idOfItemToBeVisible) const
1444 Options o (*this);
1445 o.visibleItemID = idOfItemToBeVisible;
1446 return o;
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
1462 public:
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;
1498 private:
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)
1510 return 0;
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;
1522 #else
1523 jassert (userCallback != nullptr && canBeModal);
1524 return 0;
1525 #endif
1528 //==============================================================================
1529 #if JUCE_MODAL_LOOPS_PERMITTED
1530 int PopupMenu::showMenu (const Options& options)
1532 return showWithOptionalCallback (options, 0, true);
1534 #endif
1536 void PopupMenu::showMenuAsync (const Options& options, ModalComponentManager::Callback* userCallback)
1538 #if ! JUCE_MODAL_LOOPS_PERMITTED
1539 jassert (userCallback != nullptr);
1540 #endif
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),
1556 callback, true);
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),
1570 callback, true);
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);
1589 #endif
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];
1598 if (pmw != nullptr)
1599 pmw->dismissMenu (0);
1602 return numWindows > 0;
1605 //==============================================================================
1606 int PopupMenu::getNumItems() const noexcept
1608 int num = 0;
1610 for (int i = items.size(); --i >= 0;)
1611 if (! items.getUnchecked(i)->isSeparator)
1612 ++num;
1614 return num;
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)))
1626 return true;
1630 return false;
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())
1642 return true;
1644 else if (mi->active)
1646 return true;
1650 return false;
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;
1672 repaint();
1675 void PopupMenu::CustomComponent::triggerMenuItem()
1677 PopupMenu::ItemComponent* const mic = dynamic_cast <PopupMenu::ItemComponent*> (getParentComponent());
1679 if (mic != nullptr)
1681 PopupMenu::Window* const pmw = dynamic_cast <PopupMenu::Window*> (mic->getParentComponent());
1683 if (pmw != nullptr)
1685 pmw->dismissMenu (&mic->itemInfo);
1687 else
1689 // something must have gone wrong with the component hierarchy if this happens..
1690 jassertfalse;
1693 else
1695 // why isn't this component inside a menu? Not much point triggering the item if
1696 // there's no menu.
1697 jassertfalse;
1701 //==============================================================================
1702 PopupMenu::MenuItemIterator::MenuItemIterator (const PopupMenu& menu_)
1703 : subMenu (nullptr),
1704 itemId (0),
1705 isSeparator (false),
1706 isTicked (false),
1707 isEnabled (false),
1708 isCustomComponent (false),
1709 isSectionHeader (false),
1710 customColour (0),
1711 menu (menu_),
1712 index (0)
1716 PopupMenu::MenuItemIterator::~MenuItemIterator()
1720 bool PopupMenu::MenuItemIterator::next()
1722 if (index >= menu.items.size())
1723 return false;
1725 const Item* const item = menu.items.getUnchecked (index);
1726 ++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;
1741 return true;
1745 END_JUCE_NAMESPACE