// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// 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_CONTROLLER_H_
#define UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_H_
#include "build/build_config.h"
#include <list>
#include <set>
#include <vector>
#include "base/compiler_specific.h"
#include "base/memory/scoped_ptr.h"
#include "base/timer/timer.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/platform/platform_event_dispatcher.h"
#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/menu/menu_delegate.h"
#include "ui/views/widget/widget_observer.h"
namespace base {
class MessagePumpDispatcher;
}
namespace gfx {
class Screen;
}
namespace ui {
class NativeTheme;
class OSExchangeData;
class ScopedEventDispatcher;
}
namespace views {
class MenuButton;
class MenuHostRootView;
class MenuItemView;
class MenuMessageLoop;
class MouseEvent;
class SubmenuView;
class View;
namespace internal {
class MenuControllerDelegate;
class MenuEventDispatcher;
class MenuMessagePumpDispatcher;
class MenuRunnerImpl;
}
// MenuController -------------------------------------------------------------
// MenuController is used internally by the various menu classes to manage
// showing, selecting and drag/drop for menus. All relevant events are
// forwarded to the MenuController from SubmenuView and MenuHost.
class VIEWS_EXPORT MenuController : public WidgetObserver {
public:
// Enumeration of how the menu should exit.
enum ExitType {
// Don't exit.
EXIT_NONE,
// All menus, including nested, should be exited.
EXIT_ALL,
// Only the outermost menu should be exited.
EXIT_OUTERMOST,
// This is set if the menu is being closed as the result of one of the menus
// being destroyed.
EXIT_DESTROYED
};
// If a menu is currently active, this returns the controller for it.
static MenuController* GetActiveInstance();
// Runs the menu at the specified location. If the menu was configured to
// block, the selected item is returned. If the menu does not block this
// returns NULL immediately.
MenuItemView* Run(Widget* parent,
MenuButton* button,
MenuItemView* root,
const gfx::Rect& bounds,
MenuAnchorPosition position,
bool context_menu,
int* event_flags);
// Whether or not Run blocks.
bool IsBlockingRun() const { return blocking_run_; }
// Whether or not drag operation is in progress.
bool drag_in_progress() const { return drag_in_progress_; }
// Returns the owner of child windows.
// WARNING: this may be NULL.
Widget* owner() { return owner_; }
// Get the anchor position wich is used to show this menu.
MenuAnchorPosition GetAnchorPosition() { return state_.anchor; }
// Cancels the current Run. See ExitType for a description of what happens
// with the various parameters.
void Cancel(ExitType type);
// An alternative to Cancel(EXIT_ALL) that can be used with a OneShotTimer.
void CancelAll() { Cancel(EXIT_ALL); }
// Returns the current exit type. This returns a value other than EXIT_NONE if
// the menu is being canceled.
ExitType exit_type() const { return exit_type_; }
// Returns the time from the event which closed the menu - or 0.
base::TimeDelta closing_event_time() const { return closing_event_time_; }
void set_is_combobox(bool is_combobox) { is_combobox_ = is_combobox; }
// Various events, forwarded from the submenu.
//
// NOTE: the coordinates of the events are in that of the
// MenuScrollViewContainer.
void OnMousePressed(SubmenuView* source, const ui::MouseEvent& event);
void OnMouseDragged(SubmenuView* source, const ui::MouseEvent& event);
void OnMouseReleased(SubmenuView* source, const ui::MouseEvent& event);
void OnMouseMoved(SubmenuView* source, const ui::MouseEvent& event);
void OnMouseEntered(SubmenuView* source, const ui::MouseEvent& event);
bool OnMouseWheel(SubmenuView* source, const ui::MouseWheelEvent& event);
void OnGestureEvent(SubmenuView* source, ui::GestureEvent* event);
bool GetDropFormats(
SubmenuView* source,
int* formats,
std::set<ui::OSExchangeData::CustomFormat>* custom_formats);
bool AreDropTypesRequired(SubmenuView* source);
bool CanDrop(SubmenuView* source, const ui::OSExchangeData& data);
void OnDragEntered(SubmenuView* source, const ui::DropTargetEvent& event);
int OnDragUpdated(SubmenuView* source, const ui::DropTargetEvent& event);
void OnDragExited(SubmenuView* source);
int OnPerformDrop(SubmenuView* source, const ui::DropTargetEvent& event);
// Invoked from the scroll buttons of the MenuScrollViewContainer.
void OnDragEnteredScrollButton(SubmenuView* source, bool is_up);
void OnDragExitedScrollButton(SubmenuView* source);
// Update the submenu's selection based on the current mouse location
void UpdateSubmenuSelection(SubmenuView* source);
// WidgetObserver overrides:
virtual void OnWidgetDestroying(Widget* widget) OVERRIDE;
// Only used for testing.
static void TurnOffMenuSelectionHoldForTest();
private:
friend class internal::MenuEventDispatcher;
friend class internal::MenuMessagePumpDispatcher;
friend class internal::MenuRunnerImpl;
friend class MenuControllerTest;
friend class MenuHostRootView;
friend class MenuItemView;
friend class SubmenuView;
class MenuScrollTask;
struct SelectByCharDetails;
// Values supplied to SetSelection.
enum SetSelectionTypes {
SELECTION_DEFAULT = 0,
// If set submenus are opened immediately, otherwise submenus are only
// openned after a timer fires.
SELECTION_UPDATE_IMMEDIATELY = 1 << 0,
// If set and the menu_item has a submenu, the submenu is shown.
SELECTION_OPEN_SUBMENU = 1 << 1,
// SetSelection is being invoked as the result exiting or cancelling the
// menu. This is used for debugging.
SELECTION_EXIT = 1 << 2,
};
// Result type for SendAcceleratorToHotTrackedView
enum SendAcceleratorResultType {
// Accelerator is not sent because of no hot tracked views.
ACCELERATOR_NOT_PROCESSED,
// Accelerator is sent to the hot tracked views.
ACCELERATOR_PROCESSED,
// Same as above and the accelerator causes the exit of the menu.
ACCELERATOR_PROCESSED_EXIT
};
// Tracks selection information.
struct State {
State();
~State();
// The selected menu item.
MenuItemView* item;
// If item has a submenu this indicates if the submenu is showing.
bool submenu_open;
// Bounds passed to the run menu. Used for positioning the first menu.
gfx::Rect initial_bounds;
// Position of the initial menu.
MenuAnchorPosition anchor;
// The direction child menus have opened in.
std::list<bool> open_leading;
// Bounds for the monitor we're showing on.
gfx::Rect monitor_bounds;
// Is the current menu a context menu.
bool context_menu;
};
// Used by GetMenuPart to indicate the menu part at a particular location.
struct MenuPart {
// Type of part.
enum Type {
NONE,
MENU_ITEM,
SCROLL_UP,
SCROLL_DOWN
};
MenuPart() : type(NONE), menu(NULL), parent(NULL), submenu(NULL) {}
// Convenience for testing type == SCROLL_DOWN or type == SCROLL_UP.
bool is_scroll() const { return type == SCROLL_DOWN || type == SCROLL_UP; }
// Type of part.
Type type;
// If type is MENU_ITEM, this is the menu item the mouse is over, otherwise
// this is NULL.
// NOTE: if type is MENU_ITEM and the mouse is not over a valid menu item
// but is over a menu (for example, the mouse is over a separator or
// empty menu), this is NULL and parent is the menu the mouse was
// clicked on.
MenuItemView* menu;
// If type is MENU_ITEM but the mouse is not over a menu item this is the
// parent of the menu item the user clicked on. Otherwise this is NULL.
MenuItemView* parent;
// This is the submenu the mouse is over.
SubmenuView* submenu;
};
// Sets the selection to |menu_item|. A value of NULL unselects
// everything. |types| is a bitmask of |SetSelectionTypes|.
//
// Internally this updates pending_state_ immediatley. state_ is only updated
// immediately if SELECTION_UPDATE_IMMEDIATELY is set. If
// SELECTION_UPDATE_IMMEDIATELY is not set CommitPendingSelection is invoked
// to show/hide submenus and update state_.
void SetSelection(MenuItemView* menu_item, int types);
void SetSelectionOnPointerDown(SubmenuView* source,
const ui::LocatedEvent& event);
void StartDrag(SubmenuView* source, const gfx::Point& location);
// Key processing. The return value of this is returned from Dispatch.
// In other words, if this returns false (which happens if escape was
// pressed, or a matching mnemonic was found) the message loop returns.
bool OnKeyDown(ui::KeyboardCode key_code);
// Creates a MenuController. If |blocking| is true a nested message loop is
// started in |Run|.
MenuController(ui::NativeTheme* theme,
bool blocking,
internal::MenuControllerDelegate* delegate);
virtual ~MenuController();
// Runs the platform specific bits of the message loop. If |nested_menu| is
// true we're being asked to run a menu from within a menu (eg a context
// menu).
void RunMessageLoop(bool nested_menu);
// AcceleratorPressed is invoked on the hot tracked view if it exists.
SendAcceleratorResultType SendAcceleratorToHotTrackedView();
void UpdateInitialLocation(const gfx::Rect& bounds,
MenuAnchorPosition position,
bool context_menu);
// Invoked when the user accepts the selected item. This is only used
// when blocking. This schedules the loop to quit.
void Accept(MenuItemView* item, int event_flags);
bool ShowSiblingMenu(SubmenuView* source, const gfx::Point& mouse_location);
// Shows a context menu for |menu_item| as a result of a located event if
// appropriate. This is invoked on long press and releasing the right mouse
// button. Returns whether a context menu was shown.
bool ShowContextMenu(MenuItemView* menu_item,
SubmenuView* source,
const ui::LocatedEvent& event,
ui::MenuSourceType source_type);
// Closes all menus, including any menus of nested invocations of Run.
void CloseAllNestedMenus();
// Gets the enabled menu item at the specified location.
// If over_any_menu is non-null it is set to indicate whether the location
// is over any menu. It is possible for this to return NULL, but
// over_any_menu to be true. For example, the user clicked on a separator.
MenuItemView* GetMenuItemAt(View* menu, int x, int y);
// If there is an empty menu item at the specified location, it is returned.
MenuItemView* GetEmptyMenuItemAt(View* source, int x, int y);
// Returns true if the coordinate is over the scroll buttons of the
// SubmenuView's MenuScrollViewContainer. If true is returned, part is set to
// indicate which scroll button the coordinate is.
bool IsScrollButtonAt(SubmenuView* source,
int x,
int y,
MenuPart::Type* part);
// Returns the target for the mouse event. The coordinates are in terms of
// source's scroll view container.
MenuPart GetMenuPart(SubmenuView* source, const gfx::Point& source_loc);
// Returns the target for mouse events. The search is done through |item| and
// all its parents.
MenuPart GetMenuPartByScreenCoordinateUsingMenu(MenuItemView* item,
const gfx::Point& screen_loc);
// Implementation of GetMenuPartByScreenCoordinate for a single menu. Returns
// true if the supplied SubmenuView contains the location in terms of the
// screen. If it does, part is set appropriately and true is returned.
bool GetMenuPartByScreenCoordinateImpl(SubmenuView* menu,
const gfx::Point& screen_loc,
MenuPart* part);
// Returns true if the SubmenuView contains the specified location. This does
// NOT included the scroll buttons, only the submenu view.
bool DoesSubmenuContainLocation(SubmenuView* submenu,
const gfx::Point& screen_loc);
// Opens/Closes the necessary menus such that state_ matches that of
// pending_state_. This is invoked if submenus are not opened immediately,
// but after a delay.
void CommitPendingSelection();
// If item has a submenu, it is closed. This does NOT update the selection
// in anyway.
void CloseMenu(MenuItemView* item);
// If item has a submenu, it is opened. This does NOT update the selection
// in anyway.
void OpenMenu(MenuItemView* item);
// Implementation of OpenMenu. If |show| is true, this invokes show on the
// menu, otherwise Reposition is invoked.
void OpenMenuImpl(MenuItemView* item, bool show);
// Invoked when the children of a menu change and the menu is showing.
// This closes any submenus and resizes the submenu.
void MenuChildrenChanged(MenuItemView* item);
// Builds the paths of the two menu items into the two paths, and
// sets first_diff_at to the location of the first difference between the
// two paths.
void BuildPathsAndCalculateDiff(MenuItemView* old_item,
MenuItemView* new_item,
std::vector<MenuItemView*>* old_path,
std::vector<MenuItemView*>* new_path,
size_t* first_diff_at);
// Builds the path for the specified item.
void BuildMenuItemPath(MenuItemView* item, std::vector<MenuItemView*>* path);
// Starts/stops the timer that commits the pending state to state
// (opens/closes submenus).
void StartShowTimer();
void StopShowTimer();
// Starts/stops the timer cancel the menu. This is used during drag and
// drop when the drop enters/exits the menu.
void StartCancelAllTimer();
void StopCancelAllTimer();
// Calculates the bounds of the menu to show. is_leading is set to match the
// direction the menu opened in.
gfx::Rect CalculateMenuBounds(MenuItemView* item,
bool prefer_leading,
bool* is_leading);
// Calculates the bubble bounds of the menu to show. is_leading is set to
// match the direction the menu opened in.
gfx::Rect CalculateBubbleMenuBounds(MenuItemView* item,
bool prefer_leading,
bool* is_leading);
// Returns the depth of the menu.
static int MenuDepth(MenuItemView* item);
// Selects the next/previous menu item.
void IncrementSelection(int delta);
// Returns the next selectable child menu item of |parent| starting at |index|
// and incrementing index by |delta|. If there are no more selected menu items
// NULL is returned.
MenuItemView* FindNextSelectableMenuItem(MenuItemView* parent,
int index,
int delta);
// If the selected item has a submenu and it isn't currently open, the
// the selection is changed such that the menu opens immediately.
void OpenSubmenuChangeSelectionIfCan();
// If possible, closes the submenu.
void CloseSubmenu();
// Returns details about which menu items match the mnemonic |key|.
// |match_function| is used to determine which menus match.
SelectByCharDetails FindChildForMnemonic(
MenuItemView* parent,
base::char16 key,
bool (*match_function)(MenuItemView* menu, base::char16 mnemonic));
// Selects or accepts the appropriate menu item based on |details|. Returns
// true if |Accept| was invoked (which happens if there aren't multiple item
// with the same mnemonic and the item to select does not have a submenu).
bool AcceptOrSelect(MenuItemView* parent, const SelectByCharDetails& details);
// Selects by mnemonic, and if that doesn't work tries the first character of
// the title. Returns true if a match was selected and the menu should exit.
bool SelectByChar(base::char16 key);
// For Windows and Aura we repost an event for some events that dismiss
// the context menu. The event is then reprocessed to cause its result
// if the context menu had not been present.
// On non-aura Windows, a new mouse event is generated and posted to
// the window (if there is one) at the location of the event. On
// aura, the event is reposted on the RootWindow.
void RepostEvent(SubmenuView* source, const ui::LocatedEvent& event);
// Sets the drop target to new_item.
void SetDropMenuItem(MenuItemView* new_item,
MenuDelegate::DropPosition position);
// Starts/stops scrolling as appropriate. part gives the part the mouse is
// over.
void UpdateScrolling(const MenuPart& part);
// Stops scrolling.
void StopScrolling();
// Updates active mouse view from the location of the event and sends it
// the appropriate events. This is used to send mouse events to child views so
// that they react to click-drag-release as if the user clicked on the view
// itself.
void UpdateActiveMouseView(SubmenuView* event_source,
const ui::MouseEvent& event,
View* target_menu);
// Sends a mouse release event to the current active mouse view and sets
// it to null.
void SendMouseReleaseToActiveView(SubmenuView* event_source,
const ui::MouseEvent& event);
// Sends a mouse capture lost event to the current active mouse view and sets
// it to null.
void SendMouseCaptureLostToActiveView();
// Sets/gets the active mouse view. See UpdateActiveMouseView() for details.
void SetActiveMouseView(View* view);
View* GetActiveMouseView();
// Sets exit type. Calling this can terminate the active nested message-loop.
void SetExitType(ExitType type);
// Terminates the current nested message-loop.
void TerminateNestedMessageLoop();
// Returns true if SetExitType() should quit the message loop.
bool ShouldQuitNow() const;
// Handles the mouse location event on the submenu |source|.
void HandleMouseLocation(SubmenuView* source,
const gfx::Point& mouse_location);
// Retrieve an appropriate Screen.
gfx::Screen* GetScreen();
// The active instance.
static MenuController* active_instance_;
// If true, Run blocks. If false, Run doesn't block and this is used for
// drag and drop. Note that the semantics for drag and drop are slightly
// different: cancel timer is kicked off any time the drag moves outside the
// menu, mouse events do nothing...
bool blocking_run_;
// If true, we're showing.
bool showing_;
// Indicates what to exit.
ExitType exit_type_;
// Whether we did a capture. We do a capture only if we're blocking and
// the mouse was down when Run.
bool did_capture_;
// As the user drags the mouse around pending_state_ changes immediately.
// When the user stops moving/dragging the mouse (or clicks the mouse)
// pending_state_ is committed to state_, potentially resulting in
// opening or closing submenus. This gives a slight delayed effect to
// submenus as the user moves the mouse around. This is done so that as the
// user moves the mouse all submenus don't immediately pop.
State pending_state_;
State state_;
// If the user accepted the selection, this is the result.
MenuItemView* result_;
// The event flags when the user selected the menu.
int accept_event_flags_;
// If not empty, it means we're nested. When Run is invoked from within
// Run, the current state (state_) is pushed onto menu_stack_. This allows
// MenuController to restore the state when the nested run returns.
std::list<State> menu_stack_;
// As the mouse moves around submenus are not opened immediately. Instead
// they open after this timer fires.
base::OneShotTimer<MenuController> show_timer_;
// Used to invoke CancelAll(). This is used during drag and drop to hide the
// menu after the mouse moves out of the of the menu. This is necessitated by
// the lack of an ability to detect when the drag has completed from the drop
// side.
base::OneShotTimer<MenuController> cancel_all_timer_;
// Drop target.
MenuItemView* drop_target_;
MenuDelegate::DropPosition drop_position_;
// Owner of child windows.
// WARNING: this may be NULL.
Widget* owner_;
// Indicates a possible drag operation.
bool possible_drag_;
// True when drag operation is in progress.
bool drag_in_progress_;
// Location the mouse was pressed at. Used to detect d&d.
gfx::Point press_pt_;
// We get a slew of drag updated messages as the mouse is over us. To avoid
// continually processing whether we can drop, we cache the coordinates.
bool valid_drop_coordinates_;
gfx::Point drop_pt_;
int last_drop_operation_;
// If true, we're in the middle of invoking ShowAt on a submenu.
bool showing_submenu_;
// Task for scrolling the menu. If non-null indicates a scroll is currently
// underway.
scoped_ptr<MenuScrollTask> scroll_task_;
MenuButton* menu_button_;
// ViewStorage id used to store the view mouse drag events are forwarded to.
// See UpdateActiveMouseView() for details.
const int active_mouse_view_id_;
internal::MenuControllerDelegate* delegate_;
// How deep we are in nested message loops. This should be at most 2 (when
// showing a context menu from a menu).
int message_loop_depth_;
views::MenuConfig menu_config_;
// The timestamp of the event which closed the menu - or 0 otherwise.
base::TimeDelta closing_event_time_;
// Time when the menu is first shown.
base::TimeTicks menu_start_time_;
// If a mouse press triggered this menu, this will have its location (in
// screen coordinates). Otherwise this will be (0, 0).
gfx::Point menu_start_mouse_press_loc_;
// Controls behavior differences between a combobox and other types of menu
// (like a context menu).
bool is_combobox_;
// Set to true if the menu item was selected by touch.
bool item_selected_by_touch_;
scoped_ptr<MenuMessageLoop> message_loop_;
DISALLOW_COPY_AND_ASSIGN(MenuController);
};
} // namespace views
#endif // UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_H_