// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_VIEWS_CONTROLS_MENU_SUBMENU_VIEW_H_
#define UI_VIEWS_CONTROLS_MENU_SUBMENU_VIEW_H_

#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "ui/views/animation/scroll_animator.h"
#include "ui/views/controls/menu/menu_delegate.h"
#include "ui/views/controls/menu/menu_host.h"
#include "ui/views/controls/prefix_delegate.h"
#include "ui/views/controls/prefix_selector.h"
#include "ui/views/view.h"

namespace ui {
struct OwnedWindowAnchor;
}  // namespace ui

namespace views {

class MenuControllerTest;
class MenuItemView;
class MenuScrollViewContainer;

// SubmenuView is the parent of all menu items.
//
// SubmenuView has the following responsibilities:
// . It positions and sizes all child views (any type of View may be added,
//   not just MenuItemViews).
// . Forwards the appropriate events to the MenuController. This allows the
//   MenuController to update the selection as the user moves the mouse around.
// . Renders the drop indicator during a drop operation.
// . Shows and hides the window (a NativeWidget) when the menu is shown on
//   screen.
//
// SubmenuView is itself contained in a MenuScrollViewContainer.
// MenuScrollViewContainer handles showing as much of the SubmenuView as the
// screen allows. If the SubmenuView is taller than the screen, scroll buttons
// are provided that allow the user to see all the menu items.
class VIEWS_EXPORT SubmenuView : public View,
                                 public PrefixDelegate,
                                 public ScrollDelegate {
  METADATA_HEADER(SubmenuView, View)

 public:
  // Creates a SubmenuView for the specified menu item.
  explicit SubmenuView(MenuItemView* parent);

  SubmenuView(const SubmenuView&) = delete;
  SubmenuView& operator=(const SubmenuView&) = delete;

  ~SubmenuView() override;

  // Returns the children which are menu items.
  std::vector<MenuItemView*> GetMenuItems();
  std::vector<const MenuItemView*> GetMenuItems() const;

  // Returns the MenuItemView at the specified index.
  MenuItemView* GetMenuItemAt(size_t index);
  const MenuItemView* GetMenuItemAt(size_t index) const;

  // The preferred height, in DIPs, of a "standard" (i.e. empty) menu item.
  int GetPreferredItemHeight() const;

  PrefixSelector* GetPrefixSelector();

  // Sets various menu metrics based on the current children. For example, this
  // reserves space for menu icons iff any children have icons.
  void UpdateMenuPartSizes();

  // Positions and sizes the child views. This tiles the views vertically,
  // giving each child the available width.
  void Layout(PassKey) override;

  // TODO(crbug.com/40232718): Respect `available_size`.
  gfx::Size CalculatePreferredSize(
      const SizeBounds& /*available_size*/) const override;

  // Painting.
  void PaintChildren(const PaintInfo& paint_info) override;

  // Drag and drop methods. These are forwarded to the MenuController.
  bool GetDropFormats(int* formats,
                      std::set<ui::ClipboardFormatType>* format_types) override;
  bool AreDropTypesRequired() override;
  bool CanDrop(const OSExchangeData& data) override;
  void OnDragEntered(const ui::DropTargetEvent& event) override;
  int OnDragUpdated(const ui::DropTargetEvent& event) override;
  void OnDragExited() override;
  views::View::DropCallback GetDropCallback(
      const ui::DropTargetEvent& event) override;

  // Scrolls on menu item boundaries.
  bool OnMouseWheel(const ui::MouseWheelEvent& e) override;

  // Overridden from ui::EventHandler.
  // Scrolls on menu item boundaries.
  void OnGestureEvent(ui::GestureEvent* event) override;

  // Overridden from PrefixDelegate.
  size_t GetRowCount() override;
  std::optional<size_t> GetSelectedRow() override;
  void SetSelectedRow(std::optional<size_t> row) override;
  std::u16string GetTextForRow(size_t row) override;

  // Returns true if the menu is showing.
  virtual bool IsShowing() const;

  // Shows the menu using the specified |init_params|. |init_params.bounds| are
  // in screen coordinates.
  void ShowAt(const MenuHost::InitParams& init_params);

  // Resets the bounds of the submenu to |bounds| and its anchor to |anchor|.
  void Reposition(const gfx::Rect& bounds, const ui::OwnedWindowAnchor& anchor);

  // Closes the menu, destroying the host.
  void Close();

  // Hides the hosting window.
  //
  // The hosting window is hidden first, then deleted (Close) when the menu is
  // done running. This is done to avoid deletion ordering dependencies. In
  // particular, during drag and drop (and when a modal dialog is shown as
  // a result of choosing a context menu) it is possible that an event is
  // being processed by the host, so that host is on the stack when we need to
  // close the window. If we closed the window immediately (and deleted it),
  // when control returned back to host we would crash as host was deleted.
  void Hide();

  // If mouse capture was grabbed, it is released. Does nothing if mouse was
  // not captured.
  void ReleaseCapture();

  // Overriden from View to prevent tab from doing anything.
  bool SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) override;

  // Returns the parent menu item we're showing children for.
  const MenuItemView* GetMenuItem() const;
  MenuItemView* GetMenuItem() {
    return const_cast<MenuItemView*>(std::as_const(*this).GetMenuItem());
  }

