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