| 
							- /*
 -   ==============================================================================
 - 
 -    This file is part of the JUCE library.
 -    Copyright (c) 2017 - ROLI Ltd.
 - 
 -    JUCE is an open source library subject to commercial or open-source
 -    licensing.
 - 
 -    By using JUCE, you agree to the terms of both the JUCE 5 End-User License
 -    Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
 -    27th April 2017).
 - 
 -    End User License Agreement: www.juce.com/juce-5-licence
 -    Privacy Policy: www.juce.com/juce-5-privacy-policy
 - 
 -    Or: You may also use this code under the terms of the GPL v3 (see
 -    www.gnu.org/licenses).
 - 
 -    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
 -    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
 -    DISCLAIMED.
 - 
 -   ==============================================================================
 - */
 - 
 - namespace juce
 - {
 - 
 - namespace PopupMenuSettings
 - {
 -     const int scrollZone = 24;
 -     const int dismissCommandId = 0x6287345f;
 - 
 -     static bool menuWasHiddenBecauseOfAppChange = false;
 - }
 - 
 - //==============================================================================
 - struct PopupMenu::HelperClasses
 - {
 - 
 - class MouseSourceState;
 - struct MenuWindow;
 - 
 - static bool canBeTriggered (const PopupMenu::Item& item) noexcept        { return item.isEnabled && item.itemID != 0 && ! item.isSectionHeader; }
 - static bool hasActiveSubMenu (const PopupMenu::Item& item) noexcept      { return item.isEnabled && item.subMenu != nullptr && item.subMenu->items.size() > 0; }
 - static const Colour* getColour (const PopupMenu::Item& item) noexcept    { return item.colour != Colour() ? &item.colour : nullptr; }
 - static bool hasSubMenu (const PopupMenu::Item& item) noexcept            { return item.subMenu != nullptr && (item.itemID == 0 || item.subMenu->getNumItems() > 0); }
 - 
 - //==============================================================================
 - struct HeaderItemComponent  : public PopupMenu::CustomComponent
 - {
 -     HeaderItemComponent (const String& name)  : PopupMenu::CustomComponent (false)
 -     {
 -         setName (name);
 -     }
 - 
 -     void paint (Graphics& g) override
 -     {
 -         getLookAndFeel().drawPopupMenuSectionHeader (g, getLocalBounds(), getName());
 -     }
 - 
 -     void getIdealSize (int& idealWidth, int& idealHeight) override
 -     {
 -         getLookAndFeel().getIdealPopupMenuItemSize (getName(), false, -1, idealWidth, idealHeight);
 -         idealHeight += idealHeight / 2;
 -         idealWidth += idealWidth / 4;
 -     }
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HeaderItemComponent)
 - };
 - 
 - //==============================================================================
 - struct ItemComponent  : public Component
 - {
 -     ItemComponent (const PopupMenu::Item& i, int standardItemHeight, MenuWindow& parent)
 -       : item (i), customComp (i.customComponent)
 -     {
 -         if (item.isSectionHeader)
 -             customComp = *new HeaderItemComponent (item.text);
 - 
 -         if (customComp != nullptr)
 -             addAndMakeVisible (*customComp);
 - 
 -         parent.addAndMakeVisible (this);
 - 
 -         updateShortcutKeyDescription();
 - 
 -         int itemW = 80;
 -         int itemH = 16;
 -         getIdealSize (itemW, itemH, standardItemHeight);
 -         setSize (itemW, jlimit (1, 600, itemH));
 - 
 -         addMouseListener (&parent, false);
 -     }
 - 
 -     ~ItemComponent() override
 -     {
 -         removeChildComponent (customComp.get());
 -     }
 - 
 -     void getIdealSize (int& idealWidth, int& idealHeight, const int standardItemHeight)
 -     {
 -         if (customComp != nullptr)
 -             customComp->getIdealSize (idealWidth, idealHeight);
 -         else
 -             getLookAndFeel().getIdealPopupMenuItemSize (getTextForMeasurement(),
 -                                                         item.isSeparator,
 -                                                         standardItemHeight,
 -                                                         idealWidth, idealHeight);
 -     }
 - 
 -     void paint (Graphics& g) override
 -     {
 -         if (customComp == nullptr)
 -             getLookAndFeel().drawPopupMenuItem (g, getLocalBounds(),
 -                                                 item.isSeparator,
 -                                                 item.isEnabled,
 -                                                 isHighlighted,
 -                                                 item.isTicked,
 -                                                 hasSubMenu (item),
 -                                                 item.text,
 -                                                 item.shortcutKeyDescription,
 -                                                 item.image.get(),
 -                                                 getColour (item));
 -     }
 - 
 -     void resized() override
 -     {
 -         if (auto* child = getChildComponent (0))
 -             child->setBounds (getLocalBounds().reduced (getLookAndFeel().getPopupMenuBorderSize(), 0));
 -     }
 - 
 -     void setHighlighted (bool shouldBeHighlighted)
 -     {
 -         shouldBeHighlighted = shouldBeHighlighted && item.isEnabled;
 - 
 -         if (isHighlighted != shouldBeHighlighted)
 -         {
 -             isHighlighted = shouldBeHighlighted;
 - 
 -             if (customComp != nullptr)
 -                 customComp->setHighlighted (shouldBeHighlighted);
 - 
 -             repaint();
 -         }
 -     }
 - 
 -     PopupMenu::Item item;
 - 
 - private:
 -     // NB: we use a copy of the one from the item info in case we're using our own section comp
 -     ReferenceCountedObjectPtr<CustomComponent> customComp;
 -     bool isHighlighted = false;
 - 
 -     void updateShortcutKeyDescription()
 -     {
 -         if (item.commandManager != nullptr
 -              && item.itemID != 0
 -              && item.shortcutKeyDescription.isEmpty())
 -         {
 -             String shortcutKey;
 - 
 -             for (auto& keypress : item.commandManager->getKeyMappings()
 -                                     ->getKeyPressesAssignedToCommand (item.itemID))
 -             {
 -                 auto key = keypress.getTextDescriptionWithIcons();
 - 
 -                 if (shortcutKey.isNotEmpty())
 -                     shortcutKey << ", ";
 - 
 -                 if (key.length() == 1 && key[0] < 128)
 -                     shortcutKey << "shortcut: '" << key << '\'';
 -                 else
 -                     shortcutKey << key;
 -             }
 - 
 -             item.shortcutKeyDescription = shortcutKey.trim();
 -         }
 -     }
 - 
 -     String getTextForMeasurement() const
 -     {
 -         return item.shortcutKeyDescription.isNotEmpty() ? item.text + "   " + item.shortcutKeyDescription
 -                                                         : item.text;
 -     }
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemComponent)
 - };
 - 
 - //==============================================================================
 - struct MenuWindow  : public Component
 - {
 -     MenuWindow (const PopupMenu& menu, MenuWindow* parentWindow,
 -                 const Options& opts, bool alignToRectangle, bool shouldDismissOnMouseUp,
 -                 ApplicationCommandManager** manager, float parentScaleFactor = 1.0f)
 -        : Component ("menu"),
 -          parent (parentWindow),
 -          options (opts),
 -          managerOfChosenCommand (manager),
 -          componentAttachedTo (options.getTargetComponent()),
 -          dismissOnMouseUp (shouldDismissOnMouseUp),
 -          windowCreationTime (Time::getMillisecondCounter()),
 -          lastFocusedTime (windowCreationTime),
 -          timeEnteredCurrentChildComp (windowCreationTime),
 -          scaleFactor (parentWindow != nullptr ? parentScaleFactor : 1.0f)
 -     {
 -         setWantsKeyboardFocus (false);
 -         setMouseClickGrabsKeyboardFocus (false);
 -         setAlwaysOnTop (true);
 - 
 -         setLookAndFeel (parent != nullptr ? &(parent->getLookAndFeel())
 -                                           : menu.lookAndFeel.get());
 -         auto& lf = getLookAndFeel();
 - 
 -         parentComponent = lf.getParentComponentForMenuOptions (options);
 -         const_cast<Options&>(options) = options.withParentComponent (parentComponent);
 - 
 -         if (parentComponent == nullptr && parentWindow == nullptr && lf.shouldPopupMenuScaleWithTargetComponent (options))
 -             if (auto* targetComponent = options.getTargetComponent())
 -                 scaleFactor = getApproximateScaleFactorForTargetComponent (targetComponent);
 - 
 -         setOpaque (lf.findColour (PopupMenu::backgroundColourId).isOpaque()
 -                      || ! Desktop::canUseSemiTransparentWindows());
 - 
 -         for (int i = 0; i < menu.items.size(); ++i)
 -         {
 -             auto item = menu.items.getUnchecked (i);
 - 
 -             if (i < menu.items.size() - 1 || ! item->isSeparator)
 -                 items.add (new ItemComponent (*item, options.getStandardItemHeight(), *this));
 -         }
 - 
 -         auto targetArea = options.getTargetScreenArea() / scaleFactor;
 - 
 -         calculateWindowPos (targetArea, alignToRectangle);
 -         setTopLeftPosition (windowPos.getPosition());
 -         updateYPositions();
 - 
 -         if (auto visibleID = options.getItemThatMustBeVisible())
 -         {
 -             auto targetPosition = parentComponent != nullptr ? parentComponent->getLocalPoint (nullptr, targetArea.getTopLeft())
 -                                                              : targetArea.getTopLeft();
 - 
 -             auto y = targetPosition.getY() - windowPos.getY();
 -             ensureItemIsVisible (visibleID, isPositiveAndBelow (y, windowPos.getHeight()) ? y : -1);
 -         }
 - 
 -         resizeToBestWindowPos();
 - 
 -         if (parentComponent != nullptr)
 -         {
 -             parentComponent->addChildComponent (this);
 -         }
 -         else
 -         {
 -             addToDesktop (ComponentPeer::windowIsTemporary
 -                           | ComponentPeer::windowIgnoresKeyPresses
 -                           | lf.getMenuWindowFlags());
 - 
 -             getActiveWindows().add (this);
 -             Desktop::getInstance().addGlobalMouseListener (this);
 -         }
 - 
 -         lf.preparePopupMenuWindow (*this);
 - 
 -         getMouseState (Desktop::getInstance().getMainMouseSource()); // forces creation of a mouse source watcher for the main mouse
 -     }
 - 
 -     ~MenuWindow() override
 -     {
 -         getActiveWindows().removeFirstMatchingValue (this);
 -         Desktop::getInstance().removeGlobalMouseListener (this);
 -         activeSubMenu.reset();
 -         items.clear();
 -     }
 - 
 -     //==============================================================================
 -     void paint (Graphics& g) override
 -     {
 -         if (isOpaque())
 -             g.fillAll (Colours::white);
 - 
 -         getLookAndFeel().drawPopupMenuBackground (g, getWidth(), getHeight());
 -     }
 - 
 -     void paintOverChildren (Graphics& g) override
 -     {
 -         auto& lf = getLookAndFeel();
 - 
 -         if (parentComponent != nullptr)
 -             lf.drawResizableFrame (g, getWidth(), getHeight(),
 -                                    BorderSize<int> (getLookAndFeel().getPopupMenuBorderSize()));
 - 
 -         if (canScroll())
 -         {
 -             if (isTopScrollZoneActive())
 -                 lf.drawPopupMenuUpDownArrow (g, getWidth(), PopupMenuSettings::scrollZone, true);
 - 
 -             if (isBottomScrollZoneActive())
 -             {
 -                 g.setOrigin (0, getHeight() - PopupMenuSettings::scrollZone);
 -                 lf.drawPopupMenuUpDownArrow (g, getWidth(), PopupMenuSettings::scrollZone, false);
 -             }
 -         }
 -     }
 - 
 -     //==============================================================================
 -     // hide this and all sub-comps
 -     void hide (const PopupMenu::Item* item, bool makeInvisible)
 -     {
 -         if (isVisible())
 -         {
 -             WeakReference<Component> deletionChecker (this);
 - 
 -             activeSubMenu.reset();
 -             currentChild = nullptr;
 - 
 -             if (item != nullptr
 -                  && item->commandManager != nullptr
 -                  && item->itemID != 0)
 -             {
 -                 *managerOfChosenCommand = item->commandManager;
 -             }
 - 
 -             exitModalState (getResultItemID (item));
 - 
 -             if (makeInvisible && (deletionChecker != nullptr))
 -                 setVisible (false);
 -         }
 -     }
 - 
 -     static int getResultItemID (const PopupMenu::Item* item)
 -     {
 -         if (item == nullptr)
 -             return 0;
 - 
 -         if (auto* cc = item->customCallback.get())
 -             if (! cc->menuItemTriggered())
 -                 return 0;
 - 
 -         return item->itemID;
 -     }
 - 
 -     void dismissMenu (const PopupMenu::Item* const item)
 -     {
 -         if (parent != nullptr)
 -         {
 -             parent->dismissMenu (item);
 -         }
 -         else
 -         {
 -             if (item != nullptr)
 -             {
 -                 // need a copy of this on the stack as the one passed in will get deleted during this call
 -                 auto mi (*item);
 -                 hide (&mi, false);
 -             }
 -             else
 -             {
 -                 hide (nullptr, false);
 -             }
 -         }
 -     }
 - 
 -     float getDesktopScaleFactor() const override    { return scaleFactor * Desktop::getInstance().getGlobalScaleFactor(); }
 - 
 -     //==============================================================================
 -     bool keyPressed (const KeyPress& key) override
 -     {
 -         if (key.isKeyCode (KeyPress::downKey))
 -         {
 -             selectNextItem (1);
 -         }
 -         else if (key.isKeyCode (KeyPress::upKey))
 -         {
 -             selectNextItem (-1);
 -         }
 -         else if (key.isKeyCode (KeyPress::leftKey))
 -         {
 -             if (parent != nullptr)
 -             {
 -                 Component::SafePointer<MenuWindow> parentWindow (parent);
 -                 ItemComponent* currentChildOfParent = parentWindow->currentChild;
 - 
 -                 hide (nullptr, true);
 - 
 -                 if (parentWindow != nullptr)
 -                     parentWindow->setCurrentlyHighlightedChild (currentChildOfParent);
 - 
 -                 disableTimerUntilMouseMoves();
 -             }
 -             else if (componentAttachedTo != nullptr)
 -             {
 -                 componentAttachedTo->keyPressed (key);
 -             }
 -         }
 -         else if (key.isKeyCode (KeyPress::rightKey))
 -         {
 -             disableTimerUntilMouseMoves();
 - 
 -             if (showSubMenuFor (currentChild))
 -             {
 -                 if (isSubMenuVisible())
 -                     activeSubMenu->selectNextItem (1);
 -             }
 -             else if (componentAttachedTo != nullptr)
 -             {
 -                 componentAttachedTo->keyPressed (key);
 -             }
 -         }
 -         else if (key.isKeyCode (KeyPress::returnKey))
 -         {
 -             triggerCurrentlyHighlightedItem();
 -         }
 -         else if (key.isKeyCode (KeyPress::escapeKey))
 -         {
 -             dismissMenu (nullptr);
 -         }
 -         else
 -         {
 -             return false;
 -         }
 - 
 -         return true;
 -     }
 - 
 -     void inputAttemptWhenModal() override
 -     {
 -         WeakReference<Component> deletionChecker (this);
 - 
 -         for (auto* ms : mouseSourceStates)
 -         {
 -             ms->timerCallback();
 - 
 -             if (deletionChecker == nullptr)
 -                 return;
 -         }
 - 
 -         if (! isOverAnyMenu())
 -         {
 -             if (componentAttachedTo != nullptr)
 -             {
 -                 // we want to dismiss the menu, but if we do it synchronously, then
 -                 // the mouse-click will be allowed to pass through. That's good, except
 -                 // when the user clicks on the button that originally popped the menu up,
 -                 // as they'll expect the menu to go away, and in fact it'll just
 -                 // come back. So only dismiss synchronously if they're not on the original
 -                 // comp that we're attached to.
 -                 auto mousePos = componentAttachedTo->getMouseXYRelative();
 - 
 -                 if (componentAttachedTo->reallyContains (mousePos, true))
 -                 {
 -                     postCommandMessage (PopupMenuSettings::dismissCommandId); // dismiss asynchrounously
 -                     return;
 -                 }
 -             }
 - 
 -             dismissMenu (nullptr);
 -         }
 -     }
 - 
 -     void handleCommandMessage (int commandId) override
 -     {
 -         Component::handleCommandMessage (commandId);
 - 
 -         if (commandId == PopupMenuSettings::dismissCommandId)
 -             dismissMenu (nullptr);
 -     }
 - 
 -     //==============================================================================
 -     void mouseMove  (const MouseEvent& e) override    { handleMouseEvent (e); }
 -     void mouseDown  (const MouseEvent& e) override    { handleMouseEvent (e); }
 -     void mouseDrag  (const MouseEvent& e) override    { handleMouseEvent (e); }
 -     void mouseUp    (const MouseEvent& e) override    { handleMouseEvent (e); }
 - 
 -     void mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel) override
 -     {
 -         alterChildYPos (roundToInt (-10.0f * wheel.deltaY * PopupMenuSettings::scrollZone));
 -     }
 - 
 -     void handleMouseEvent (const MouseEvent& e)
 -     {
 -         getMouseState (e.source).handleMouseEvent (e);
 -     }
 - 
 -     bool windowIsStillValid()
 -     {
 -         if (! isVisible())
 -             return false;
 - 
 -         if (componentAttachedTo != options.getTargetComponent())
 -         {
 -             dismissMenu (nullptr);
 -             return false;
 -         }
 - 
 -         if (auto* currentlyModalWindow = dynamic_cast<MenuWindow*> (Component::getCurrentlyModalComponent()))
 -             if (! treeContains (currentlyModalWindow))
 -                 return false;
 - 
 -         return true;
 -     }
 - 
 -     static Array<MenuWindow*>& getActiveWindows()
 -     {
 -         static Array<MenuWindow*> activeMenuWindows;
 -         return activeMenuWindows;
 -     }
 - 
 -     MouseSourceState& getMouseState (MouseInputSource source)
 -     {
 -         MouseSourceState* mouseState = nullptr;
 - 
 -         for (auto* ms : mouseSourceStates)
 -         {
 -             if      (ms->source == source)                        mouseState = ms;
 -             else if (ms->source.getType() != source.getType())    ms->stopTimer();
 -         }
 - 
 -         if (mouseState == nullptr)
 -         {
 -             mouseState = new MouseSourceState (*this, source);
 -             mouseSourceStates.add (mouseState);
 -         }
 - 
 -         return *mouseState;
 -     }
 - 
 -     //==============================================================================
 -     bool isOverAnyMenu() const
 -     {
 -         return parent != nullptr ? parent->isOverAnyMenu()
 -                                  : isOverChildren();
 -     }
 - 
 -     bool isOverChildren() const
 -     {
 -         return isVisible()
 -                 && (isAnyMouseOver() || (activeSubMenu != nullptr && activeSubMenu->isOverChildren()));
 -     }
 - 
 -     bool isAnyMouseOver() const
 -     {
 -         for (auto* ms : mouseSourceStates)
 -             if (ms->isOver())
 -                 return true;
 - 
 -         return false;
 -     }
 - 
 -     bool treeContains (const MenuWindow* const window) const noexcept
 -     {
 -         auto* mw = this;
 - 
 -         while (mw->parent != nullptr)
 -             mw = mw->parent;
 - 
 -         while (mw != nullptr)
 -         {
 -             if (mw == window)
 -                 return true;
 - 
 -             mw = mw->activeSubMenu.get();
 -         }
 - 
 -         return false;
 -     }
 - 
 -     bool doesAnyJuceCompHaveFocus()
 -     {
 -         bool anyFocused = Process::isForegroundProcess();
 - 
 -         if (anyFocused && Component::getCurrentlyFocusedComponent() == nullptr)
 -         {
 -             // because no component at all may have focus, our test here will
 -             // only be triggered when something has focus and then loses it.
 -             anyFocused = ! hasAnyJuceCompHadFocus;
 - 
 -             for (int i = ComponentPeer::getNumPeers(); --i >= 0;)
 -             {
 -                 if (ComponentPeer::getPeer (i)->isFocused())
 -                 {
 -                     anyFocused = true;
 -                     hasAnyJuceCompHadFocus = true;
 -                     break;
 -                 }
 -             }
 -         }
 - 
 -         return anyFocused;
 -     }
 - 
 -     //==============================================================================
 -     Rectangle<int> getParentArea (Point<int> targetPoint, Component* relativeTo = nullptr)
 -     {
 -         if (relativeTo != nullptr)
 -             targetPoint = relativeTo->localPointToGlobal (targetPoint);
 - 
 -         auto parentArea = Desktop::getInstance().getDisplays().findDisplayForPoint (targetPoint)
 -                               #if JUCE_MAC || JUCE_ANDROID
 -                                .userArea;
 -                               #else
 -                                .totalArea; // on windows, don't stop the menu overlapping the taskbar
 -                               #endif
 - 
 -         if (parentComponent == nullptr)
 -             return parentArea;
 - 
 -         return parentComponent->getLocalArea (nullptr,
 -                                               parentComponent->getScreenBounds()
 -                                                     .reduced (getLookAndFeel().getPopupMenuBorderSize())
 -                                                     .getIntersection (parentArea));
 -     }
 - 
 -     void calculateWindowPos (Rectangle<int> target, const bool alignToRectangle)
 -     {
 -         auto parentArea = getParentArea (target.getCentre()) / scaleFactor;
 - 
 -         if (parentComponent != nullptr)
 -             target = parentComponent->getLocalArea (nullptr, target).getIntersection (parentArea);
 - 
 -         auto maxMenuHeight = parentArea.getHeight() - 24;
 - 
 -         int x, y, widthToUse, heightToUse;
 -         layoutMenuItems (parentArea.getWidth() - 24, maxMenuHeight, widthToUse, heightToUse);
 - 
 -         if (alignToRectangle)
 -         {
 -             x = target.getX();
 - 
 -             auto spaceUnder = parentArea.getBottom() - target.getBottom();
 -             auto spaceOver = target.getY() - parentArea.getY();
 -             auto bufferHeight = 30;
 - 
 -             if (options.getPreferredPopupDirection() == Options::PopupDirection::upwards)
 -                 y = (heightToUse < spaceOver - bufferHeight  || spaceOver >= spaceUnder) ? target.getY() - heightToUse
 -                                                                                          : target.getBottom();
 -             else
 -                 y = (heightToUse < spaceUnder - bufferHeight || spaceUnder >= spaceOver) ? target.getBottom()
 -                                                                                          : target.getY() - heightToUse;
 -         }
 -         else
 -         {
 -             bool tendTowardsRight = target.getCentreX() < parentArea.getCentreX();
 - 
 -             if (parent != nullptr)
 -             {
 -                 if (parent->parent != nullptr)
 -                 {
 -                     const bool parentGoingRight = (parent->getX() + parent->getWidth() / 2
 -                                                     > parent->parent->getX() + parent->parent->getWidth() / 2);
 - 
 -                     if (parentGoingRight && target.getRight() + widthToUse < parentArea.getRight() - 4)
 -                         tendTowardsRight = true;
 -                     else if ((! parentGoingRight) && target.getX() > widthToUse + 4)
 -                         tendTowardsRight = false;
 -                 }
 -                 else if (target.getRight() + widthToUse < parentArea.getRight() - 32)
 -                 {
 -                     tendTowardsRight = true;
 -                 }
 -             }
 - 
 -             auto biggestSpace = jmax (parentArea.getRight() - target.getRight(),
 -                                       target.getX() - parentArea.getX()) - 32;
 - 
 -             if (biggestSpace < widthToUse)
 -             {
 -                 layoutMenuItems (biggestSpace + target.getWidth() / 3, maxMenuHeight, widthToUse, heightToUse);
 - 
 -                 if (numColumns > 1)
 -                     layoutMenuItems (biggestSpace - 4, maxMenuHeight, widthToUse, heightToUse);
 - 
 -                 tendTowardsRight = (parentArea.getRight() - target.getRight()) >= (target.getX() - parentArea.getX());
 -             }
 - 
 -             x = tendTowardsRight ? jmin (parentArea.getRight() - widthToUse - 4, target.getRight())
 -                                  : jmax (parentArea.getX() + 4, target.getX() - widthToUse);
 - 
 -             if (getLookAndFeel().getPopupMenuBorderSize() == 0) // workaround for dismissing the window on mouse up when border size is 0
 -                 x += tendTowardsRight ? 1 : -1;
 - 
 -             y = target.getCentreY() > parentArea.getCentreY() ? jmax (parentArea.getY(), target.getBottom() - heightToUse)
 -                                                               : target.getY();
 -         }
 - 
 -         x = jmax (parentArea.getX() + 1, jmin (parentArea.getRight()  - (widthToUse  + 6), x));
 -         y = jmax (parentArea.getY() + 1, jmin (parentArea.getBottom() - (heightToUse + 6), y));
 - 
 -         windowPos.setBounds (x, y, widthToUse, heightToUse);
 - 
 -         // sets this flag if it's big enough to obscure any of its parent menus
 -         hideOnExit = parent != nullptr
 -                       && parent->windowPos.intersects (windowPos.expanded (-4, -4));
 -     }
 - 
 -     void layoutMenuItems (const int maxMenuW, const int maxMenuH, int& width, int& height)
 -     {
 -         numColumns = options.getMinimumNumColumns();
 -         contentHeight = 0;
 - 
 -         auto maximumNumColumns = options.getMaximumNumColumns() > 0 ? options.getMaximumNumColumns() : 7;
 - 
 -         for (;;)
 -         {
 -             auto totalW = workOutBestSize (maxMenuW);
 - 
 -             if (totalW > maxMenuW)
 -             {
 -                 numColumns = jmax (1, numColumns - 1);
 -                 workOutBestSize (maxMenuW); // to update col widths
 -                 break;
 -             }
 - 
 -             if (totalW > maxMenuW / 2
 -                  || contentHeight < maxMenuH
 -                  || numColumns >= maximumNumColumns)
 -                 break;
 - 
 -             ++numColumns;
 -         }
 - 
 -         auto actualH = jmin (contentHeight, maxMenuH);
 - 
 -         needsToScroll = contentHeight > actualH;
 - 
 -         width = updateYPositions();
 -         height = actualH + getLookAndFeel().getPopupMenuBorderSize() * 2;
 -     }
 - 
 -     int workOutBestSize (const int maxMenuW)
 -     {
 -         int totalW = 0;
 -         contentHeight = 0;
 -         int childNum = 0;
 - 
 -         for (int col = 0; col < numColumns; ++col)
 -         {
 -             int colW = options.getStandardItemHeight(), colH = 0;
 - 
 -             auto numChildren = jmin (items.size() - childNum,
 -                                      (items.size() + numColumns - 1) / numColumns);
 - 
 -             for (int i = numChildren; --i >= 0;)
 -             {
 -                 colW = jmax (colW, items.getUnchecked (childNum + i)->getWidth());
 -                 colH += items.getUnchecked (childNum + i)->getHeight();
 -             }
 - 
 -             colW = jmin (maxMenuW / jmax (1, numColumns - 2), colW + getLookAndFeel().getPopupMenuBorderSize() * 2);
 - 
 -             columnWidths.set (col, colW);
 -             totalW += colW;
 -             contentHeight = jmax (contentHeight, colH);
 - 
 -             childNum += numChildren;
 -         }
 - 
 -         // width must never be larger than the screen
 -         auto minWidth = jmin (maxMenuW, options.getMinimumWidth());
 - 
 -         if (totalW < minWidth)
 -         {
 -             totalW = minWidth;
 - 
 -             for (int col = 0; col < numColumns; ++col)
 -                 columnWidths.set (0, totalW / numColumns);
 -         }
 - 
 -         return totalW;
 -     }
 - 
 -     void ensureItemIsVisible (const int itemID, int wantedY)
 -     {
 -         jassert (itemID != 0);
 - 
 -         for (int i = items.size(); --i >= 0;)
 -         {
 -             if (auto* m = items.getUnchecked (i))
 -             {
 -                 if (m->item.itemID == itemID
 -                      && windowPos.getHeight() > PopupMenuSettings::scrollZone * 4)
 -                 {
 -                     auto currentY = m->getY();
 - 
 -                     if (wantedY > 0 || currentY < 0 || m->getBottom() > windowPos.getHeight())
 -                     {
 -                         if (wantedY < 0)
 -                             wantedY = jlimit (PopupMenuSettings::scrollZone,
 -                                               jmax (PopupMenuSettings::scrollZone,
 -                                                     windowPos.getHeight() - (PopupMenuSettings::scrollZone + m->getHeight())),
 -                                               currentY);
 - 
 -                         auto parentArea = getParentArea (windowPos.getPosition(), parentComponent) / scaleFactor;
 -                         auto deltaY = wantedY - currentY;
 - 
 -                         windowPos.setSize (jmin (windowPos.getWidth(), parentArea.getWidth()),
 -                                            jmin (windowPos.getHeight(), parentArea.getHeight()));
 - 
 -                         auto newY = jlimit (parentArea.getY(),
 -                                             parentArea.getBottom() - windowPos.getHeight(),
 -                                             windowPos.getY() + deltaY);
 - 
 -                         deltaY -= newY - windowPos.getY();
 - 
 -                         childYOffset -= deltaY;
 -                         windowPos.setPosition (windowPos.getX(), newY);
 - 
 -                         updateYPositions();
 -                     }
 - 
 -                     break;
 -                 }
 -             }
 -         }
 -     }
 - 
 -     void resizeToBestWindowPos()
 -     {
 -         auto r = windowPos;
 - 
 -         if (childYOffset < 0)
 -         {
 -             r = r.withTop (r.getY() - childYOffset);
 -         }
 -         else if (childYOffset > 0)
 -         {
 -             auto spaceAtBottom = r.getHeight() - (contentHeight - childYOffset);
 - 
 -             if (spaceAtBottom > 0)
 -                 r.setSize (r.getWidth(), r.getHeight() - spaceAtBottom);
 -         }
 - 
 -         setBounds (r);
 -         updateYPositions();
 -     }
 - 
 -     void alterChildYPos (int delta)
 -     {
 -         if (canScroll())
 -         {
 -             childYOffset += delta;
 - 
 -             if (delta < 0)
 -                 childYOffset = jmax (childYOffset, 0);
 -             else if (delta > 0)
 -                 childYOffset = jmin (childYOffset,
 -                                      contentHeight - windowPos.getHeight() + getLookAndFeel().getPopupMenuBorderSize());
 - 
 -             updateYPositions();
 -         }
 -         else
 -         {
 -             childYOffset = 0;
 -         }
 - 
 -         resizeToBestWindowPos();
 -         repaint();
 -     }
 - 
 -     int updateYPositions()
 -     {
 -         int x = 0;
 -         int childNum = 0;
 - 
 -         for (int col = 0; col < numColumns; ++col)
 -         {
 -             auto numChildren = jmin (items.size() - childNum,
 -                                      (items.size() + numColumns - 1) / numColumns);
 - 
 -             auto colW = columnWidths[col];
 -             auto y = getLookAndFeel().getPopupMenuBorderSize() - (childYOffset + (getY() - windowPos.getY()));
 - 
 -             for (int i = 0; i < numChildren; ++i)
 -             {
 -                 auto* c = items.getUnchecked (childNum + i);
 -                 c->setBounds (x, y, colW, c->getHeight());
 -                 y += c->getHeight();
 -             }
 - 
 -             x += colW;
 -             childNum += numChildren;
 -         }
 - 
 -         return x;
 -     }
 - 
 -     void setCurrentlyHighlightedChild (ItemComponent* child)
 -     {
 -         if (currentChild != nullptr)
 -             currentChild->setHighlighted (false);
 - 
 -         currentChild = child;
 - 
 -         if (currentChild != nullptr)
 -         {
 -             currentChild->setHighlighted (true);
 -             timeEnteredCurrentChildComp = Time::getApproximateMillisecondCounter();
 -         }
 -     }
 - 
 -     bool isSubMenuVisible() const noexcept          { return activeSubMenu != nullptr && activeSubMenu->isVisible(); }
 - 
 -     bool showSubMenuFor (ItemComponent* childComp)
 -     {
 -         activeSubMenu.reset();
 - 
 -         if (childComp != nullptr
 -              && hasActiveSubMenu (childComp->item))
 -         {
 -             activeSubMenu.reset (new HelperClasses::MenuWindow (*(childComp->item.subMenu), this,
 -                                                                 options.withTargetScreenArea (childComp->getScreenBounds())
 -                                                                        .withMinimumWidth (0)
 -                                                                        .withTargetComponent (nullptr)
 -                                                                        .withParentComponent (parentComponent),
 -                                                                 false, dismissOnMouseUp, managerOfChosenCommand, scaleFactor));
 - 
 -             activeSubMenu->setVisible (true); // (must be called before enterModalState on Windows to avoid DropShadower confusion)
 -             activeSubMenu->enterModalState (false);
 -             activeSubMenu->toFront (false);
 -             return true;
 -         }
 - 
 -         return false;
 -     }
 - 
 -     void triggerCurrentlyHighlightedItem()
 -     {
 -         if (currentChild != nullptr
 -              && canBeTriggered (currentChild->item)
 -              && (currentChild->item.customComponent == nullptr
 -                   || currentChild->item.customComponent->isTriggeredAutomatically()))
 -         {
 -             dismissMenu (¤tChild->item);
 -         }
 -     }
 - 
 -     void selectNextItem (int delta)
 -     {
 -         disableTimerUntilMouseMoves();
 - 
 -         auto start = jmax (0, items.indexOf (currentChild));
 - 
 -         for (int i = items.size(); --i >= 0;)
 -         {
 -             start += delta;
 - 
 -             if (auto* mic = items.getUnchecked ((start + items.size()) % items.size()))
 -             {
 -                 if (canBeTriggered (mic->item) || hasActiveSubMenu (mic->item))
 -                 {
 -                     setCurrentlyHighlightedChild (mic);
 -                     break;
 -                 }
 -             }
 -         }
 -     }
 - 
 -     void disableTimerUntilMouseMoves()
 -     {
 -         disableMouseMoves = true;
 - 
 -         if (parent != nullptr)
 -             parent->disableTimerUntilMouseMoves();
 -     }
 - 
 -     bool canScroll() const noexcept                 { return childYOffset != 0 || needsToScroll; }
 -     bool isTopScrollZoneActive() const noexcept     { return canScroll() && childYOffset > 0; }
 -     bool isBottomScrollZoneActive() const noexcept  { return canScroll() && childYOffset < contentHeight - windowPos.getHeight(); }
 - 
 -     //==============================================================================
 -     static float getApproximateScaleFactorForTargetComponent (Component* targetComponent)
 -     {
 -         AffineTransform transform;
 - 
 -         for (auto* target = targetComponent; target != nullptr; target = target->getParentComponent())
 -         {
 -             transform = transform.followedBy (target->getTransform());
 - 
 -             if (target->isOnDesktop())
 -                 transform = transform.scaled (target->getDesktopScaleFactor());
 -         }
 - 
 -         return (transform.getScaleFactor() / Desktop::getInstance().getGlobalScaleFactor());
 -     }
 - 
 -     //==============================================================================
 -     MenuWindow* parent;
 -     const Options options;
 -     OwnedArray<ItemComponent> items;
 -     ApplicationCommandManager** managerOfChosenCommand;
 -     WeakReference<Component> componentAttachedTo;
 -     Component* parentComponent = nullptr;
 -     Rectangle<int> windowPos;
 -     bool hasBeenOver = false, needsToScroll = false;
 -     bool dismissOnMouseUp, hideOnExit = false, disableMouseMoves = false, hasAnyJuceCompHadFocus = false;
 -     int numColumns = 0, contentHeight = 0, childYOffset = 0;
 -     Component::SafePointer<ItemComponent> currentChild;
 -     std::unique_ptr<MenuWindow> activeSubMenu;
 -     Array<int> columnWidths;
 -     uint32 windowCreationTime, lastFocusedTime, timeEnteredCurrentChildComp;
 -     OwnedArray<MouseSourceState> mouseSourceStates;
 -     float scaleFactor;
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MenuWindow)
 - };
 - 
 - //==============================================================================
 - class MouseSourceState  : public Timer
 - {
 - public:
 -     MouseSourceState (MenuWindow& w, MouseInputSource s)
 -         : window (w), source (s), lastScrollTime (Time::getMillisecondCounter())
 -     {
 -         startTimerHz (20);
 -     }
 - 
 -     void handleMouseEvent (const MouseEvent& e)
 -     {
 -         if (! window.windowIsStillValid())
 -             return;
 - 
 -         startTimerHz (20);
 -         handleMousePosition (e.getScreenPosition());
 -     }
 - 
 -     void timerCallback() override
 -     {
 -        #if JUCE_WINDOWS
 -         // touch and pen devices on Windows send an offscreen mouse move after mouse up events
 -         // but we don't want to forward these on as they will dismiss the menu
 -         if ((source.isTouch() || source.isPen()) && ! isValidMousePosition())
 -             return;
 -        #endif
 - 
 -         if (window.windowIsStillValid())
 -             handleMousePosition (source.getScreenPosition().roundToInt());
 -     }
 - 
 -     bool isOver() const
 -     {
 -         return window.reallyContains (window.getLocalPoint (nullptr, source.getScreenPosition()).roundToInt(), true);
 -     }
 - 
 -     MenuWindow& window;
 -     MouseInputSource source;
 - 
 - private:
 -     Point<int> lastMousePos;
 -     double scrollAcceleration = 0;
 -     uint32 lastScrollTime, lastMouseMoveTime = 0;
 -     bool isDown = false;
 - 
 -     void handleMousePosition (Point<int> globalMousePos)
 -     {
 -         auto localMousePos = window.getLocalPoint (nullptr, globalMousePos);
 -         auto timeNow = Time::getMillisecondCounter();
 - 
 -         if (timeNow > window.timeEnteredCurrentChildComp + 100
 -              && window.reallyContains (localMousePos, true)
 -              && window.currentChild != nullptr
 -              && ! (window.disableMouseMoves || window.isSubMenuVisible()))
 -         {
 -             window.showSubMenuFor (window.currentChild);
 -         }
 - 
 -         highlightItemUnderMouse (globalMousePos, localMousePos, timeNow);
 - 
 -         const bool overScrollArea = scrollIfNecessary (localMousePos, timeNow);
 -         const bool isOverAny = window.isOverAnyMenu();
 - 
 -         if (window.hideOnExit && window.hasBeenOver && ! isOverAny)
 -             window.hide (nullptr, true);
 -         else
 -             checkButtonState (localMousePos, timeNow, isDown, overScrollArea, isOverAny);
 -     }
 - 
 -     void checkButtonState (Point<int> localMousePos, const uint32 timeNow,
 -                            const bool wasDown, const bool overScrollArea, const bool isOverAny)
 -     {
 -         isDown = window.hasBeenOver
 -                     && (ModifierKeys::currentModifiers.isAnyMouseButtonDown()
 -                          || ComponentPeer::getCurrentModifiersRealtime().isAnyMouseButtonDown());
 - 
 -         if (! window.doesAnyJuceCompHaveFocus())
 -         {
 -             if (timeNow > window.lastFocusedTime + 10)
 -             {
 -                 PopupMenuSettings::menuWasHiddenBecauseOfAppChange = true;
 -                 window.dismissMenu (nullptr);
 -                 // Note: This object may have been deleted by the previous call.
 -             }
 -         }
 -         else if (wasDown && timeNow > window.windowCreationTime + 250
 -                    && ! (isDown || overScrollArea))
 -         {
 -             if (window.reallyContains (localMousePos, true))
 -                 window.triggerCurrentlyHighlightedItem();
 -             else if ((window.hasBeenOver || ! window.dismissOnMouseUp) && ! isOverAny)
 -                 window.dismissMenu (nullptr);
 - 
 -             // Note: This object may have been deleted by the previous call.
 -         }
 -         else
 -         {
 -             window.lastFocusedTime = timeNow;
 -         }
 -     }
 - 
 -     void highlightItemUnderMouse (Point<int> globalMousePos, Point<int> localMousePos, const uint32 timeNow)
 -     {
 -         if (globalMousePos != lastMousePos || timeNow > lastMouseMoveTime + 350)
 -         {
 -             const bool isMouseOver = window.reallyContains (localMousePos, true);
 - 
 -             if (isMouseOver)
 -                 window.hasBeenOver = true;
 - 
 -             if (lastMousePos.getDistanceFrom (globalMousePos) > 2)
 -             {
 -                 lastMouseMoveTime = timeNow;
 - 
 -                 if (window.disableMouseMoves && isMouseOver)
 -                     window.disableMouseMoves = false;
 -             }
 - 
 -             if (window.disableMouseMoves || (window.activeSubMenu != nullptr && window.activeSubMenu->isOverChildren()))
 -                 return;
 - 
 -             const bool isMovingTowardsMenu = isMouseOver && globalMousePos != lastMousePos
 -                                                 && isMovingTowardsSubmenu (globalMousePos);
 - 
 -             lastMousePos = globalMousePos;
 - 
 -             if (! isMovingTowardsMenu)
 -             {
 -                 auto* c = window.getComponentAt (localMousePos);
 - 
 -                 if (c == &window)
 -                     c = nullptr;
 - 
 -                 auto* itemUnderMouse = dynamic_cast<ItemComponent*> (c);
 - 
 -                 if (itemUnderMouse == nullptr && c != nullptr)
 -                     itemUnderMouse = c->findParentComponentOfClass<ItemComponent>();
 - 
 -                 if (itemUnderMouse != window.currentChild
 -                       && (isMouseOver || (window.activeSubMenu == nullptr) || ! window.activeSubMenu->isVisible()))
 -                 {
 -                     if (isMouseOver && (c != nullptr) && (window.activeSubMenu != nullptr))
 -                         window.activeSubMenu->hide (nullptr, true);
 - 
 -                     if (! isMouseOver)
 -                         itemUnderMouse = nullptr;
 - 
 -                     window.setCurrentlyHighlightedChild (itemUnderMouse);
 -                 }
 -             }
 -         }
 -     }
 - 
 -     bool isMovingTowardsSubmenu (Point<int> newGlobalPos) const
 -     {
 -         if (window.activeSubMenu == nullptr)
 -             return false;
 - 
 -         // try to intelligently guess whether the user is moving the mouse towards a currently-open
 -         // submenu. To do this, look at whether the mouse stays inside a triangular region that
 -         // extends from the last mouse pos to the submenu's rectangle..
 - 
 -         auto itemScreenBounds = window.activeSubMenu->getScreenBounds();
 -         auto subX = (float) itemScreenBounds.getX();
 - 
 -         auto oldGlobalPos = lastMousePos;
 - 
 -         if (itemScreenBounds.getX() > window.getX())
 -         {
 -             oldGlobalPos -= Point<int> (2, 0);  // to enlarge the triangle a bit, in case the mouse only moves a couple of pixels
 -         }
 -         else
 -         {
 -             oldGlobalPos += Point<int> (2, 0);
 -             subX += itemScreenBounds.getWidth();
 -         }
 - 
 -         Path areaTowardsSubMenu;
 -         areaTowardsSubMenu.addTriangle ((float) oldGlobalPos.x, (float) oldGlobalPos.y,
 -                                         subX, (float) itemScreenBounds.getY(),
 -                                         subX, (float) itemScreenBounds.getBottom());
 - 
 -         return areaTowardsSubMenu.contains (newGlobalPos.toFloat());
 -     }
 - 
 -     bool scrollIfNecessary (Point<int> localMousePos, const uint32 timeNow)
 -     {
 -         if (window.canScroll()
 -              && isPositiveAndBelow (localMousePos.x, window.getWidth())
 -              && (isPositiveAndBelow (localMousePos.y, window.getHeight()) || source.isDragging()))
 -         {
 -             if (window.isTopScrollZoneActive() && localMousePos.y < PopupMenuSettings::scrollZone)
 -                 return scroll (timeNow, -1);
 - 
 -             if (window.isBottomScrollZoneActive() && localMousePos.y > window.getHeight() - PopupMenuSettings::scrollZone)
 -                 return scroll (timeNow, 1);
 -         }
 - 
 -         scrollAcceleration = 1.0;
 -         return false;
 -     }
 - 
 -     bool scroll (const uint32 timeNow, const int direction)
 -     {
 -         if (timeNow > lastScrollTime + 20)
 -         {
 -             scrollAcceleration = jmin (4.0, scrollAcceleration * 1.04);
 -             int amount = 0;
 - 
 -             for (int i = 0; i < window.items.size() && amount == 0; ++i)
 -                 amount = ((int) scrollAcceleration) * window.items.getUnchecked (i)->getHeight();
 - 
 -             window.alterChildYPos (amount * direction);
 -             lastScrollTime = timeNow;
 -         }
 - 
 -         return true;
 -     }
 - 
 -    #if JUCE_WINDOWS
 -     bool isValidMousePosition()
 -     {
 -         auto screenPos = source.getScreenPosition();
 -         auto localPos = (window.activeSubMenu == nullptr) ? window.getLocalPoint (nullptr, screenPos)
 -                                                           : window.activeSubMenu->getLocalPoint (nullptr, screenPos);
 - 
 -         if (localPos.x < 0 && localPos.y < 0)
 -             return false;
 - 
 -         return true;
 -     }
 -    #endif
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MouseSourceState)
 - };
 - 
 - //==============================================================================
 - struct NormalComponentWrapper : public PopupMenu::CustomComponent
 - {
 -     NormalComponentWrapper (Component* comp, int w, int h, bool triggerMenuItemAutomaticallyWhenClicked)
 -         : PopupMenu::CustomComponent (triggerMenuItemAutomaticallyWhenClicked),
 -           width (w), height (h)
 -     {
 -         addAndMakeVisible (comp);
 -     }
 - 
 -     void getIdealSize (int& idealWidth, int& idealHeight) override
 -     {
 -         idealWidth = width;
 -         idealHeight = height;
 -     }
 - 
 -     void resized() override
 -     {
 -         if (auto* child = getChildComponent (0))
 -             child->setBounds (getLocalBounds());
 -     }
 - 
 -     const int width, height;
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NormalComponentWrapper)
 - };
 - 
 - };
 - 
 - //==============================================================================
 - PopupMenu::PopupMenu()
 - {
 - }
 - 
 - PopupMenu::PopupMenu (const PopupMenu& other)
 -     : lookAndFeel (other.lookAndFeel)
 - {
 -     items.addCopiesOf (other.items);
 - }
 - 
 - PopupMenu& PopupMenu::operator= (const PopupMenu& other)
 - {
 -     if (this != &other)
 -     {
 -         lookAndFeel = other.lookAndFeel;
 - 
 -         clear();
 -         items.addCopiesOf (other.items);
 -     }
 - 
 -     return *this;
 - }
 - 
 - PopupMenu::PopupMenu (PopupMenu&& other) noexcept
 -     : lookAndFeel (other.lookAndFeel)
 - {
 -     items.swapWith (other.items);
 - }
 - 
 - PopupMenu& PopupMenu::operator= (PopupMenu&& other) noexcept
 - {
 -     jassert (this != &other); // hopefully the compiler should make this situation impossible!
 - 
 -     items.swapWith (other.items);
 -     lookAndFeel = other.lookAndFeel;
 -     return *this;
 - }
 - 
 - PopupMenu::~PopupMenu()
 - {
 - }
 - 
 - void PopupMenu::clear()
 - {
 -     items.clear();
 - }
 - 
 - //==============================================================================
 - PopupMenu::Item::Item() noexcept
 - {
 - }
 - 
 - PopupMenu::Item::Item (const Item& other)
 -   : text (other.text),
 -     itemID (other.itemID),
 -     subMenu (createCopyIfNotNull (other.subMenu.get())),
 -     image (other.image != nullptr ? other.image->createCopy() : nullptr),
 -     customComponent (other.customComponent),
 -     customCallback (other.customCallback),
 -     commandManager (other.commandManager),
 -     shortcutKeyDescription (other.shortcutKeyDescription),
 -     colour (other.colour),
 -     isEnabled (other.isEnabled),
 -     isTicked (other.isTicked),
 -     isSeparator (other.isSeparator),
 -     isSectionHeader (other.isSectionHeader)
 - {
 - }
 - 
 - PopupMenu::Item& PopupMenu::Item::operator= (const Item& other)
 - {
 -     text = other.text;
 -     itemID = other.itemID;
 -     subMenu.reset (createCopyIfNotNull (other.subMenu.get()));
 -     image.reset (other.image != nullptr ? other.image->createCopy() : nullptr);
 -     customComponent = other.customComponent;
 -     customCallback = other.customCallback;
 -     commandManager = other.commandManager;
 -     shortcutKeyDescription = other.shortcutKeyDescription;
 -     colour = other.colour;
 -     isEnabled = other.isEnabled;
 -     isTicked = other.isTicked;
 -     isSeparator = other.isSeparator;
 -     isSectionHeader = other.isSectionHeader;
 -     return *this;
 - }
 - 
 - void PopupMenu::addItem (const Item& newItem)
 - {
 -     // An ID of 0 is used as a return value to indicate that the user
 -     // didn't pick anything, so you shouldn't use it as the ID for an item..
 -     jassert (newItem.itemID != 0
 -               || newItem.isSeparator || newItem.isSectionHeader
 -               || newItem.subMenu != nullptr);
 - 
 -     items.add (new Item (newItem));
 - }
 - 
 - void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked)
 - {
 -     Item i;
 -     i.text = itemText;
 -     i.itemID = itemResultID;
 -     i.isEnabled = isActive;
 -     i.isTicked = isTicked;
 -     addItem (i);
 - }
 - 
 - static Drawable* createDrawableFromImage (const Image& im)
 - {
 -     if (im.isValid())
 -     {
 -         auto d = new DrawableImage();
 -         d->setImage (im);
 -         return d;
 -     }
 - 
 -     return nullptr;
 - }
 - 
 - void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked, const Image& iconToUse)
 - {
 -     addItem (itemResultID, itemText, isActive, isTicked, createDrawableFromImage (iconToUse));
 - }
 - 
 - void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked, Drawable* iconToUse)
 - {
 -     Item i;
 -     i.text = itemText;
 -     i.itemID = itemResultID;
 -     i.isEnabled = isActive;
 -     i.isTicked = isTicked;
 -     i.image.reset (iconToUse);
 -     addItem (i);
 - }
 - 
 - void PopupMenu::addCommandItem (ApplicationCommandManager* commandManager,
 -                                 const CommandID commandID,
 -                                 const String& displayName,
 -                                 Drawable* iconToUse)
 - {
 -     jassert (commandManager != nullptr && commandID != 0);
 - 
 -     if (auto* registeredInfo = commandManager->getCommandForID (commandID))
 -     {
 -         ApplicationCommandInfo info (*registeredInfo);
 -         auto* target = commandManager->getTargetForCommand (commandID, info);
 - 
 -         Item i;
 -         i.text = displayName.isNotEmpty() ? displayName : info.shortName;
 -         i.itemID = (int) commandID;
 -         i.commandManager = commandManager;
 -         i.isEnabled = target != nullptr && (info.flags & ApplicationCommandInfo::isDisabled) == 0;
 -         i.isTicked = (info.flags & ApplicationCommandInfo::isTicked) != 0;
 -         i.image.reset (iconToUse);
 -         addItem (i);
 -     }
 - }
 - 
 - void PopupMenu::addColouredItem (int itemResultID, const String& itemText, Colour itemTextColour,
 -                                  bool isActive, bool isTicked, Drawable* iconToUse)
 - {
 -     Item i;
 -     i.text = itemText;
 -     i.itemID = itemResultID;
 -     i.colour = itemTextColour;
 -     i.isEnabled = isActive;
 -     i.isTicked = isTicked;
 -     i.image.reset (iconToUse);
 -     addItem (i);
 - }
 - 
 - void PopupMenu::addColouredItem (int itemResultID, const String& itemText, Colour itemTextColour,
 -                                  bool isActive, bool isTicked, const Image& iconToUse)
 - {
 -     Item i;
 -     i.text = itemText;
 -     i.itemID = itemResultID;
 -     i.colour = itemTextColour;
 -     i.isEnabled = isActive;
 -     i.isTicked = isTicked;
 -     i.image.reset (createDrawableFromImage (iconToUse));
 -     addItem (i);
 - }
 - 
 - void PopupMenu::addCustomItem (int itemResultID, CustomComponent* cc, const PopupMenu* subMenu)
 - {
 -     Item i;
 -     i.itemID = itemResultID;
 -     i.customComponent = cc;
 -     i.subMenu.reset (createCopyIfNotNull (subMenu));
 -     addItem (i);
 - }
 - 
 - void PopupMenu::addCustomItem (int itemResultID, Component* customComponent, int idealWidth, int idealHeight,
 -                                bool triggerMenuItemAutomaticallyWhenClicked, const PopupMenu* subMenu)
 - {
 -     addCustomItem (itemResultID,
 -                    new HelperClasses::NormalComponentWrapper (customComponent, idealWidth, idealHeight,
 -                                                               triggerMenuItemAutomaticallyWhenClicked),
 -                    subMenu);
 - }
 - 
 - void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, bool isActive)
 - {
 -     addSubMenu (subMenuName, subMenu, isActive, nullptr, false, 0);
 - }
 - 
 - void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, bool isActive,
 -                             const Image& iconToUse, bool isTicked, int itemResultID)
 - {
 -     addSubMenu (subMenuName, subMenu, isActive, createDrawableFromImage (iconToUse), isTicked, itemResultID);
 - }
 - 
 - void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, bool isActive,
 -                             Drawable* iconToUse, bool isTicked, int itemResultID)
 - {
 -     Item i;
 -     i.text = subMenuName;
 -     i.itemID = itemResultID;
 -     i.subMenu.reset (new PopupMenu (subMenu));
 -     i.isEnabled = isActive && (itemResultID != 0 || subMenu.getNumItems() > 0);
 -     i.isTicked = isTicked;
 -     i.image.reset (iconToUse);
 -     addItem (i);
 - }
 - 
 - void PopupMenu::addSeparator()
 - {
 -     if (items.size() > 0 && ! items.getLast()->isSeparator)
 -     {
 -         Item i;
 -         i.isSeparator = true;
 -         addItem (i);
 -     }
 - }
 - 
 - void PopupMenu::addSectionHeader (const String& title)
 - {
 -     Item i;
 -     i.text = title;
 -     i.isSectionHeader = true;
 -     addItem (i);
 - }
 - 
 - //==============================================================================
 - PopupMenu::Options::Options()
 - {
 -     targetArea.setPosition (Desktop::getMousePosition());
 - }
 - 
 - PopupMenu::Options PopupMenu::Options::withTargetComponent (Component* comp) const noexcept
 - {
 -     Options o (*this);
 -     o.targetComponent = comp;
 - 
 -     if (comp != nullptr)
 -         o.targetArea = comp->getScreenBounds();
 - 
 -     return o;
 - }
 - 
 - PopupMenu::Options PopupMenu::Options::withTargetScreenArea (Rectangle<int> area) const noexcept
 - {
 -     Options o (*this);
 -     o.targetArea = area;
 -     return o;
 - }
 - 
 - PopupMenu::Options PopupMenu::Options::withMinimumWidth (int w) const noexcept
 - {
 -     Options o (*this);
 -     o.minWidth = w;
 -     return o;
 - }
 - 
 - PopupMenu::Options PopupMenu::Options::withMinimumNumColumns (int cols) const noexcept
 - {
 -     Options o (*this);
 -     o.minColumns = cols;
 -     return o;
 - }
 - 
 - PopupMenu::Options PopupMenu::Options::withMaximumNumColumns (int cols) const noexcept
 - {
 -     Options o (*this);
 -     o.maxColumns = cols;
 -     return o;
 - }
 - 
 - PopupMenu::Options PopupMenu::Options::withStandardItemHeight (int height) const noexcept
 - {
 -     Options o (*this);
 -     o.standardHeight = height;
 -     return o;
 - }
 - 
 - PopupMenu::Options PopupMenu::Options::withItemThatMustBeVisible (int idOfItemToBeVisible) const noexcept
 - {
 -     Options o (*this);
 -     o.visibleItemID = idOfItemToBeVisible;
 -     return o;
 - }
 - 
 - PopupMenu::Options PopupMenu::Options::withParentComponent (Component* parent) const noexcept
 - {
 -     Options o (*this);
 -     o.parentComponent = parent;
 -     return o;
 - }
 - 
 - PopupMenu::Options PopupMenu::Options::withPreferredPopupDirection (PopupDirection direction) const noexcept
 - {
 -     Options o (*this);
 -     o.preferredPopupDirection = direction;
 -     return o;
 - }
 - 
 - Component* PopupMenu::createWindow (const Options& options,
 -                                     ApplicationCommandManager** managerOfChosenCommand) const
 - {
 -     return items.isEmpty() ? nullptr
 -                            : new HelperClasses::MenuWindow (*this, nullptr, options,
 -                                                             ! options.getTargetScreenArea().isEmpty(),
 -                                                             ModifierKeys::currentModifiers.isAnyMouseButtonDown(),
 -                                                             managerOfChosenCommand);
 - }
 - 
 - //==============================================================================
 - // This invokes any command manager commands and deletes the menu window when it is dismissed
 - struct PopupMenuCompletionCallback  : public ModalComponentManager::Callback
 - {
 -     PopupMenuCompletionCallback()
 -         : prevFocused (Component::getCurrentlyFocusedComponent()),
 -           prevTopLevel (prevFocused != nullptr ? prevFocused->getTopLevelComponent() : nullptr)
 -     {
 -         PopupMenuSettings::menuWasHiddenBecauseOfAppChange = false;
 -     }
 - 
 -     void modalStateFinished (int result) override
 -     {
 -         if (managerOfChosenCommand != nullptr && result != 0)
 -         {
 -             ApplicationCommandTarget::InvocationInfo info (result);
 -             info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;
 - 
 -             managerOfChosenCommand->invoke (info, true);
 -         }
 - 
 -         // (this would be the place to fade out the component, if that's what's required)
 -         component.reset();
 - 
 -         if (! PopupMenuSettings::menuWasHiddenBecauseOfAppChange)
 -         {
 -             if (prevTopLevel != nullptr)
 -                 prevTopLevel->toFront (true);
 - 
 -             if (prevFocused != nullptr && prevFocused->isShowing())
 -                 prevFocused->grabKeyboardFocus();
 -         }
 -     }
 - 
 -     ApplicationCommandManager* managerOfChosenCommand = nullptr;
 -     std::unique_ptr<Component> component;
 -     WeakReference<Component> prevFocused, prevTopLevel;
 - 
 -     JUCE_DECLARE_NON_COPYABLE (PopupMenuCompletionCallback)
 - };
 - 
 - int PopupMenu::showWithOptionalCallback (const Options& options, ModalComponentManager::Callback* const userCallback,
 -                                          const bool canBeModal)
 - {
 -     std::unique_ptr<ModalComponentManager::Callback> userCallbackDeleter (userCallback);
 -     std::unique_ptr<PopupMenuCompletionCallback> callback (new PopupMenuCompletionCallback());
 - 
 -     if (auto* window = createWindow (options, &(callback->managerOfChosenCommand)))
 -     {
 -         callback->component.reset (window);
 - 
 -         window->setVisible (true); // (must be called before enterModalState on Windows to avoid DropShadower confusion)
 -         window->enterModalState (false, userCallbackDeleter.release());
 -         ModalComponentManager::getInstance()->attachCallback (window, callback.release());
 - 
 -         window->toFront (false);  // need to do this after making it modal, or it could
 -                                   // be stuck behind other comps that are already modal..
 - 
 -        #if JUCE_MODAL_LOOPS_PERMITTED
 -         if (userCallback == nullptr && canBeModal)
 -             return window->runModalLoop();
 -        #else
 -         ignoreUnused (canBeModal);
 -         jassert (! (userCallback == nullptr && canBeModal));
 -        #endif
 -     }
 - 
 -     return 0;
 - }
 - 
 - //==============================================================================
 - #if JUCE_MODAL_LOOPS_PERMITTED
 - int PopupMenu::showMenu (const Options& options)
 - {
 -     return showWithOptionalCallback (options, nullptr, true);
 - }
 - #endif
 - 
 - void PopupMenu::showMenuAsync (const Options& options, ModalComponentManager::Callback* userCallback)
 - {
 -    #if ! JUCE_MODAL_LOOPS_PERMITTED
 -     jassert (userCallback != nullptr);
 -    #endif
 - 
 -     showWithOptionalCallback (options, userCallback, false);
 - }
 - 
 - void PopupMenu::showMenuAsync (const Options& options, std::function<void(int)> userCallback)
 - {
 -     showWithOptionalCallback (options, ModalCallbackFunction::create (userCallback), false);
 - }
 - 
 - //==============================================================================
 - #if JUCE_MODAL_LOOPS_PERMITTED
 - int PopupMenu::show (int itemIDThatMustBeVisible, int minimumWidth,
 -                      int maximumNumColumns, int standardItemHeight,
 -                      ModalComponentManager::Callback* callback)
 - {
 -     return showWithOptionalCallback (Options().withItemThatMustBeVisible (itemIDThatMustBeVisible)
 -                                               .withMinimumWidth (minimumWidth)
 -                                               .withMaximumNumColumns (maximumNumColumns)
 -                                               .withStandardItemHeight (standardItemHeight),
 -                                      callback, true);
 - }
 - 
 - int PopupMenu::showAt (Rectangle<int> screenAreaToAttachTo,
 -                        int itemIDThatMustBeVisible, int minimumWidth,
 -                        int maximumNumColumns, int standardItemHeight,
 -                        ModalComponentManager::Callback* callback)
 - {
 -     return showWithOptionalCallback (Options().withTargetScreenArea (screenAreaToAttachTo)
 -                                               .withItemThatMustBeVisible (itemIDThatMustBeVisible)
 -                                               .withMinimumWidth (minimumWidth)
 -                                               .withMaximumNumColumns (maximumNumColumns)
 -                                               .withStandardItemHeight (standardItemHeight),
 -                                      callback, true);
 - }
 - 
 - int PopupMenu::showAt (Component* componentToAttachTo,
 -                        int itemIDThatMustBeVisible, int minimumWidth,
 -                        int maximumNumColumns, int standardItemHeight,
 -                        ModalComponentManager::Callback* callback)
 - {
 -     auto options = Options().withItemThatMustBeVisible (itemIDThatMustBeVisible)
 -                             .withMinimumWidth (minimumWidth)
 -                             .withMaximumNumColumns (maximumNumColumns)
 -                             .withStandardItemHeight (standardItemHeight);
 - 
 -     if (componentToAttachTo != nullptr)
 -         options = options.withTargetComponent (componentToAttachTo);
 - 
 -     return showWithOptionalCallback (options, callback, true);
 - }
 - #endif
 - 
 - bool JUCE_CALLTYPE PopupMenu::dismissAllActiveMenus()
 - {
 -     auto& windows = HelperClasses::MenuWindow::getActiveWindows();
 -     auto numWindows = windows.size();
 - 
 -     for (int i = numWindows; --i >= 0;)
 -     {
 -         if (auto* pmw = windows[i])
 -         {
 -             pmw->setLookAndFeel (nullptr);
 -             pmw->dismissMenu (nullptr);
 -         }
 -     }
 - 
 -     return numWindows > 0;
 - }
 - 
 - //==============================================================================
 - int PopupMenu::getNumItems() const noexcept
 - {
 -     int num = 0;
 - 
 -     for (auto* mi : items)
 -         if (! mi->isSeparator)
 -             ++num;
 - 
 -     return num;
 - }
 - 
 - bool PopupMenu::containsCommandItem (const int commandID) const
 - {
 -     for (auto* mi : items)
 -         if ((mi->itemID == commandID && mi->commandManager != nullptr)
 -               || (mi->subMenu != nullptr && mi->subMenu->containsCommandItem (commandID)))
 -             return true;
 - 
 -     return false;
 - }
 - 
 - bool PopupMenu::containsAnyActiveItems() const noexcept
 - {
 -     for (auto* mi : items)
 -     {
 -         if (mi->subMenu != nullptr)
 -         {
 -             if (mi->subMenu->containsAnyActiveItems())
 -                 return true;
 -         }
 -         else if (mi->isEnabled)
 -         {
 -             return true;
 -         }
 -     }
 - 
 -     return false;
 - }
 - 
 - void PopupMenu::setLookAndFeel (LookAndFeel* const newLookAndFeel)
 - {
 -     lookAndFeel = newLookAndFeel;
 - }
 - 
 - //==============================================================================
 - PopupMenu::CustomComponent::CustomComponent (bool autoTrigger)
 -     : triggeredAutomatically (autoTrigger)
 - {
 - }
 - 
 - PopupMenu::CustomComponent::~CustomComponent()
 - {
 - }
 - 
 - void PopupMenu::CustomComponent::setHighlighted (bool shouldBeHighlighted)
 - {
 -     isHighlighted = shouldBeHighlighted;
 -     repaint();
 - }
 - 
 - void PopupMenu::CustomComponent::triggerMenuItem()
 - {
 -     if (auto* mic = findParentComponentOfClass<HelperClasses::ItemComponent>())
 -     {
 -         if (auto* pmw = mic->findParentComponentOfClass<HelperClasses::MenuWindow>())
 -         {
 -             pmw->dismissMenu (&mic->item);
 -         }
 -         else
 -         {
 -             // something must have gone wrong with the component hierarchy if this happens..
 -             jassertfalse;
 -         }
 -     }
 -     else
 -     {
 -         // why isn't this component inside a menu? Not much point triggering the item if
 -         // there's no menu.
 -         jassertfalse;
 -     }
 - }
 - 
 - //==============================================================================
 - PopupMenu::CustomCallback::CustomCallback() {}
 - PopupMenu::CustomCallback::~CustomCallback() {}
 - 
 - //==============================================================================
 - PopupMenu::MenuItemIterator::MenuItemIterator (const PopupMenu& m, bool recurse) : searchRecursively (recurse)
 - {
 -     index.add (0);
 -     menus.add (&m);
 - }
 - 
 - PopupMenu::MenuItemIterator::~MenuItemIterator() {}
 - 
 - bool PopupMenu::MenuItemIterator::next()
 - {
 -     if (index.size() == 0 || menus.getLast()->items.size() == 0)
 -         return false;
 - 
 -     currentItem = menus.getLast()->items.getUnchecked (index.getLast());
 - 
 -     if (searchRecursively && currentItem->subMenu != nullptr)
 -     {
 -         index.add (0);
 -         menus.add (currentItem->subMenu.get());
 -     }
 -     else
 -     {
 -         index.setUnchecked (index.size() - 1, index.getLast() + 1);
 -     }
 - 
 -     while (index.size() > 0 && index.getLast() >= menus.getLast()->items.size())
 -     {
 -         index.removeLast();
 -         menus.removeLast();
 - 
 -         if (index.size() > 0)
 -             index.setUnchecked (index.size() - 1, index.getLast() + 1);
 -     }
 - 
 -     return true;
 - }
 - 
 - PopupMenu::Item& PopupMenu::MenuItemIterator::getItem() const noexcept
 - {
 -     jassert (currentItem != nullptr);
 -     return *(currentItem);
 - }
 - 
 - } // namespace juce
 
 
  |