  // Set the drop item and position.
  void SetDropMenuItem(MenuItemView* item, MenuDelegate::DropPosition position);

  // Returns whether the selection should be shown for the specified item.
  // The selection is NOT shown during drag and drop when the drop is over
  // the menu.
  bool GetShowSelection(const MenuItemView* item) const;

  // Returns the container for the SubmenuView.
  MenuScrollViewContainer* GetScrollViewContainer();

  // Returns the last MenuItemView in this submenu.
  MenuItemView* GetLastItem();
  const MenuItemView* GetLastItem() const;

  // Invoked if the menu is prematurely destroyed. This can happen if the window
  // closes while the menu is shown. If invoked the SubmenuView must drop all
  // references to the MenuHost as the MenuHost is about to be deleted.
  void MenuHostDestroyed();

  // Safely detaches the scroll view container and resets the MenuHost.
  void ReleaseMenuHost();

  int icon_area_width() const { return icon_area_width_; }
  int min_icon_height() const { return min_icon_height_; }
  int label_start() const { return label_start_; }
  int trailing_padding() const { return trailing_padding_; }

  // Max width of minor text (accelerator or subtitle) in child menu items. This
  // doesn't include children's children, only direct children.
  int max_minor_text_width() const { return max_minor_text_width_; }

  // Minimum width of menu in pixels (default 0).  This becomes the smallest
  // width returned by GetPreferredSize().
  void set_minimum_preferred_width(int minimum_preferred_width) {
    minimum_preferred_width_ = minimum_preferred_width;
  }

  // Automatically resize menu if a subview's preferred size changes.
  bool resize_open_menu() const { return resize_open_menu_; }
  void set_resize_open_menu(bool resize_open_menu) {
    resize_open_menu_ = resize_open_menu;
  }
  MenuHost* host() { return host_; }
  const MenuHost* host() const { return host_; }

  void SetBorderColorId(std::optional<ui::ColorId> color_id);

 protected:
  // View method. Overridden to schedule a paint. We do this so that when
  // scrolling occurs, everything is repainted correctly.
  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;

  void ChildPreferredSizeChanged(View* child) override;
  void ViewHierarchyChanged(
      const ViewHierarchyChangedDetails& details) override;

 private:
  friend class MenuControllerTest;

  void SchedulePaintForDropIndicator(MenuItemView* item,
                                     MenuDelegate::DropPosition position);

  // Calculates the location of th edrop indicator.
  gfx::Rect CalculateDropIndicatorBounds(MenuItemView* item,
                                         MenuDelegate::DropPosition position);

  // Implementation of ScrollDelegate
  bool OnScroll(float dx, float dy) override;

  // Parent menu item.
  raw_ptr<MenuItemView> parent_menu_item_;

  // Widget subclass used to show the children. This is deleted when we invoke
  // |DestroyMenuHost|, or |MenuHostDestroyed| is invoked back on us.
  raw_ptr<MenuHost> host_ = nullptr;

  // If non-null, indicates a drop is in progress and drop_item is the item
  // the drop is over.
  raw_ptr<MenuItemView> drop_item_ = nullptr;

  // Position of the drop.
  MenuDelegate::DropPosition drop_position_ = MenuDelegate::DropPosition::kNone;

  // Ancestor of the SubmenuView, lazily created.
  std::unique_ptr<MenuScrollViewContainer> detached_scroll_view_container_;
  raw_ptr<MenuScrollViewContainer> scroll_view_container_ = nullptr;

  // Width of a menu icon area.
  int icon_area_width_ = 0;

  // The minimum height items should reserve for icons. If any item has icons,
  // checks, or radios, this is set to kMenuCheckSize, which is also the
  // common-case size for icons. This ensures that
  //   * When no items have icons etc., we don't add unnecessary padding.
  //   * When some items have icons, we make ~all items "the same size"; but --
  //   * If any items have especially large icons, we don't add _too_ much
  //     padding to every item.
  // In other words, this tries to "have roughly consistent height" without
  // incurring a lot of extra padding that makes the menu look spaced-out.
  int min_icon_height_ = 0;

  // X-coordinate of where the label starts.
  int label_start_ = 0;

  // The width of the padding after the minor text. If there is a dedicated
  // submenu arrow column, it fits inside this.
  int trailing_padding_ = 0;

  // See description above getter.
  mutable int max_minor_text_width_ = 0;

  // Minimum width returned in GetPreferredSize().
  int minimum_preferred_width_ = 0;

  // Reposition open menu when contained views change size.
  bool resize_open_menu_ = false;

  // The submenu's scroll animator
  std::unique_ptr<ScrollAnimator> scroll_animator_{
      std::make_unique<ScrollAnimator>(this)};

  // Difference between current position and cumulative deltas passed to
  // OnScroll.
  // TODO(tdresser): This should be removed when raw pixel scrolling for views
  // is enabled. See crbug.com/329354.
  float roundoff_error_ = 0;

  PrefixSelector prefix_selector_{this, this};

  std::optional<ui::ColorId> border_color_id_;

  base::WeakPtrFactory<SubmenuView> weak_ptr_factory_{this};
};

}  // namespace views

#endif  // UI_VIEWS_CONTROLS_MENU_SUBMENU_VIEW_H_
