// 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_MENU_RUNNER_H_
#define UI_VIEWS_CONTROLS_MENU_MENU_RUNNER_H_

#include <stdint.h>

#include <memory>
#include <optional>
#include <string>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "ui/base/mojom/menu_source_type.mojom-forward.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/views/controls/menu/menu_types.h"
#include "ui/views/views_export.h"

namespace base {
class TimeTicks;
}

namespace gfx {
class Rect;
}  // namespace gfx

namespace ui {
class MenuModel;
}

namespace views {

class MenuButtonController;
class MenuItemView;
class MenuRunnerHandler;
class Widget;

namespace internal {
class MenuRunnerImplInterface;
}

namespace test {
class MenuRunnerTestAPI;
}

// MenuRunner is responsible for showing (running) the menu and additionally
// owning the MenuItemView. It is safe to delete MenuRunner at any point, but
// MenuRunner will not notify you of the closure caused by a deletion.
// If MenuRunner is deleted while the menu is showing the delegate of the menu
// is reset. This is done to ensure delegates aren't notified after they may
// have been deleted.
//
// Similarly you should avoid creating MenuRunner on the stack. Doing so means
// MenuRunner may not be immediately destroyed if your object is destroyed,
// resulting in possible callbacks to your now deleted object. Instead you
// should define MenuRunner as a scoped_ptr in your class so that when your
// object is destroyed MenuRunner initiates the proper cleanup and ensures your
// object isn't accessed again.
class VIEWS_EXPORT MenuRunner {
 public:
  enum RunTypes {
    NO_FLAGS = 0,

    // The menu has mnemonics.
    HAS_MNEMONICS = 1 << 0,

    // The menu is a nested context menu. For example, click a folder on the
    // bookmark bar, then right click an entry to get its context menu.
    IS_NESTED = 1 << 1,

    // Used for showing a menu during a drop operation. This does NOT block the
    // caller, instead the delegate is notified when the menu closes via the
    // DropMenuClosed method.
    FOR_DROP = 1 << 2,

    // The menu is a context menu (not necessarily nested), for example right
    // click on a link on a website in the browser.
    CONTEXT_MENU = 1 << 3,

    // The menu should behave like a Windows native Combobox dropdow menu.
    // This behavior includes accepting the pending item and closing on F4.
    COMBOBOX = 1 << 4,

    // A child view is performing a drag-and-drop operation, so the menu should
    // stay open (even if it doesn't receive drag updated events). In this case,
    // the caller is responsible for closing the menu upon completion of the
    // drag-and-drop.
    NESTED_DRAG = 1 << 5,

    // Menu with fixed anchor position, so |MenuRunner| will not attempt to
    // adjust the anchor point. For example the context menu of shelf item.
    FIXED_ANCHOR = 1 << 6,

    // The menu's owner could be in the middle of a gesture when the menu opens
    // and can use this flag to continue the gesture. For example, Chrome OS's
    // shelf uses the flag to continue dragging an item without lifting the
    // finger after the context menu of the item is opened.
    SEND_GESTURE_EVENTS_TO_OWNER = 1 << 7,

    // Applying the system UI specific layout to the context menu. This should
    // be set for the context menu inside system UI only (e.g, the context menu
    // while right clicking the wallpaper of a ChromeBook). Thus, this can be
    // used to differentiate the context menu inside system UI from others. It
    // is useful if we want to customize some attributes of the context menu
    // inside system UI, e.g, colors.
    USE_ASH_SYS_UI_LAYOUT = 1 << 8,

    // Similar to COMBOBOX, but does not capture the mouse and lets some keys
    // propagate back to the parent so the combobox content can be edited even
    // while the menu is open.
    EDITABLE_COMBOBOX = 1 << 9,

    // Indicates that the menu should show mnemonics.
    SHOULD_SHOW_MNEMONICS = 1 << 10,

    // Indicates that the menu was invoked from the keyboard.
    INVOKED_FROM_KEYBOARD = 1 << 11,

    // The menu is a context menu (not necessarily nested) for a menu item, for
    // example right click on bookmark drop-down.
    MENU_ITEM_CONTEXT_MENU = 1 << 12,
  };

  // Creates a new MenuRunner, which may use a native menu if available.
  // |run_types| is a bitmask of RunTypes. If provided,
  // |on_menu_closed_callback| is invoked when the menu is closed.
  // The MenuModelDelegate of |menu_model| will be overwritten by this call.
  MenuRunner(ui::MenuModel* menu_model,
             int32_t run_types,
             base::RepeatingClosure on_menu_closed_callback =
                 base::RepeatingClosure());

  // Creates a runner for a custom-created toolkit-views menu.
  MenuRunner(std::unique_ptr<MenuItemView> menu, int32_t run_types);

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

  ~MenuRunner();

  // Runs the menu. MenuDelegate::OnMenuClosed will be notified of the results.
  // If `anchor` uses a `BUBBLE_..` type, the bounds will get determined by
  // using `bounds` as the thing to point at in screen coordinates.
  // `native_view_for_gestures` is a NativeView that is used for cases where the
  // surface hosting the menu has a different gfx::NativeView than the `parent`.
  // This is required to correctly route gesture events to the correct
  // NativeView in the cases where the surface hosting the menu is a
  // WebContents.
  // `corners` assigns the customized `RoundedCornersF` to the context menu. If
  // it's set, the passed in corner will be used to render each corner radius of
  // the context menu. This only works when using `USE_ASH_SYS_UI_LAYOUT`.
  // Note that this is a blocking call for a native menu on Mac. See
  // http://crbug.com/682544.
  // `show_menu_host_duration_histogram` is the name of the histogram measuring
  // time from when Widget::Show() is called to when the first frame is
  // presented for menu. It is recorded in MenuHost and it happens only when the
  // histogram name is non-empty.
  void RunMenuAt(Widget* parent,
                 MenuButtonController* button_controller,
                 const gfx::Rect& bounds,
                 MenuAnchorPosition anchor,
                 ui::mojom::MenuSourceType source_type,
                 gfx::NativeView native_view_for_gestures = gfx::NativeView(),
                 gfx::AcceleratedWidget parent_widget =
                     gfx::kNullAcceleratedWidget,
                 std::optional<gfx::RoundedCornersF> corners = std::nullopt,
                 std::optional<std::string> show_menu_host_duration_histogram =
                     std::nullopt);

  // Returns true if we're in a nested run loop running the menu.
  bool IsRunning() const;

  // Hides and cancels the menu. This does nothing if the menu is not open.
  void Cancel();

  // Returns the time from the event which closed the menu - or 0.
  base::TimeTicks closing_event_time() const;

 private:
  friend class test::MenuRunnerTestAPI;

  // Sets an implementation of RunMenuAt. This is intended to be used at test.
  void SetRunnerHandler(std::unique_ptr<MenuRunnerHandler> runner_handler);

  const int32_t run_types_;

  // We own this. No scoped_ptr because it is destroyed by calling Release().
  raw_ptr<internal::MenuRunnerImplInterface> impl_;

  // An implementation of RunMenuAt. This is usually NULL and ignored. If this
  // is not NULL, this implementation will be used.
  std::unique_ptr<MenuRunnerHandler> runner_handler_;
};

}  // namespace views

#endif  // UI_VIEWS_CONTROLS_MENU_MENU_RUNNER_H_