text: /*
    ==============================================================================
  
     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();
  
              m.showMenuAsync (PopupMenu::Options().withTargetComponent (this)
                                                   .withTargetScreenArea (localAreaToGlobal (itemBounds))
                                                   .withMinimumWidth (itemBounds.getWidth()),
                               [this, index] (int result) { menuDismissed (index, result); });
          }
      }
  }
  
  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
  