1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #ifndef UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_H_
6 #define UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_H_
8 #include "build/build_config.h"
14 #include "base/compiler_specific.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/message_loop/message_pump_dispatcher.h"
17 #include "base/timer/timer.h"
18 #include "ui/events/event_constants.h"
19 #include "ui/views/controls/menu/menu_delegate.h"
20 #include "ui/views/controls/menu/menu_item_view.h"
21 #include "ui/views/widget/widget_observer.h"
33 class MenuHostRootView;
39 class MenuControllerDelegate;
43 // MenuController -------------------------------------------------------------
45 // MenuController is used internally by the various menu classes to manage
46 // showing, selecting and drag/drop for menus. All relevant events are
47 // forwarded to the MenuController from SubmenuView and MenuHost.
48 class VIEWS_EXPORT MenuController : public base::MessagePumpDispatcher,
49 public WidgetObserver {
51 // Enumeration of how the menu should exit.
56 // All menus, including nested, should be exited.
59 // Only the outermost menu should be exited.
62 // This is set if the menu is being closed as the result of one of the menus
67 // If a menu is currently active, this returns the controller for it.
68 static MenuController* GetActiveInstance();
70 // Runs the menu at the specified location. If the menu was configured to
71 // block, the selected item is returned. If the menu does not block this
72 // returns NULL immediately.
73 MenuItemView* Run(Widget* parent,
76 const gfx::Rect& bounds,
77 MenuItemView::AnchorPosition position,
81 // Whether or not Run blocks.
82 bool IsBlockingRun() const { return blocking_run_; }
84 // Whether or not drag operation is in progress.
85 bool drag_in_progress() const { return drag_in_progress_; }
87 // Get the anchor position wich is used to show this menu.
88 MenuItemView::AnchorPosition GetAnchorPosition() { return state_.anchor; }
90 // Cancels the current Run. See ExitType for a description of what happens
91 // with the various parameters.
92 void Cancel(ExitType type);
94 // An alternative to Cancel(EXIT_ALL) that can be used with a OneShotTimer.
95 void CancelAll() { Cancel(EXIT_ALL); }
97 // Returns the current exit type. This returns a value other than EXIT_NONE if
98 // the menu is being canceled.
99 ExitType exit_type() const { return exit_type_; }
101 // Returns the time from the event which closed the menu - or 0.
102 base::TimeDelta closing_event_time() const { return closing_event_time_; }
104 void set_accept_on_f4(bool accept_on_f4) { accept_on_f4_ = accept_on_f4; }
106 // Various events, forwarded from the submenu.
108 // NOTE: the coordinates of the events are in that of the
109 // MenuScrollViewContainer.
110 void OnMousePressed(SubmenuView* source, const ui::MouseEvent& event);
111 void OnMouseDragged(SubmenuView* source, const ui::MouseEvent& event);
112 void OnMouseReleased(SubmenuView* source, const ui::MouseEvent& event);
113 void OnMouseMoved(SubmenuView* source, const ui::MouseEvent& event);
114 void OnMouseEntered(SubmenuView* source, const ui::MouseEvent& event);
115 bool OnMouseWheel(SubmenuView* source, const ui::MouseWheelEvent& event);
116 void OnGestureEvent(SubmenuView* source, ui::GestureEvent* event);
121 std::set<ui::OSExchangeData::CustomFormat>* custom_formats);
122 bool AreDropTypesRequired(SubmenuView* source);
123 bool CanDrop(SubmenuView* source, const ui::OSExchangeData& data);
124 void OnDragEntered(SubmenuView* source, const ui::DropTargetEvent& event);
125 int OnDragUpdated(SubmenuView* source, const ui::DropTargetEvent& event);
126 void OnDragExited(SubmenuView* source);
127 int OnPerformDrop(SubmenuView* source, const ui::DropTargetEvent& event);
129 // Invoked from the scroll buttons of the MenuScrollViewContainer.
130 void OnDragEnteredScrollButton(SubmenuView* source, bool is_up);
131 void OnDragExitedScrollButton(SubmenuView* source);
133 // Update the submenu's selection based on the current mouse location
134 void UpdateSubmenuSelection(SubmenuView* source);
136 // WidgetObserver overrides:
137 virtual void OnWidgetDestroying(Widget* widget) OVERRIDE;
139 // Only used for testing.
140 static void TurnOffMenuSelectionHoldForTest();
143 friend class internal::MenuRunnerImpl;
144 friend class MenuHostRootView;
145 friend class MenuItemView;
146 friend class SubmenuView;
148 class MenuScrollTask;
150 struct SelectByCharDetails;
152 // Values supplied to SetSelection.
153 enum SetSelectionTypes {
154 SELECTION_DEFAULT = 0,
156 // If set submenus are opened immediately, otherwise submenus are only
157 // openned after a timer fires.
158 SELECTION_UPDATE_IMMEDIATELY = 1 << 0,
160 // If set and the menu_item has a submenu, the submenu is shown.
161 SELECTION_OPEN_SUBMENU = 1 << 1,
163 // SetSelection is being invoked as the result exiting or cancelling the
164 // menu. This is used for debugging.
165 SELECTION_EXIT = 1 << 2,
168 // Result type for SendAcceleratorToHotTrackedView
169 enum SendAcceleratorResultType {
170 // Accelerator is not sent because of no hot tracked views.
171 ACCELERATOR_NOT_PROCESSED,
173 // Accelerator is sent to the hot tracked views.
174 ACCELERATOR_PROCESSED,
176 // Same as above and the accelerator causes the exit of the menu.
177 ACCELERATOR_PROCESSED_EXIT
180 // Tracks selection information.
185 // The selected menu item.
188 // If item has a submenu this indicates if the submenu is showing.
191 // Bounds passed to the run menu. Used for positioning the first menu.
192 gfx::Rect initial_bounds;
194 // Position of the initial menu.
195 MenuItemView::AnchorPosition anchor;
197 // The direction child menus have opened in.
198 std::list<bool> open_leading;
200 // Bounds for the monitor we're showing on.
201 gfx::Rect monitor_bounds;
203 // Is the current menu a context menu.
207 // Used by GetMenuPart to indicate the menu part at a particular location.
217 MenuPart() : type(NONE), menu(NULL), parent(NULL), submenu(NULL) {}
219 // Convenience for testing type == SCROLL_DOWN or type == SCROLL_UP.
220 bool is_scroll() const { return type == SCROLL_DOWN || type == SCROLL_UP; }
225 // If type is MENU_ITEM, this is the menu item the mouse is over, otherwise
227 // NOTE: if type is MENU_ITEM and the mouse is not over a valid menu item
228 // but is over a menu (for example, the mouse is over a separator or
229 // empty menu), this is NULL and parent is the menu the mouse was
233 // If type is MENU_ITEM but the mouse is not over a menu item this is the
234 // parent of the menu item the user clicked on. Otherwise this is NULL.
235 MenuItemView* parent;
237 // This is the submenu the mouse is over.
238 SubmenuView* submenu;
241 // Sets the selection to |menu_item|. A value of NULL unselects
242 // everything. |types| is a bitmask of |SetSelectionTypes|.
244 // Internally this updates pending_state_ immediatley. state_ is only updated
245 // immediately if SELECTION_UPDATE_IMMEDIATELY is set. If
246 // SELECTION_UPDATE_IMMEDIATELY is not set CommitPendingSelection is invoked
247 // to show/hide submenus and update state_.
248 void SetSelection(MenuItemView* menu_item, int types);
250 void SetSelectionOnPointerDown(SubmenuView* source,
251 const ui::LocatedEvent& event);
252 void StartDrag(SubmenuView* source, const gfx::Point& location);
254 // Dispatcher method. This returns true if the menu was canceled, or
255 // if the message is such that the menu should be closed.
256 virtual uint32_t Dispatch(const base::NativeEvent& event) OVERRIDE;
258 // Key processing. The return value of this is returned from Dispatch.
259 // In other words, if this returns false (which happens if escape was
260 // pressed, or a matching mnemonic was found) the message loop returns.
261 bool OnKeyDown(ui::KeyboardCode key_code);
263 // Creates a MenuController. If |blocking| is true a nested message loop is
265 MenuController(ui::NativeTheme* theme,
267 internal::MenuControllerDelegate* delegate);
269 virtual ~MenuController();
271 // Runs the platform specific bits of the message loop. If |nested_menu| is
272 // true we're being asked to run a menu from within a menu (eg a context
274 void RunMessageLoop(bool nested_menu);
276 // AcceleratorPressed is invoked on the hot tracked view if it exists.
277 SendAcceleratorResultType SendAcceleratorToHotTrackedView();
279 void UpdateInitialLocation(const gfx::Rect& bounds,
280 MenuItemView::AnchorPosition position,
283 // Invoked when the user accepts the selected item. This is only used
284 // when blocking. This schedules the loop to quit.
285 void Accept(MenuItemView* item, int event_flags);
287 bool ShowSiblingMenu(SubmenuView* source, const gfx::Point& mouse_location);
289 // Shows a context menu for |menu_item| as a result of a located event if
290 // appropriate. This is invoked on long press and releasing the right mouse
291 // button. Returns whether a context menu was shown.
292 bool ShowContextMenu(MenuItemView* menu_item,
294 const ui::LocatedEvent& event,
295 ui::MenuSourceType source_type);
297 // Closes all menus, including any menus of nested invocations of Run.
298 void CloseAllNestedMenus();
300 // Gets the enabled menu item at the specified location.
301 // If over_any_menu is non-null it is set to indicate whether the location
302 // is over any menu. It is possible for this to return NULL, but
303 // over_any_menu to be true. For example, the user clicked on a separator.
304 MenuItemView* GetMenuItemAt(View* menu, int x, int y);
306 // If there is an empty menu item at the specified location, it is returned.
307 MenuItemView* GetEmptyMenuItemAt(View* source, int x, int y);
309 // Returns true if the coordinate is over the scroll buttons of the
310 // SubmenuView's MenuScrollViewContainer. If true is returned, part is set to
311 // indicate which scroll button the coordinate is.
312 bool IsScrollButtonAt(SubmenuView* source,
315 MenuPart::Type* part);
317 // Returns the target for the mouse event. The coordinates are in terms of
318 // source's scroll view container.
319 MenuPart GetMenuPart(SubmenuView* source, const gfx::Point& source_loc);
321 // Returns the target for mouse events. The search is done through |item| and
323 MenuPart GetMenuPartByScreenCoordinateUsingMenu(MenuItemView* item,
324 const gfx::Point& screen_loc);
326 // Implementation of GetMenuPartByScreenCoordinate for a single menu. Returns
327 // true if the supplied SubmenuView contains the location in terms of the
328 // screen. If it does, part is set appropriately and true is returned.
329 bool GetMenuPartByScreenCoordinateImpl(SubmenuView* menu,
330 const gfx::Point& screen_loc,
333 // Returns true if the SubmenuView contains the specified location. This does
334 // NOT included the scroll buttons, only the submenu view.
335 bool DoesSubmenuContainLocation(SubmenuView* submenu,
336 const gfx::Point& screen_loc);
338 // Opens/Closes the necessary menus such that state_ matches that of
339 // pending_state_. This is invoked if submenus are not opened immediately,
340 // but after a delay.
341 void CommitPendingSelection();
343 // If item has a submenu, it is closed. This does NOT update the selection
345 void CloseMenu(MenuItemView* item);
347 // If item has a submenu, it is opened. This does NOT update the selection
349 void OpenMenu(MenuItemView* item);
351 // Implementation of OpenMenu. If |show| is true, this invokes show on the
352 // menu, otherwise Reposition is invoked.
353 void OpenMenuImpl(MenuItemView* item, bool show);
355 // Invoked when the children of a menu change and the menu is showing.
356 // This closes any submenus and resizes the submenu.
357 void MenuChildrenChanged(MenuItemView* item);
359 // Builds the paths of the two menu items into the two paths, and
360 // sets first_diff_at to the location of the first difference between the
362 void BuildPathsAndCalculateDiff(MenuItemView* old_item,
363 MenuItemView* new_item,
364 std::vector<MenuItemView*>* old_path,
365 std::vector<MenuItemView*>* new_path,
366 size_t* first_diff_at);
368 // Builds the path for the specified item.
369 void BuildMenuItemPath(MenuItemView* item, std::vector<MenuItemView*>* path);
371 // Starts/stops the timer that commits the pending state to state
372 // (opens/closes submenus).
373 void StartShowTimer();
374 void StopShowTimer();
376 // Starts/stops the timer cancel the menu. This is used during drag and
377 // drop when the drop enters/exits the menu.
378 void StartCancelAllTimer();
379 void StopCancelAllTimer();
381 // Calculates the bounds of the menu to show. is_leading is set to match the
382 // direction the menu opened in.
383 gfx::Rect CalculateMenuBounds(MenuItemView* item,
387 // Calculates the bubble bounds of the menu to show. is_leading is set to
388 // match the direction the menu opened in.
389 gfx::Rect CalculateBubbleMenuBounds(MenuItemView* item,
393 // Returns the depth of the menu.
394 static int MenuDepth(MenuItemView* item);
396 // Selects the next/previous menu item.
397 void IncrementSelection(int delta);
399 // Returns the next selectable child menu item of |parent| starting at |index|
400 // and incrementing index by |delta|. If there are no more selected menu items
402 MenuItemView* FindNextSelectableMenuItem(MenuItemView* parent,
406 // If the selected item has a submenu and it isn't currently open, the
407 // the selection is changed such that the menu opens immediately.
408 void OpenSubmenuChangeSelectionIfCan();
410 // If possible, closes the submenu.
413 // Returns details about which menu items match the mnemonic |key|.
414 // |match_function| is used to determine which menus match.
415 SelectByCharDetails FindChildForMnemonic(
416 MenuItemView* parent,
418 bool (*match_function)(MenuItemView* menu, base::char16 mnemonic));
420 // Selects or accepts the appropriate menu item based on |details|. Returns
421 // true if |Accept| was invoked (which happens if there aren't multiple item
422 // with the same mnemonic and the item to select does not have a submenu).
423 bool AcceptOrSelect(MenuItemView* parent, const SelectByCharDetails& details);
425 // Selects by mnemonic, and if that doesn't work tries the first character of
426 // the title. Returns true if a match was selected and the menu should exit.
427 bool SelectByChar(base::char16 key);
429 // For Windows and Aura we repost an event for some events that dismiss
430 // the context menu. The event is then reprocessed to cause its result
431 // if the context menu had not been present.
432 // On non-aura Windows, a new mouse event is generated and posted to
433 // the window (if there is one) at the location of the event. On
434 // aura, the event is reposted on the RootWindow.
435 void RepostEvent(SubmenuView* source, const ui::LocatedEvent& event);
437 // Sets the drop target to new_item.
438 void SetDropMenuItem(MenuItemView* new_item,
439 MenuDelegate::DropPosition position);
441 // Starts/stops scrolling as appropriate. part gives the part the mouse is
443 void UpdateScrolling(const MenuPart& part);
446 void StopScrolling();
448 // Updates active mouse view from the location of the event and sends it
449 // the appropriate events. This is used to send mouse events to child views so
450 // that they react to click-drag-release as if the user clicked on the view
452 void UpdateActiveMouseView(SubmenuView* event_source,
453 const ui::MouseEvent& event,
456 // Sends a mouse release event to the current active mouse view and sets
458 void SendMouseReleaseToActiveView(SubmenuView* event_source,
459 const ui::MouseEvent& event);
461 // Sends a mouse capture lost event to the current active mouse view and sets
463 void SendMouseCaptureLostToActiveView();
465 // Sets/gets the active mouse view. See UpdateActiveMouseView() for details.
466 void SetActiveMouseView(View* view);
467 View* GetActiveMouseView();
470 void SetExitType(ExitType type);
472 // Returns true if SetExitType() should quit the message loop.
473 bool ShouldQuitNow() const;
475 // Handles the mouse location event on the submenu |source|.
476 void HandleMouseLocation(SubmenuView* source,
477 const gfx::Point& mouse_location);
479 // Retrieve an appropriate Screen.
480 gfx::Screen* GetScreen();
482 // The active instance.
483 static MenuController* active_instance_;
485 // If true, Run blocks. If false, Run doesn't block and this is used for
486 // drag and drop. Note that the semantics for drag and drop are slightly
487 // different: cancel timer is kicked off any time the drag moves outside the
488 // menu, mouse events do nothing...
491 // If true, we're showing.
494 // Indicates what to exit.
497 // Whether we did a capture. We do a capture only if we're blocking and
498 // the mouse was down when Run.
501 // As the user drags the mouse around pending_state_ changes immediately.
502 // When the user stops moving/dragging the mouse (or clicks the mouse)
503 // pending_state_ is committed to state_, potentially resulting in
504 // opening or closing submenus. This gives a slight delayed effect to
505 // submenus as the user moves the mouse around. This is done so that as the
506 // user moves the mouse all submenus don't immediately pop.
507 State pending_state_;
510 // If the user accepted the selection, this is the result.
511 MenuItemView* result_;
513 // The event flags when the user selected the menu.
514 int accept_event_flags_;
516 // If not empty, it means we're nested. When Run is invoked from within
517 // Run, the current state (state_) is pushed onto menu_stack_. This allows
518 // MenuController to restore the state when the nested run returns.
519 std::list<State> menu_stack_;
521 // As the mouse moves around submenus are not opened immediately. Instead
522 // they open after this timer fires.
523 base::OneShotTimer<MenuController> show_timer_;
525 // Used to invoke CancelAll(). This is used during drag and drop to hide the
526 // menu after the mouse moves out of the of the menu. This is necessitated by
527 // the lack of an ability to detect when the drag has completed from the drop
529 base::OneShotTimer<MenuController> cancel_all_timer_;
532 MenuItemView* drop_target_;
533 MenuDelegate::DropPosition drop_position_;
535 // Owner of child windows.
536 // WARNING: this may be NULL.
539 // Indicates a possible drag operation.
542 // True when drag operation is in progress.
543 bool drag_in_progress_;
545 // Location the mouse was pressed at. Used to detect d&d.
546 gfx::Point press_pt_;
548 // We get a slew of drag updated messages as the mouse is over us. To avoid
549 // continually processing whether we can drop, we cache the coordinates.
550 bool valid_drop_coordinates_;
552 int last_drop_operation_;
554 // If true, we're in the middle of invoking ShowAt on a submenu.
555 bool showing_submenu_;
557 // Task for scrolling the menu. If non-null indicates a scroll is currently
559 scoped_ptr<MenuScrollTask> scroll_task_;
561 MenuButton* menu_button_;
563 // ViewStorage id used to store the view mouse drag events are forwarded to.
564 // See UpdateActiveMouseView() for details.
565 const int active_mouse_view_id_;
567 internal::MenuControllerDelegate* delegate_;
569 // How deep we are in nested message loops. This should be at most 2 (when
570 // showing a context menu from a menu).
571 int message_loop_depth_;
573 views::MenuConfig menu_config_;
575 // The timestamp of the event which closed the menu - or 0 otherwise.
576 base::TimeDelta closing_event_time_;
578 // Time when the menu is first shown.
579 base::TimeTicks menu_start_time_;
581 // If a mouse press triggered this menu, this will have its location (in
582 // screen coordinates). Otherwise this will be (0, 0).
583 gfx::Point menu_start_mouse_press_loc_;
585 // Whether the menu should accept on F4, like Windows native Combobox menus.
588 // Set to true if the menu item was selected by touch.
589 bool item_selected_by_touch_;
591 DISALLOW_COPY_AND_ASSIGN(MenuController);
596 #endif // UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_H_