|  | /*
  ==============================================================================
   This file is part of the JUCE library.
   Copyright (c) 2020 - Raw Material Software Limited
   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 6 End-User License
   Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
   End User License Agreement: www.juce.com/juce-6-licence
   Privacy Policy: www.juce.com/juce-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
{
class MenuBarComponent::AccessibleItemComponent  : public Component
{
public:
    AccessibleItemComponent (MenuBarComponent& comp, const String& menuItemName)
        : owner (comp),
          name (menuItemName)
    {
        setInterceptsMouseClicks (false, false);
    }
    const String& getName() const noexcept    { return name; }
private:
    std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
    {
        class ComponentHandler  : public AccessibilityHandler
        {
        public:
            explicit ComponentHandler (AccessibleItemComponent& item)
                : AccessibilityHandler (item,
                                        AccessibilityRole::menuItem,
                                        getAccessibilityActions (item)),
                  itemComponent (item)
            {
            }
            String getTitle() const override  { return itemComponent.name; }
        private:
            static AccessibilityActions getAccessibilityActions (AccessibleItemComponent& item)
            {
                auto showMenu = [&item] { item.owner.showMenu (item.owner.indexOfItemComponent (&item)); };
                return AccessibilityActions().addAction (AccessibilityActionType::focus,
                                                         [&item] { item.owner.setItemUnderMouse (item.owner.indexOfItemComponent (&item)); })
                                             .addAction (AccessibilityActionType::press,    showMenu)
                                             .addAction (AccessibilityActionType::showMenu, showMenu);
            }
            AccessibleItemComponent& itemComponent;
        };
        return std::make_unique<ComponentHandler> (*this);
    }
    MenuBarComponent& owner;
    const String name;
};
MenuBarComponent::MenuBarComponent (MenuBarModel* m)
{
    setRepaintsOnMouseActivity (true);
    setWantsKeyboardFocus (false);
    setMouseClickGrabsKeyboardFocus (false);
    setModel (m);
}
MenuBarComponent::~MenuBarComponent()
{
    setModel (nullptr);
    Desktop::getInstance().removeGlobalMouseListener (this);
}
MenuBarModel* MenuBarComponent::getModel() const noexcept
{
    return model;
}
void MenuBarComponent::setModel (MenuBarModel* const newModel)
{
    if (model != newModel)
    {
        if (model != nullptr)
            model->removeListener (this);
        model = newModel;
        if (model != nullptr)
            model->addListener (this);
        repaint();
        menuBarItemsChanged (nullptr);
    }
}
//==============================================================================
void MenuBarComponent::paint (Graphics& g)
{
    const auto isMouseOverBar = (currentPopupIndex >= 0 || itemUnderMouse >= 0 || isMouseOver());
    getLookAndFeel().drawMenuBarBackground (g, getWidth(), getHeight(), isMouseOverBar, *this);
    if (model == nullptr)
        return;
    for (size_t i = 0; i < itemComponents.size(); ++i)
    {
        const auto& itemComponent = itemComponents[i];
        const auto itemBounds = itemComponent->getBounds();
        Graphics::ScopedSaveState ss (g);
        g.setOrigin (itemBounds.getX(), 0);
        g.reduceClipRegion (0, 0, itemBounds.getWidth(), itemBounds.getHeight());
        getLookAndFeel().drawMenuBarItem (g,
                                          itemBounds.getWidth(),
                                          itemBounds.getHeight(),
                                          (int) i,
                                          itemComponent->getName(),
                                          (int) i == itemUnderMouse,
                                          (int) i == currentPopupIndex,
                                          isMouseOverBar,
                                          *this);
    }
}
void MenuBarComponent::resized()
{
    int x = 0;
    for (size_t i = 0; i < itemComponents.size(); ++i)
    {
        auto& itemComponent = itemComponents[i];
        auto w = getLookAndFeel().getMenuBarItemWidth (*this, (int) i, itemComponent->getName());
        itemComponent->setBounds (x, 0, w, getHeight());
        x += w;
    }
}
int MenuBarComponent::getItemAt (Point<int> p)
{
    for (size_t i = 0; i < itemComponents.size(); ++i)
        if (itemComponents[i]->getBounds().contains (p) && reallyContains (p, true))
            return (int) i;
    return -1;
}
void MenuBarComponent::repaintMenuItem (int index)
{
    if (isPositiveAndBelow (index, (int) itemComponents.size()))
    {
        auto itemBounds = itemComponents[(size_t) index]->getBounds();
        repaint (itemBounds.getX() - 2,
                 0,
                 itemBounds.getWidth() + 4,
                 itemBounds.getHeight());
    }
}
void MenuBarComponent::setItemUnderMouse (int index)
{
    if (itemUnderMouse == index)
        return;
    repaintMenuItem (itemUnderMouse);
    itemUnderMouse = index;
    repaintMenuItem (itemUnderMouse);
    if (isPositiveAndBelow (itemUnderMouse, (int) itemComponents.size()))
        if (auto* handler = itemComponents[(size_t) itemUnderMouse]->getAccessibilityHandler())
            handler->grabFocus();
}
void MenuBarComponent::setOpenItem (int index)
{
    if (currentPopupIndex != index)
    {
        if (currentPopupIndex < 0 && index >= 0)
            model->handleMenuBarActivate (true);
        else if (currentPopupIndex >= 0 && index < 0)
            model->handleMenuBarActivate (false);
        repaintMenuItem (currentPopupIndex);
        currentPopupIndex = index;
        repaintMenuItem (currentPopupIndex);
        auto& desktop = Desktop::getInstance();
        if (index >= 0)
            desktop.addGlobalMouseListener (this);
        else
            desktop.removeGlobalMouseListener (this);
    }
}
void MenuBarComponent::updateItemUnderMouse (Point<int> p)
{
    setItemUnderMouse (getItemAt (p));
}
void MenuBarComponent::showMenu (int index)
{
    if (index != currentPopupIndex)
    {
        PopupMenu::dismissAllActiveMenus();
        menuBarItemsChanged (nullptr);
        setOpenItem (index);
        setItemUnderMouse (index);
        if (isPositiveAndBelow (index, (int) itemComponents.size()))
        {
            const auto& itemComponent = itemComponents[(size_t) index];
            auto m = model->getMenuForIndex (itemUnderMouse, itemComponent->getName());
            if (m.lookAndFeel == nullptr)
                m.setLookAndFeel (&getLookAndFeel());
            auto itemBounds = itemComponent->getBounds();
            const auto callback = [ref = SafePointer<MenuBarComponent> (this), index] (int result)
            {
                if (ref != nullptr)
                    ref->menuDismissed (index, result);
            };
            m.showMenuAsync (PopupMenu::Options().withTargetComponent (this)
                                                 .withTargetScreenArea (localAreaToGlobal (itemBounds))
                                                 .withMinimumWidth (itemBounds.getWidth()),
                             callback);
        }
    }
}
void MenuBarComponent::menuDismissed (int topLevelIndex, int itemId)
{
    topLevelIndexClicked = topLevelIndex;
    postCommandMessage (itemId);
}
void MenuBarComponent::handleCommandMessage (int commandId)
{
    updateItemUnderMouse (getMouseXYRelative());
    if (currentPopupIndex == topLevelIndexClicked)
        setOpenItem (-1);
    if (commandId != 0 && model != nullptr)
        model->menuItemSelected (commandId, topLevelIndexClicked);
}
//==============================================================================
void MenuBarComponent::mouseEnter (const MouseEvent& e)
{
    if (e.eventComponent == this)
        updateItemUnderMouse (e.getPosition());
}
void MenuBarComponent::mouseExit (const MouseEvent& e)
{
    if (e.eventComponent == this)
        updateItemUnderMouse (e.getPosition());
}
void MenuBarComponent::mouseDown (const MouseEvent& e)
{
    if (currentPopupIndex < 0)
    {
        updateItemUnderMouse (e.getEventRelativeTo (this).getPosition());
        currentPopupIndex = -2;
        showMenu (itemUnderMouse);
    }
}
void MenuBarComponent::mouseDrag (const MouseEvent& e)
{
    const auto item = getItemAt (e.getEventRelativeTo (this).getPosition());
    if (item >= 0)
        showMenu (item);
}
void MenuBarComponent::mouseUp (const MouseEvent& e)
{
    const auto e2 = e.getEventRelativeTo (this);
    updateItemUnderMouse (e2.getPosition());
    if (itemUnderMouse < 0 && getLocalBounds().contains (e2.x, e2.y))
    {
        setOpenItem (-1);
        PopupMenu::dismissAllActiveMenus();
    }
}
void MenuBarComponent::mouseMove (const MouseEvent& e)
{
    const auto e2 = e.getEventRelativeTo (this);
    if (lastMousePos != e2.getPosition())
    {
        if (currentPopupIndex >= 0)
        {
            const auto item = getItemAt (e2.getPosition());
            if (item >= 0)
                showMenu (item);
        }
        else
        {
            updateItemUnderMouse (e2.getPosition());
        }
        lastMousePos = e2.getPosition();
    }
}
bool MenuBarComponent::keyPressed (const KeyPress& key)
{
    const auto numMenus = (int) itemComponents.size();
    if (numMenus > 0)
    {
        const auto currentIndex = jlimit (0, numMenus - 1, currentPopupIndex);
        if (key.isKeyCode (KeyPress::leftKey))
        {
            showMenu ((currentIndex + numMenus - 1) % numMenus);
            return true;
        }
        if (key.isKeyCode (KeyPress::rightKey))
        {
            showMenu ((currentIndex + 1) % numMenus);
            return true;
        }
    }
    return false;
}
void MenuBarComponent::menuBarItemsChanged (MenuBarModel*)
{
    StringArray newNames;
    if (model != nullptr)
        newNames = model->getMenuBarNames();
    auto itemsHaveChanged = [this, &newNames]
    {
        if ((int) itemComponents.size() != newNames.size())
            return true;
        for (size_t i = 0; i < itemComponents.size(); ++i)
            if (itemComponents[i]->getName() != newNames[(int) i])
                return true;
        return false;
    }();
    if (itemsHaveChanged)
    {
        updateItemComponents (newNames);
        repaint();
        resized();
    }
}
void MenuBarComponent::updateItemComponents (const StringArray& menuNames)
{
    itemComponents.clear();
    for (const auto& name : menuNames)
    {
        itemComponents.push_back (std::make_unique<AccessibleItemComponent> (*this, name));
        addAndMakeVisible (*itemComponents.back());
    }
}
int MenuBarComponent::indexOfItemComponent (AccessibleItemComponent* itemComponent) const
{
    const auto iter = std::find_if (itemComponents.cbegin(), itemComponents.cend(),
                                    [itemComponent] (const std::unique_ptr<AccessibleItemComponent>& c) { return c.get() == itemComponent; });
    if (iter != itemComponents.cend())
        return (int) std::distance (itemComponents.cbegin(), iter);
    jassertfalse;
    return -1;
}
void MenuBarComponent::menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info)
{
    if (model == nullptr || (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) != 0)
        return;
    for (size_t i = 0; i < itemComponents.size(); ++i)
    {
        const auto menu = model->getMenuForIndex ((int) i, itemComponents[i]->getName());
        if (menu.containsCommandItem (info.commandID))
        {
            setItemUnderMouse ((int) i);
            startTimer (200);
            break;
        }
    }
}
void MenuBarComponent::timerCallback()
{
    stopTimer();
    updateItemUnderMouse (getMouseXYRelative());
}
//==============================================================================
std::unique_ptr<AccessibilityHandler> MenuBarComponent::createAccessibilityHandler()
{
    struct MenuBarComponentAccessibilityHandler  : public AccessibilityHandler
    {
        explicit MenuBarComponentAccessibilityHandler (MenuBarComponent& menuBarComponent)
            : AccessibilityHandler (menuBarComponent, AccessibilityRole::menuBar)
        {
        }
        AccessibleState getCurrentState() const override  { return AccessibleState().withIgnored(); }
    };
    return std::make_unique<MenuBarComponentAccessibilityHandler> (*this);
}
} // namespace juce
 |