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/timer/timer.h"
17 #include "ui/events/event.h"
18 #include "ui/events/event_constants.h"
19 #include "ui/events/platform/platform_event_dispatcher.h"
20 #include "ui/views/controls/menu/menu_config.h"
21 #include "ui/views/controls/menu/menu_delegate.h"
22 #include "ui/views/widget/widget_observer.h"
25 class MessagePumpDispatcher;
33 class ScopedEventDispatcher;
38 class MenuHostRootView;
45 class MenuControllerDelegate;
46 class MenuEventDispatcher;
47 class MenuMessagePumpDispatcher;
51 // MenuController -------------------------------------------------------------
53 // MenuController is used internally by the various menu classes to manage
54 // showing, selecting and drag/drop for menus. All relevant events are
55 // forwarded to the MenuController from SubmenuView and MenuHost.
56 class VIEWS_EXPORT MenuController : public WidgetObserver {
58 // Enumeration of how the menu should exit.
63 // All menus, including nested, should be exited.
66 // Only the outermost menu should be exited.
69 // This is set if the menu is being closed as the result of one of the menus
74 // If a menu is currently active, this returns the controller for it.
75 static MenuController* GetActiveInstance();
77 // Runs the menu at the specified location. If the menu was configured to
78 // block, the selected item is returned. If the menu does not block this
79 // returns NULL immediately.
80 MenuItemView* Run(Widget* parent,
83 const gfx::Rect& bounds,
84 MenuAnchorPosition position,
88 // Whether or not Run blocks.
89 bool IsBlockingRun() const { return blocking_run_; }
91 // Whether or not drag operation is in progress.
92 bool drag_in_progress() const { return drag_in_progress_; }
94 // Get the anchor position wich is used to show this menu.
95 MenuAnchorPosition GetAnchorPosition() { return state_.anchor; }
97 // Cancels the current Run. See ExitType for a description of what happens
98 // with the various parameters.
99 void Cancel(ExitType type);
101 // An alternative to Cancel(EXIT_ALL) that can be used with a OneShotTimer.
102 void CancelAll() { Cancel(EXIT_ALL); }
104 // Returns the current exit type. This returns a value other than EXIT_NONE if
105 // the menu is being canceled.
106 ExitType exit_type() const { return exit_type_; }
108 // Returns the time from the event which closed the menu - or 0.
109 base::TimeDelta closing_event_time() const { return closing_event_time_; }
111 void set_is_combobox(bool is_combobox) { is_combobox_ = is_combobox; }
113 // Various events, forwarded from the submenu.
115 // NOTE: the coordinates of the events are in that of the
116 // MenuScrollViewContainer.
117 void OnMousePressed(SubmenuView* source, const ui::MouseEvent& event);
118 void OnMouseDragged(SubmenuView* source, const ui::MouseEvent& event);
119 void OnMouseReleased(SubmenuView* source, const ui::MouseEvent& event);
120 void OnMouseMoved(SubmenuView* source, const ui::MouseEvent& event);
121 void OnMouseEntered(SubmenuView* source, const ui::MouseEvent& event);
122 bool OnMouseWheel(SubmenuView* source, const ui::MouseWheelEvent& event);
123 void OnGestureEvent(SubmenuView* source, ui::GestureEvent* event);
128 std::set<ui::OSExchangeData::CustomFormat>* custom_formats);
129 bool AreDropTypesRequired(SubmenuView* source);
130 bool CanDrop(SubmenuView* source, const ui::OSExchangeData& data);
131 void OnDragEntered(SubmenuView* source, const ui::DropTargetEvent& event);
132 int OnDragUpdated(SubmenuView* source, const ui::DropTargetEvent& event);
133 void OnDragExited(SubmenuView* source);
134 int OnPerformDrop(SubmenuView* source, const ui::DropTargetEvent& event);
136 // Invoked from the scroll buttons of the MenuScrollViewContainer.
137 void OnDragEnteredScrollButton(SubmenuView* source, bool is_up);
138 void OnDragExitedScrollButton(SubmenuView* source);
140 // Update the submenu's selection based on the current mouse location
141 void UpdateSubmenuSelection(SubmenuView* source);
143 // WidgetObserver overrides:
144 virtual void OnWidgetDestroying(Widget* widget) OVERRIDE;
146 // Only used for testing.
147 static void TurnOffMenuSelectionHoldForTest();
150 friend class internal::MenuEventDispatcher;
151 friend class internal::MenuMessagePumpDispatcher;
152 friend class internal::MenuRunnerImpl;
153 friend class MenuHostRootView;
154 friend class MenuItemView;
155 friend class SubmenuView;
157 class MenuScrollTask;
159 struct SelectByCharDetails;
161 // Values supplied to SetSelection.
162 enum SetSelectionTypes {
163 SELECTION_DEFAULT = 0,
165 // If set submenus are opened immediately, otherwise submenus are only
166 // openned after a timer fires.
167 SELECTION_UPDATE_IMMEDIATELY = 1 << 0,
169 // If set and the menu_item has a submenu, the submenu is shown.
170 SELECTION_OPEN_SUBMENU = 1 << 1,
172 // SetSelection is being invoked as the result exiting or cancelling the
173 // menu. This is used for debugging.
174 SELECTION_EXIT = 1 << 2,
177 // Result type for SendAcceleratorToHotTrackedView
178 enum SendAcceleratorResultType {
179 // Accelerator is not sent because of no hot tracked views.
180 ACCELERATOR_NOT_PROCESSED,
182 // Accelerator is sent to the hot tracked views.
183 ACCELERATOR_PROCESSED,
185 // Same as above and the accelerator causes the exit of the menu.
186 ACCELERATOR_PROCESSED_EXIT
189 // Tracks selection information.
194 // The selected menu item.
197 // If item has a submenu this indicates if the submenu is showing.
200 // Bounds passed to the run menu. Used for positioning the first menu.
201 gfx::Rect initial_bounds;
203 // Position of the initial menu.
204 MenuAnchorPosition anchor;
206 // The direction child menus have opened in.
207 std::list<bool> open_leading;
209 // Bounds for the monitor we're showing on.
210 gfx::Rect monitor_bounds;
212 // Is the current menu a context menu.
216 // Used by GetMenuPart to indicate the menu part at a particular location.
226 MenuPart() : type(NONE), menu(NULL), parent(NULL), submenu(NULL) {}
228 // Convenience for testing type == SCROLL_DOWN or type == SCROLL_UP.
229 bool is_scroll() const { return type == SCROLL_DOWN || type == SCROLL_UP; }
234 // If type is MENU_ITEM, this is the menu item the mouse is over, otherwise
236 // NOTE: if type is MENU_ITEM and the mouse is not over a valid menu item
237 // but is over a menu (for example, the mouse is over a separator or
238 // empty menu), this is NULL and parent is the menu the mouse was
242 // If type is MENU_ITEM but the mouse is not over a menu item this is the
243 // parent of the menu item the user clicked on. Otherwise this is NULL.
244 MenuItemView* parent;
246 // This is the submenu the mouse is over.
247 SubmenuView* submenu;
250 // Sets the selection to |menu_item|. A value of NULL unselects
251 // everything. |types| is a bitmask of |SetSelectionTypes|.
253 // Internally this updates pending_state_ immediatley. state_ is only updated
254 // immediately if SELECTION_UPDATE_IMMEDIATELY is set. If
255 // SELECTION_UPDATE_IMMEDIATELY is not set CommitPendingSelection is invoked
256 // to show/hide submenus and update state_.
257 void SetSelection(MenuItemView* menu_item, int types);
259 void SetSelectionOnPointerDown(SubmenuView* source,
260 const ui::LocatedEvent& event);
261 void StartDrag(SubmenuView* source, const gfx::Point& location);
263 // Key processing. The return value of this is returned from Dispatch.
264 // In other words, if this returns false (which happens if escape was
265 // pressed, or a matching mnemonic was found) the message loop returns.
266 bool OnKeyDown(ui::KeyboardCode key_code);
268 // Creates a MenuController. If |blocking| is true a nested message loop is
270 MenuController(ui::NativeTheme* theme,
272 internal::MenuControllerDelegate* delegate);
274 virtual ~MenuController();
276 // Runs the platform specific bits of the message loop. If |nested_menu| is
277 // true we're being asked to run a menu from within a menu (eg a context
279 void RunMessageLoop(bool nested_menu);
281 // AcceleratorPressed is invoked on the hot tracked view if it exists.
282 SendAcceleratorResultType SendAcceleratorToHotTrackedView();
284 void UpdateInitialLocation(const gfx::Rect& bounds,
285 MenuAnchorPosition position,
288 // Invoked when the user accepts the selected item. This is only used
289 // when blocking. This schedules the loop to quit.
290 void Accept(MenuItemView* item, int event_flags);
292 bool ShowSiblingMenu(SubmenuView* source, const gfx::Point& mouse_location);
294 // Shows a context menu for |menu_item| as a result of a located event if
295 // appropriate. This is invoked on long press and releasing the right mouse
296 // button. Returns whether a context menu was shown.
297 bool ShowContextMenu(MenuItemView* menu_item,
299 const ui::LocatedEvent& event,
300 ui::MenuSourceType source_type);
302 // Closes all menus, including any menus of nested invocations of Run.
303 void CloseAllNestedMenus();
305 // Gets the enabled menu item at the specified location.
306 // If over_any_menu is non-null it is set to indicate whether the location
307 // is over any menu. It is possible for this to return NULL, but
308 // over_any_menu to be true. For example, the user clicked on a separator.
309 MenuItemView* GetMenuItemAt(View* menu, int x, int y);
311 // If there is an empty menu item at the specified location, it is returned.
312 MenuItemView* GetEmptyMenuItemAt(View* source, int x, int y);
314 // Returns true if the coordinate is over the scroll buttons of the
315 // SubmenuView's MenuScrollViewContainer. If true is returned, part is set to
316 // indicate which scroll button the coordinate is.
317 bool IsScrollButtonAt(SubmenuView* source,
320 MenuPart::Type* part);
322 // Returns the target for the mouse event. The coordinates are in terms of
323 // source's scroll view container.
324 MenuPart GetMenuPart(SubmenuView* source, const gfx::Point& source_loc);
326 // Returns the target for mouse events. The search is done through |item| and
328 MenuPart GetMenuPartByScreenCoordinateUsingMenu(MenuItemView* item,
329 const gfx::Point& screen_loc);
331 // Implementation of GetMenuPartByScreenCoordinate for a single menu. Returns
332 // true if the supplied SubmenuView contains the location in terms of the
333 // screen. If it does, part is set appropriately and true is returned.
334 bool GetMenuPartByScreenCoordinateImpl(SubmenuView* menu,
335 const gfx::Point& screen_loc,
338 // Returns true if the SubmenuView contains the specified location. This does
339 // NOT included the scroll buttons, only the submenu view.
340 bool DoesSubmenuContainLocation(SubmenuView* submenu,
341 const gfx::Point& screen_loc);
343 // Opens/Closes the necessary menus such that state_ matches that of
344 // pending_state_. This is invoked if submenus are not opened immediately,
345 // but after a delay.
346 void CommitPendingSelection();
348 // If item has a submenu, it is closed. This does NOT update the selection
350 void CloseMenu(MenuItemView* item);
352 // If item has a submenu, it is opened. This does NOT update the selection
354 void OpenMenu(MenuItemView* item);
356 // Implementation of OpenMenu. If |show| is true, this invokes show on the
357 // menu, otherwise Reposition is invoked.
358 void OpenMenuImpl(MenuItemView* item, bool show);
360 // Invoked when the children of a menu change and the menu is showing.
361 // This closes any submenus and resizes the submenu.
362 void MenuChildrenChanged(MenuItemView* item);
364 // Builds the paths of the two menu items into the two paths, and
365 // sets first_diff_at to the location of the first difference between the
367 void BuildPathsAndCalculateDiff(MenuItemView* old_item,
368 MenuItemView* new_item,
369 std::vector<MenuItemView*>* old_path,
370 std::vector<MenuItemView*>* new_path,
371 size_t* first_diff_at);
373 // Builds the path for the specified item.
374 void BuildMenuItemPath(MenuItemView* item, std::vector<MenuItemView*>* path);
376 // Starts/stops the timer that commits the pending state to state
377 // (opens/closes submenus).
378 void StartShowTimer();
379 void StopShowTimer();
381 // Starts/stops the timer cancel the menu. This is used during drag and
382 // drop when the drop enters/exits the menu.
383 void StartCancelAllTimer();
384 void StopCancelAllTimer();
386 // Calculates the bounds of the menu to show. is_leading is set to match the
387 // direction the menu opened in.
388 gfx::Rect CalculateMenuBounds(MenuItemView* item,
392 // Calculates the bubble bounds of the menu to show. is_leading is set to
393 // match the direction the menu opened in.
394 gfx::Rect CalculateBubbleMenuBounds(MenuItemView* item,
398 // Returns the depth of the menu.
399 static int MenuDepth(MenuItemView* item);
401 // Selects the next/previous menu item.
402 void IncrementSelection(int delta);
404 // Returns the next selectable child menu item of |parent| starting at |index|
405 // and incrementing index by |delta|. If there are no more selected menu items
407 MenuItemView* FindNextSelectableMenuItem(MenuItemView* parent,
411 // If the selected item has a submenu and it isn't currently open, the
412 // the selection is changed such that the menu opens immediately.
413 void OpenSubmenuChangeSelectionIfCan();
415 // If possible, closes the submenu.
418 // Returns details about which menu items match the mnemonic |key|.
419 // |match_function| is used to determine which menus match.
420 SelectByCharDetails FindChildForMnemonic(
421 MenuItemView* parent,
423 bool (*match_function)(MenuItemView* menu, base::char16 mnemonic));
425 // Selects or accepts the appropriate menu item based on |details|. Returns
426 // true if |Accept| was invoked (which happens if there aren't multiple item
427 // with the same mnemonic and the item to select does not have a submenu).
428 bool AcceptOrSelect(MenuItemView* parent, const SelectByCharDetails& details);
430 // Selects by mnemonic, and if that doesn't work tries the first character of
431 // the title. Returns true if a match was selected and the menu should exit.
432 bool SelectByChar(base::char16 key);
434 // For Windows and Aura we repost an event for some events that dismiss
435 // the context menu. The event is then reprocessed to cause its result
436 // if the context menu had not been present.
437 // On non-aura Windows, a new mouse event is generated and posted to
438 // the window (if there is one) at the location of the event. On
439 // aura, the event is reposted on the RootWindow.
440 void RepostEvent(SubmenuView* source, const ui::LocatedEvent& event);
442 // Sets the drop target to new_item.
443 void SetDropMenuItem(MenuItemView* new_item,
444 MenuDelegate::DropPosition position);
446 // Starts/stops scrolling as appropriate. part gives the part the mouse is
448 void UpdateScrolling(const MenuPart& part);
451 void StopScrolling();
453 // Updates active mouse view from the location of the event and sends it
454 // the appropriate events. This is used to send mouse events to child views so
455 // that they react to click-drag-release as if the user clicked on the view
457 void UpdateActiveMouseView(SubmenuView* event_source,
458 const ui::MouseEvent& event,
461 // Sends a mouse release event to the current active mouse view and sets
463 void SendMouseReleaseToActiveView(SubmenuView* event_source,
464 const ui::MouseEvent& event);
466 // Sends a mouse capture lost event to the current active mouse view and sets
468 void SendMouseCaptureLostToActiveView();
470 // Sets/gets the active mouse view. See UpdateActiveMouseView() for details.
471 void SetActiveMouseView(View* view);
472 View* GetActiveMouseView();
475 void SetExitType(ExitType type);
477 // Returns true if SetExitType() should quit the message loop.
478 bool ShouldQuitNow() const;
480 // Handles the mouse location event on the submenu |source|.
481 void HandleMouseLocation(SubmenuView* source,
482 const gfx::Point& mouse_location);
484 // Retrieve an appropriate Screen.
485 gfx::Screen* GetScreen();
487 // The active instance.
488 static MenuController* active_instance_;
490 // If true, Run blocks. If false, Run doesn't block and this is used for
491 // drag and drop. Note that the semantics for drag and drop are slightly
492 // different: cancel timer is kicked off any time the drag moves outside the
493 // menu, mouse events do nothing...
496 // If true, we're showing.
499 // Indicates what to exit.
502 // Whether we did a capture. We do a capture only if we're blocking and
503 // the mouse was down when Run.
506 // As the user drags the mouse around pending_state_ changes immediately.
507 // When the user stops moving/dragging the mouse (or clicks the mouse)
508 // pending_state_ is committed to state_, potentially resulting in
509 // opening or closing submenus. This gives a slight delayed effect to
510 // submenus as the user moves the mouse around. This is done so that as the
511 // user moves the mouse all submenus don't immediately pop.
512 State pending_state_;
515 // If the user accepted the selection, this is the result.
516 MenuItemView* result_;
518 // The event flags when the user selected the menu.
519 int accept_event_flags_;
521 // If not empty, it means we're nested. When Run is invoked from within
522 // Run, the current state (state_) is pushed onto menu_stack_. This allows
523 // MenuController to restore the state when the nested run returns.
524 std::list<State> menu_stack_;
526 // As the mouse moves around submenus are not opened immediately. Instead
527 // they open after this timer fires.
528 base::OneShotTimer<MenuController> show_timer_;
530 // Used to invoke CancelAll(). This is used during drag and drop to hide the
531 // menu after the mouse moves out of the of the menu. This is necessitated by
532 // the lack of an ability to detect when the drag has completed from the drop
534 base::OneShotTimer<MenuController> cancel_all_timer_;
537 MenuItemView* drop_target_;
538 MenuDelegate::DropPosition drop_position_;
540 // Owner of child windows.
541 // WARNING: this may be NULL.
544 // Indicates a possible drag operation.
547 // True when drag operation is in progress.
548 bool drag_in_progress_;
550 // Location the mouse was pressed at. Used to detect d&d.
551 gfx::Point press_pt_;
553 // We get a slew of drag updated messages as the mouse is over us. To avoid
554 // continually processing whether we can drop, we cache the coordinates.
555 bool valid_drop_coordinates_;
557 int last_drop_operation_;
559 // If true, we're in the middle of invoking ShowAt on a submenu.
560 bool showing_submenu_;
562 // Task for scrolling the menu. If non-null indicates a scroll is currently
564 scoped_ptr<MenuScrollTask> scroll_task_;
566 MenuButton* menu_button_;
568 // ViewStorage id used to store the view mouse drag events are forwarded to.
569 // See UpdateActiveMouseView() for details.
570 const int active_mouse_view_id_;
572 internal::MenuControllerDelegate* delegate_;
574 // How deep we are in nested message loops. This should be at most 2 (when
575 // showing a context menu from a menu).
576 int message_loop_depth_;
578 views::MenuConfig menu_config_;
580 // The timestamp of the event which closed the menu - or 0 otherwise.
581 base::TimeDelta closing_event_time_;
583 // Time when the menu is first shown.
584 base::TimeTicks menu_start_time_;
586 // If a mouse press triggered this menu, this will have its location (in
587 // screen coordinates). Otherwise this will be (0, 0).
588 gfx::Point menu_start_mouse_press_loc_;
590 // Controls behavior differences between a combobox and other types of menu
591 // (like a context menu).
594 // Set to true if the menu item was selected by touch.
595 bool item_selected_by_touch_;
597 scoped_ptr<ui::ScopedEventDispatcher> nested_dispatcher_;
599 DISALLOW_COPY_AND_ASSIGN(MenuController);
604 #endif // UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_H_