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. 4 5 #ifndef UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_H_ 6 #define UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_H_ 7 8 #include "build/build_config.h" 9 10 #include <list> 11 #include <set> 12 #include <vector> 13 14 #include "base/compiler_specific.h" 15 #include "base/memory/linked_ptr.h" 16 #include "base/memory/scoped_ptr.h" 17 #include "base/timer/timer.h" 18 #include "ui/events/event.h" 19 #include "ui/events/event_constants.h" 20 #include "ui/events/platform/platform_event_dispatcher.h" 21 #include "ui/views/controls/button/menu_button.h" 22 #include "ui/views/controls/menu/menu_config.h" 23 #include "ui/views/controls/menu/menu_delegate.h" 24 #include "ui/views/widget/widget_observer.h" 25 26 namespace base { 27 class MessagePumpDispatcher; 28 } 29 namespace gfx { 30 class Screen; 31 } 32 namespace ui { 33 class NativeTheme; 34 class OSExchangeData; 35 class ScopedEventDispatcher; 36 } 37 namespace views { 38 39 class MenuButton; 40 class MenuHostRootView; 41 class MenuItemView; 42 class MenuMessageLoop; 43 class MouseEvent; 44 class SubmenuView; 45 class View; 46 47 namespace internal { 48 class MenuControllerDelegate; 49 class MenuEventDispatcher; 50 class MenuMessagePumpDispatcher; 51 class MenuRunnerImpl; 52 } 53 54 // MenuController ------------------------------------------------------------- 55 56 // MenuController is used internally by the various menu classes to manage 57 // showing, selecting and drag/drop for menus. All relevant events are 58 // forwarded to the MenuController from SubmenuView and MenuHost. 59 class VIEWS_EXPORT MenuController : public WidgetObserver { 60 public: 61 // Enumeration of how the menu should exit. 62 enum ExitType { 63 // Don't exit. 64 EXIT_NONE, 65 66 // All menus, including nested, should be exited. 67 EXIT_ALL, 68 69 // Only the outermost menu should be exited. 70 EXIT_OUTERMOST, 71 72 // This is set if the menu is being closed as the result of one of the menus 73 // being destroyed. 74 EXIT_DESTROYED 75 }; 76 77 // If a menu is currently active, this returns the controller for it. 78 static MenuController* GetActiveInstance(); 79 80 // Runs the menu at the specified location. If the menu was configured to 81 // block, the selected item is returned. If the menu does not block this 82 // returns NULL immediately. 83 MenuItemView* Run(Widget* parent, 84 MenuButton* button, 85 MenuItemView* root, 86 const gfx::Rect& bounds, 87 MenuAnchorPosition position, 88 bool context_menu, 89 bool is_nested_drag, 90 int* event_flags); 91 92 // Whether or not Run blocks. IsBlockingRun()93 bool IsBlockingRun() const { return blocking_run_; } 94 in_nested_run()95 bool in_nested_run() const { return !menu_stack_.empty(); } 96 97 // Whether or not drag operation is in progress. drag_in_progress()98 bool drag_in_progress() const { return drag_in_progress_; } 99 100 // Whether the MenuController initiated the drag in progress. False if there 101 // is no drag in progress. did_initiate_drag()102 bool did_initiate_drag() const { return did_initiate_drag_; } 103 104 // Returns the owner of child windows. 105 // WARNING: this may be NULL. owner()106 Widget* owner() { return owner_; } 107 108 // Get the anchor position wich is used to show this menu. GetAnchorPosition()109 MenuAnchorPosition GetAnchorPosition() { return state_.anchor; } 110 111 // Cancels the current Run. See ExitType for a description of what happens 112 // with the various parameters. 113 void Cancel(ExitType type); 114 115 // An alternative to Cancel(EXIT_ALL) that can be used with a OneShotTimer. CancelAll()116 void CancelAll() { Cancel(EXIT_ALL); } 117 118 // Returns the current exit type. This returns a value other than EXIT_NONE if 119 // the menu is being canceled. exit_type()120 ExitType exit_type() const { return exit_type_; } 121 122 // Returns the time from the event which closed the menu - or 0. closing_event_time()123 base::TimeDelta closing_event_time() const { return closing_event_time_; } 124 set_is_combobox(bool is_combobox)125 void set_is_combobox(bool is_combobox) { is_combobox_ = is_combobox; } 126 127 // Various events, forwarded from the submenu. 128 // 129 // NOTE: the coordinates of the events are in that of the 130 // MenuScrollViewContainer. 131 void OnMousePressed(SubmenuView* source, const ui::MouseEvent& event); 132 void OnMouseDragged(SubmenuView* source, const ui::MouseEvent& event); 133 void OnMouseReleased(SubmenuView* source, const ui::MouseEvent& event); 134 void OnMouseMoved(SubmenuView* source, const ui::MouseEvent& event); 135 void OnMouseEntered(SubmenuView* source, const ui::MouseEvent& event); 136 bool OnMouseWheel(SubmenuView* source, const ui::MouseWheelEvent& event); 137 void OnGestureEvent(SubmenuView* source, ui::GestureEvent* event); 138 139 bool GetDropFormats( 140 SubmenuView* source, 141 int* formats, 142 std::set<ui::OSExchangeData::CustomFormat>* custom_formats); 143 bool AreDropTypesRequired(SubmenuView* source); 144 bool CanDrop(SubmenuView* source, const ui::OSExchangeData& data); 145 void OnDragEntered(SubmenuView* source, const ui::DropTargetEvent& event); 146 int OnDragUpdated(SubmenuView* source, const ui::DropTargetEvent& event); 147 void OnDragExited(SubmenuView* source); 148 int OnPerformDrop(SubmenuView* source, const ui::DropTargetEvent& event); 149 150 // Invoked from the scroll buttons of the MenuScrollViewContainer. 151 void OnDragEnteredScrollButton(SubmenuView* source, bool is_up); 152 void OnDragExitedScrollButton(SubmenuView* source); 153 154 // Called by the Widget when a drag is about to start on a child view. This 155 // could be initiated by one of our MenuItemViews, or could be through another 156 // child View. 157 void OnDragWillStart(); 158 159 // Called by the Widget when the drag has completed. |should_close| 160 // corresponds to whether or not the menu should close. 161 void OnDragComplete(bool should_close); 162 163 // Update the submenu's selection based on the current mouse location 164 void UpdateSubmenuSelection(SubmenuView* source); 165 166 // WidgetObserver overrides: 167 virtual void OnWidgetDestroying(Widget* widget) OVERRIDE; 168 169 // Only used for testing. 170 bool IsCancelAllTimerRunningForTest(); 171 172 // Only used for testing. 173 static void TurnOffMenuSelectionHoldForTest(); 174 175 private: 176 friend class internal::MenuEventDispatcher; 177 friend class internal::MenuMessagePumpDispatcher; 178 friend class internal::MenuRunnerImpl; 179 friend class MenuControllerTest; 180 friend class MenuHostRootView; 181 friend class MenuItemView; 182 friend class SubmenuView; 183 184 class MenuScrollTask; 185 186 struct SelectByCharDetails; 187 188 // Values supplied to SetSelection. 189 enum SetSelectionTypes { 190 SELECTION_DEFAULT = 0, 191 192 // If set submenus are opened immediately, otherwise submenus are only 193 // openned after a timer fires. 194 SELECTION_UPDATE_IMMEDIATELY = 1 << 0, 195 196 // If set and the menu_item has a submenu, the submenu is shown. 197 SELECTION_OPEN_SUBMENU = 1 << 1, 198 199 // SetSelection is being invoked as the result exiting or cancelling the 200 // menu. This is used for debugging. 201 SELECTION_EXIT = 1 << 2, 202 }; 203 204 // Result type for SendAcceleratorToHotTrackedView 205 enum SendAcceleratorResultType { 206 // Accelerator is not sent because of no hot tracked views. 207 ACCELERATOR_NOT_PROCESSED, 208 209 // Accelerator is sent to the hot tracked views. 210 ACCELERATOR_PROCESSED, 211 212 // Same as above and the accelerator causes the exit of the menu. 213 ACCELERATOR_PROCESSED_EXIT 214 }; 215 216 // Tracks selection information. 217 struct State { 218 State(); 219 ~State(); 220 221 // The selected menu item. 222 MenuItemView* item; 223 224 // If item has a submenu this indicates if the submenu is showing. 225 bool submenu_open; 226 227 // Bounds passed to the run menu. Used for positioning the first menu. 228 gfx::Rect initial_bounds; 229 230 // Position of the initial menu. 231 MenuAnchorPosition anchor; 232 233 // The direction child menus have opened in. 234 std::list<bool> open_leading; 235 236 // Bounds for the monitor we're showing on. 237 gfx::Rect monitor_bounds; 238 239 // Is the current menu a context menu. 240 bool context_menu; 241 }; 242 243 // Used by GetMenuPart to indicate the menu part at a particular location. 244 struct MenuPart { 245 // Type of part. 246 enum Type { 247 NONE, 248 MENU_ITEM, 249 SCROLL_UP, 250 SCROLL_DOWN 251 }; 252 MenuPartMenuPart253 MenuPart() : type(NONE), menu(NULL), parent(NULL), submenu(NULL) {} 254 255 // Convenience for testing type == SCROLL_DOWN or type == SCROLL_UP. is_scrollMenuPart256 bool is_scroll() const { return type == SCROLL_DOWN || type == SCROLL_UP; } 257 258 // Type of part. 259 Type type; 260 261 // If type is MENU_ITEM, this is the menu item the mouse is over, otherwise 262 // this is NULL. 263 // NOTE: if type is MENU_ITEM and the mouse is not over a valid menu item 264 // but is over a menu (for example, the mouse is over a separator or 265 // empty menu), this is NULL and parent is the menu the mouse was 266 // clicked on. 267 MenuItemView* menu; 268 269 // If type is MENU_ITEM but the mouse is not over a menu item this is the 270 // parent of the menu item the user clicked on. Otherwise this is NULL. 271 MenuItemView* parent; 272 273 // This is the submenu the mouse is over. 274 SubmenuView* submenu; 275 }; 276 277 // Sets the selection to |menu_item|. A value of NULL unselects 278 // everything. |types| is a bitmask of |SetSelectionTypes|. 279 // 280 // Internally this updates pending_state_ immediatley. state_ is only updated 281 // immediately if SELECTION_UPDATE_IMMEDIATELY is set. If 282 // SELECTION_UPDATE_IMMEDIATELY is not set CommitPendingSelection is invoked 283 // to show/hide submenus and update state_. 284 void SetSelection(MenuItemView* menu_item, int types); 285 286 void SetSelectionOnPointerDown(SubmenuView* source, 287 const ui::LocatedEvent& event); 288 void StartDrag(SubmenuView* source, const gfx::Point& location); 289 290 // Key processing. The return value of this is returned from Dispatch. 291 // In other words, if this returns false (which happens if escape was 292 // pressed, or a matching mnemonic was found) the message loop returns. 293 bool OnKeyDown(ui::KeyboardCode key_code); 294 295 // Creates a MenuController. If |blocking| is true a nested message loop is 296 // started in |Run|. 297 MenuController(ui::NativeTheme* theme, 298 bool blocking, 299 internal::MenuControllerDelegate* delegate); 300 301 virtual ~MenuController(); 302 303 // Runs the platform specific bits of the message loop. If |nested_menu| is 304 // true we're being asked to run a menu from within a menu (eg a context 305 // menu). 306 void RunMessageLoop(bool nested_menu); 307 308 // AcceleratorPressed is invoked on the hot tracked view if it exists. 309 SendAcceleratorResultType SendAcceleratorToHotTrackedView(); 310 311 void UpdateInitialLocation(const gfx::Rect& bounds, 312 MenuAnchorPosition position, 313 bool context_menu); 314 315 // Invoked when the user accepts the selected item. This is only used 316 // when blocking. This schedules the loop to quit. 317 void Accept(MenuItemView* item, int event_flags); 318 319 bool ShowSiblingMenu(SubmenuView* source, const gfx::Point& mouse_location); 320 321 // Shows a context menu for |menu_item| as a result of a located event if 322 // appropriate. This is invoked on long press and releasing the right mouse 323 // button. Returns whether a context menu was shown. 324 bool ShowContextMenu(MenuItemView* menu_item, 325 SubmenuView* source, 326 const ui::LocatedEvent& event, 327 ui::MenuSourceType source_type); 328 329 // Closes all menus, including any menus of nested invocations of Run. 330 void CloseAllNestedMenus(); 331 332 // Gets the enabled menu item at the specified location. 333 // If over_any_menu is non-null it is set to indicate whether the location 334 // is over any menu. It is possible for this to return NULL, but 335 // over_any_menu to be true. For example, the user clicked on a separator. 336 MenuItemView* GetMenuItemAt(View* menu, int x, int y); 337 338 // If there is an empty menu item at the specified location, it is returned. 339 MenuItemView* GetEmptyMenuItemAt(View* source, int x, int y); 340 341 // Returns true if the coordinate is over the scroll buttons of the 342 // SubmenuView's MenuScrollViewContainer. If true is returned, part is set to 343 // indicate which scroll button the coordinate is. 344 bool IsScrollButtonAt(SubmenuView* source, 345 int x, 346 int y, 347 MenuPart::Type* part); 348 349 // Returns the target for the mouse event. The coordinates are in terms of 350 // source's scroll view container. 351 MenuPart GetMenuPart(SubmenuView* source, const gfx::Point& source_loc); 352 353 // Returns the target for mouse events. The search is done through |item| and 354 // all its parents. 355 MenuPart GetMenuPartByScreenCoordinateUsingMenu(MenuItemView* item, 356 const gfx::Point& screen_loc); 357 358 // Implementation of GetMenuPartByScreenCoordinate for a single menu. Returns 359 // true if the supplied SubmenuView contains the location in terms of the 360 // screen. If it does, part is set appropriately and true is returned. 361 bool GetMenuPartByScreenCoordinateImpl(SubmenuView* menu, 362 const gfx::Point& screen_loc, 363 MenuPart* part); 364 365 // Returns true if the SubmenuView contains the specified location. This does 366 // NOT included the scroll buttons, only the submenu view. 367 bool DoesSubmenuContainLocation(SubmenuView* submenu, 368 const gfx::Point& screen_loc); 369 370 // Opens/Closes the necessary menus such that state_ matches that of 371 // pending_state_. This is invoked if submenus are not opened immediately, 372 // but after a delay. 373 void CommitPendingSelection(); 374 375 // If item has a submenu, it is closed. This does NOT update the selection 376 // in anyway. 377 void CloseMenu(MenuItemView* item); 378 379 // If item has a submenu, it is opened. This does NOT update the selection 380 // in anyway. 381 void OpenMenu(MenuItemView* item); 382 383 // Implementation of OpenMenu. If |show| is true, this invokes show on the 384 // menu, otherwise Reposition is invoked. 385 void OpenMenuImpl(MenuItemView* item, bool show); 386 387 // Invoked when the children of a menu change and the menu is showing. 388 // This closes any submenus and resizes the submenu. 389 void MenuChildrenChanged(MenuItemView* item); 390 391 // Builds the paths of the two menu items into the two paths, and 392 // sets first_diff_at to the location of the first difference between the 393 // two paths. 394 void BuildPathsAndCalculateDiff(MenuItemView* old_item, 395 MenuItemView* new_item, 396 std::vector<MenuItemView*>* old_path, 397 std::vector<MenuItemView*>* new_path, 398 size_t* first_diff_at); 399 400 // Builds the path for the specified item. 401 void BuildMenuItemPath(MenuItemView* item, std::vector<MenuItemView*>* path); 402 403 // Starts/stops the timer that commits the pending state to state 404 // (opens/closes submenus). 405 void StartShowTimer(); 406 void StopShowTimer(); 407 408 // Starts/stops the timer cancel the menu. This is used during drag and 409 // drop when the drop enters/exits the menu. 410 void StartCancelAllTimer(); 411 void StopCancelAllTimer(); 412 413 // Calculates the bounds of the menu to show. is_leading is set to match the 414 // direction the menu opened in. 415 gfx::Rect CalculateMenuBounds(MenuItemView* item, 416 bool prefer_leading, 417 bool* is_leading); 418 419 // Calculates the bubble bounds of the menu to show. is_leading is set to 420 // match the direction the menu opened in. 421 gfx::Rect CalculateBubbleMenuBounds(MenuItemView* item, 422 bool prefer_leading, 423 bool* is_leading); 424 425 // Returns the depth of the menu. 426 static int MenuDepth(MenuItemView* item); 427 428 // Selects the next/previous menu item. 429 void IncrementSelection(int delta); 430 431 // Returns the next selectable child menu item of |parent| starting at |index| 432 // and incrementing index by |delta|. If there are no more selected menu items 433 // NULL is returned. 434 MenuItemView* FindNextSelectableMenuItem(MenuItemView* parent, 435 int index, 436 int delta); 437 438 // If the selected item has a submenu and it isn't currently open, the 439 // the selection is changed such that the menu opens immediately. 440 void OpenSubmenuChangeSelectionIfCan(); 441 442 // If possible, closes the submenu. 443 void CloseSubmenu(); 444 445 // Returns details about which menu items match the mnemonic |key|. 446 // |match_function| is used to determine which menus match. 447 SelectByCharDetails FindChildForMnemonic( 448 MenuItemView* parent, 449 base::char16 key, 450 bool (*match_function)(MenuItemView* menu, base::char16 mnemonic)); 451 452 // Selects or accepts the appropriate menu item based on |details|. Returns 453 // true if |Accept| was invoked (which happens if there aren't multiple item 454 // with the same mnemonic and the item to select does not have a submenu). 455 bool AcceptOrSelect(MenuItemView* parent, const SelectByCharDetails& details); 456 457 // Selects by mnemonic, and if that doesn't work tries the first character of 458 // the title. Returns true if a match was selected and the menu should exit. 459 bool SelectByChar(base::char16 key); 460 461 // For Windows and Aura we repost an event for some events that dismiss 462 // the context menu. The event is then reprocessed to cause its result 463 // if the context menu had not been present. 464 // On non-aura Windows, a new mouse event is generated and posted to 465 // the window (if there is one) at the location of the event. On 466 // aura, the event is reposted on the RootWindow. 467 void RepostEvent(SubmenuView* source, const ui::LocatedEvent& event); 468 469 // Sets the drop target to new_item. 470 void SetDropMenuItem(MenuItemView* new_item, 471 MenuDelegate::DropPosition position); 472 473 // Starts/stops scrolling as appropriate. part gives the part the mouse is 474 // over. 475 void UpdateScrolling(const MenuPart& part); 476 477 // Stops scrolling. 478 void StopScrolling(); 479 480 // Updates active mouse view from the location of the event and sends it 481 // the appropriate events. This is used to send mouse events to child views so 482 // that they react to click-drag-release as if the user clicked on the view 483 // itself. 484 void UpdateActiveMouseView(SubmenuView* event_source, 485 const ui::MouseEvent& event, 486 View* target_menu); 487 488 // Sends a mouse release event to the current active mouse view and sets 489 // it to null. 490 void SendMouseReleaseToActiveView(SubmenuView* event_source, 491 const ui::MouseEvent& event); 492 493 // Sends a mouse capture lost event to the current active mouse view and sets 494 // it to null. 495 void SendMouseCaptureLostToActiveView(); 496 497 // Sets/gets the active mouse view. See UpdateActiveMouseView() for details. 498 void SetActiveMouseView(View* view); 499 View* GetActiveMouseView(); 500 501 // Sets exit type. Calling this can terminate the active nested message-loop. 502 void SetExitType(ExitType type); 503 504 // Terminates the current nested message-loop. 505 void TerminateNestedMessageLoop(); 506 507 // Returns true if SetExitType() should quit the message loop. 508 bool ShouldQuitNow() const; 509 510 // Handles the mouse location event on the submenu |source|. 511 void HandleMouseLocation(SubmenuView* source, 512 const gfx::Point& mouse_location); 513 514 // Retrieve an appropriate Screen. 515 gfx::Screen* GetScreen(); 516 517 // The active instance. 518 static MenuController* active_instance_; 519 520 // If true, Run blocks. If false, Run doesn't block and this is used for 521 // drag and drop. Note that the semantics for drag and drop are slightly 522 // different: cancel timer is kicked off any time the drag moves outside the 523 // menu, mouse events do nothing... 524 bool blocking_run_; 525 526 // If true, we're showing. 527 bool showing_; 528 529 // Indicates what to exit. 530 ExitType exit_type_; 531 532 // Whether we did a capture. We do a capture only if we're blocking and 533 // the mouse was down when Run. 534 bool did_capture_; 535 536 // As the user drags the mouse around pending_state_ changes immediately. 537 // When the user stops moving/dragging the mouse (or clicks the mouse) 538 // pending_state_ is committed to state_, potentially resulting in 539 // opening or closing submenus. This gives a slight delayed effect to 540 // submenus as the user moves the mouse around. This is done so that as the 541 // user moves the mouse all submenus don't immediately pop. 542 State pending_state_; 543 State state_; 544 545 // If the user accepted the selection, this is the result. 546 MenuItemView* result_; 547 548 // The event flags when the user selected the menu. 549 int accept_event_flags_; 550 551 // If not empty, it means we're nested. When Run is invoked from within 552 // Run, the current state (state_) is pushed onto menu_stack_. This allows 553 // MenuController to restore the state when the nested run returns. 554 typedef std::pair<State, linked_ptr<MenuButton::PressedLock> > NestedState; 555 std::list<NestedState> menu_stack_; 556 557 // As the mouse moves around submenus are not opened immediately. Instead 558 // they open after this timer fires. 559 base::OneShotTimer<MenuController> show_timer_; 560 561 // Used to invoke CancelAll(). This is used during drag and drop to hide the 562 // menu after the mouse moves out of the of the menu. This is necessitated by 563 // the lack of an ability to detect when the drag has completed from the drop 564 // side. 565 base::OneShotTimer<MenuController> cancel_all_timer_; 566 567 // Drop target. 568 MenuItemView* drop_target_; 569 MenuDelegate::DropPosition drop_position_; 570 571 // Owner of child windows. 572 // WARNING: this may be NULL. 573 Widget* owner_; 574 575 // Indicates a possible drag operation. 576 bool possible_drag_; 577 578 // True when drag operation is in progress. 579 bool drag_in_progress_; 580 581 // True when the drag operation in progress was initiated by the 582 // MenuController for a child MenuItemView (as opposed to initiated separately 583 // by a child View). 584 bool did_initiate_drag_; 585 586 // Location the mouse was pressed at. Used to detect d&d. 587 gfx::Point press_pt_; 588 589 // We get a slew of drag updated messages as the mouse is over us. To avoid 590 // continually processing whether we can drop, we cache the coordinates. 591 bool valid_drop_coordinates_; 592 gfx::Point drop_pt_; 593 int last_drop_operation_; 594 595 // If true, we're in the middle of invoking ShowAt on a submenu. 596 bool showing_submenu_; 597 598 // Task for scrolling the menu. If non-null indicates a scroll is currently 599 // underway. 600 scoped_ptr<MenuScrollTask> scroll_task_; 601 602 // The lock to keep the menu button pressed while a menu is visible. 603 scoped_ptr<MenuButton::PressedLock> pressed_lock_; 604 605 // ViewStorage id used to store the view mouse drag events are forwarded to. 606 // See UpdateActiveMouseView() for details. 607 const int active_mouse_view_id_; 608 609 internal::MenuControllerDelegate* delegate_; 610 611 // How deep we are in nested message loops. This should be at most 2 (when 612 // showing a context menu from a menu). 613 int message_loop_depth_; 614 615 views::MenuConfig menu_config_; 616 617 // The timestamp of the event which closed the menu - or 0 otherwise. 618 base::TimeDelta closing_event_time_; 619 620 // Time when the menu is first shown. 621 base::TimeTicks menu_start_time_; 622 623 // If a mouse press triggered this menu, this will have its location (in 624 // screen coordinates). Otherwise this will be (0, 0). 625 gfx::Point menu_start_mouse_press_loc_; 626 627 // Controls behavior differences between a combobox and other types of menu 628 // (like a context menu). 629 bool is_combobox_; 630 631 // Set to true if the menu item was selected by touch. 632 bool item_selected_by_touch_; 633 634 scoped_ptr<MenuMessageLoop> message_loop_; 635 636 DISALLOW_COPY_AND_ASSIGN(MenuController); 637 }; 638 639 } // namespace views 640 641 #endif // UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_H_ 642