// 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 #include #include #include #include "base/compiler_specific.h" #include "base/macros.h" #include "base/memory/linked_ptr.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/button/menu_button.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 gfx { class Screen; } namespace ui { class OSExchangeData; class ScopedEventDispatcher; } namespace views { class MenuButton; class MenuHostRootView; class MenuItemView; class MenuMessageLoop; class MouseEvent; class SubmenuView; class View; #if defined(USE_AURA) class MenuKeyEventHandler; #endif namespace internal { class MenuControllerDelegate; class MenuRunnerImpl; } namespace test { class MenuControllerTest; } // 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, bool is_nested_drag, int* event_flags); // Whether or not Run blocks. bool IsBlockingRun() const { return blocking_run_; } bool in_nested_run() const { return !menu_stack_.empty(); } // Whether or not drag operation is in progress. bool drag_in_progress() const { return drag_in_progress_; } // Whether the MenuController initiated the drag in progress. False if there // is no drag in progress. bool did_initiate_drag() const { return did_initiate_drag_; } // 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); } // When is_nested_run() this will add a delegate to the stack. The most recent // delegate will be notified. It will be removed upon the exiting of the // nested menu. Ownership is not taken. void AddNestedDelegate(internal::MenuControllerDelegate* delegate); // Sets whether the subsequent call to Run is asynchronous. When nesting calls // to Run, if a new MenuControllerDelegate has been nested, the previous // asynchronous state will be reapplied once nesting has ended. void SetAsyncRun(bool is_async); // 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. bool OnMousePressed(SubmenuView* source, const ui::MouseEvent& event); bool 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); void OnTouchEvent(SubmenuView* source, ui::TouchEvent* event); View* GetTooltipHandlerForPoint(SubmenuView* source, const gfx::Point& point); void ViewHierarchyChanged(SubmenuView* source, const View::ViewHierarchyChangedDetails& details); bool GetDropFormats(SubmenuView* source, int* formats, std::set* format_types); 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); // Called by the Widget when a drag is about to start on a child view. This // could be initiated by one of our MenuItemViews, or could be through another // child View. void OnDragWillStart(); // Called by the Widget when the drag has completed. |should_close| // corresponds to whether or not the menu should close. void OnDragComplete(bool should_close); // Called while dispatching messages to intercept key events. // If |character| is other than 0, it is handled as a mnemonic. // Otherwise, |key_code| is handled as a menu navigation command. // Returns ui::POST_DISPATCH_NONE if the event was swallowed by the menu. ui::PostDispatchAction OnWillDispatchKeyEvent(base::char16 character, ui::KeyboardCode key_code); // Update the submenu's selection based on the current mouse location void UpdateSubmenuSelection(SubmenuView* source); // WidgetObserver overrides: void OnWidgetDestroying(Widget* widget) override; // Only used for testing. bool IsCancelAllTimerRunningForTest(); // Only used for testing. static void TurnOffMenuSelectionHoldForTest(); private: friend class internal::MenuRunnerImpl; friend class test::MenuControllerTest; friend class MenuKeyEventHandler; 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, }; // Direction for IncrementSelection and FindInitialSelectableMenuItem. enum SelectionIncrementDirectionType { // Navigate the menu up. INCREMENT_SELECTION_UP, // Navigate the menu down. INCREMENT_SELECTION_DOWN, }; // Tracks selection information. struct State { State(); State(const State& other); ~State(); // The selected menu item. MenuItemView* item; // Used to capture a hot tracked child button when a nested menu is opened // and to restore the hot tracked state when exiting a nested menu. CustomButton* hot_button; // 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 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. void OnKeyDown(ui::KeyboardCode key_code); // Creates a MenuController. If |blocking| is true a nested message loop is // started in |Run|. MenuController(bool blocking, internal::MenuControllerDelegate* delegate); ~MenuController() override; // 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); // Invokes AcceleratorPressed() on the hot tracked view if there is one. // Returns true if AcceleratorPressed() was invoked. bool 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 an event if // appropriate, using the given |screen_location|. This is invoked on long // press, releasing the right mouse button, and pressing the "app" key. // Returns whether a context menu was shown. bool ShowContextMenu(MenuItemView* menu_item, const gfx::Point& screen_location, 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 the RootView of the target for the mouse event, if there is a // target at |source_loc|. MenuHostRootView* GetRootView(SubmenuView* source, const gfx::Point& source_loc); // Converts the located event from |source|'s geometry to |dst|'s geometry, // iff the root view of source and dst differ. void ConvertLocatedEventForRootView(View* source, View* dst, ui::LocatedEvent* event); // 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* old_path, std::vector* new_path, size_t* first_diff_at); // Builds the path for the specified item. void BuildMenuItemPath(MenuItemView* item, std::vector* 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 or previous (depending on |direction|) menu item. void IncrementSelection(SelectionIncrementDirectionType direction); // Returns the first (|direction| == NAVIGATE_SELECTION_DOWN) or the last // (|direction| == INCREMENT_SELECTION_UP) selectable child menu item of // |parent|. If there are no selectable items returns NULL. MenuItemView* FindInitialSelectableMenuItem( MenuItemView* parent, SelectionIncrementDirectionType direction); // Returns the next or previous selectable child menu item of |parent| // starting at |index| and incrementing or decrementing index by 1 depending // on |direction|. If there are no more selectable items NULL is returned. MenuItemView* FindNextSelectableMenuItem( MenuItemView* parent, int index, SelectionIncrementDirectionType direction); // 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|. void AcceptOrSelect(MenuItemView* parent, const SelectByCharDetails& details); // Selects by mnemonic, and if that doesn't work tries the first character of // the title. void SelectByChar(base::char16 key); // For Windows and Aura we repost an event which dismisses the |source| menu. // The menu may also be canceled depending on the target of the event. |event| // is then processed without the menu 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 RepostEventAndCancel(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, if there is any. Returns |true| // if any message loop is terminated. bool TerminateNestedMessageLoopIfNecessary(); // Performs the teardown of menus launched with |async_run_|. This will // notifiy the |delegate_|. If |exit_type_| is EXIT_ALL all nested // asynchronous runs will be exited. void ExitAsyncRun(); // Performs the teardown of the menu launched by Run(). The selected item is // returned. MenuItemView* ExitMenuRun(); // Handles the mouse location event on the submenu |source|. void HandleMouseLocation(SubmenuView* source, const gfx::Point& mouse_location); // Sets hot-tracked state to the first focusable descendant view of |item|. void SetInitialHotTrackedView(MenuItemView* item, SelectionIncrementDirectionType direction); // Updates the current |hot_button_| and its hot tracked state. void SetHotTrackedButton(CustomButton* hot_button); // 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. typedef std::pair > NestedState; std::list menu_stack_; // When Run is invoked during an active Run, it may be called from a separate // MenuControllerDelegate. If not empty is means we are nested, and the // stacked delegates should be notified instead of |delegate_|. typedef std::pair NestedDelegate; std::list delegate_stack_; // As the mouse moves around submenus are not opened immediately. Instead // they open after this timer fires. base::OneShotTimer 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 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_; // True when the drag operation in progress was initiated by the // MenuController for a child MenuItemView (as opposed to initiated separately // by a child View). bool did_initiate_drag_; // 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 scroll_task_; // The lock to keep the menu button pressed while a menu is visible. scoped_ptr pressed_lock_; // ViewStorage id used to store the view mouse drag events are forwarded to. // See UpdateActiveMouseView() for details. const int active_mouse_view_id_; // Current hot tracked child button if any. CustomButton* hot_button_; 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_; // 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 behaviour differences between an asynchronous run, and other types // of run (blocking, drag and drop). bool async_run_; // 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_; // During mouse event handling, this is the RootView to forward mouse events // to. We need this, because if we forward one event to it (e.g., mouse // pressed), subsequent events (like dragging) should also go to it, even if // the mouse is no longer over the view. MenuHostRootView* current_mouse_event_target_; // A mask of the EventFlags for the mouse buttons currently pressed. int current_mouse_pressed_state_; scoped_ptr message_loop_; #if defined(USE_AURA) scoped_ptr key_event_handler_; #endif DISALLOW_COPY_AND_ASSIGN(MenuController); }; } // namespace views #endif // UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_H_