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