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/base/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 // Various events, forwarded from the submenu. 105 // 106 // NOTE: the coordinates of the events are in that of the 107 // MenuScrollViewContainer. 108 void OnMousePressed(SubmenuView* source, const ui::MouseEvent& event); 109 void OnMouseDragged(SubmenuView* source, const ui::MouseEvent& event); 110 void OnMouseReleased(SubmenuView* source, const ui::MouseEvent& event); 111 void OnMouseMoved(SubmenuView* source, const ui::MouseEvent& event); 112 void OnMouseEntered(SubmenuView* source, const ui::MouseEvent& event); 113 #if defined(OS_LINUX) 114 bool OnMouseWheel(SubmenuView* source, const ui::MouseWheelEvent& event); 115 #endif 116 void OnGestureEvent(SubmenuView* source, ui::GestureEvent* event); 117 118 bool GetDropFormats( 119 SubmenuView* source, 120 int* formats, 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); 128 129 // Invoked from the scroll buttons of the MenuScrollViewContainer. 130 void OnDragEnteredScrollButton(SubmenuView* source, bool is_up); 131 void OnDragExitedScrollButton(SubmenuView* source); 132 133 // Update the submenu's selection based on the current mouse location 134 void UpdateSubmenuSelection(SubmenuView* source); 135 136 // WidgetObserver overrides: 137 virtual void OnWidgetDestroying(Widget* widget) OVERRIDE; 138 139 // Only used for testing. 140 static void TurnOffContextMenuSelectionHoldForTest(); 141 142 private: 143 friend class internal::MenuRunnerImpl; 144 friend class MenuHostRootView; 145 friend class MenuItemView; 146 friend class SubmenuView; 147 148 class MenuScrollTask; 149 150 struct SelectByCharDetails; 151 152 // Values supplied to SetSelection. 153 enum SetSelectionTypes { 154 SELECTION_DEFAULT = 0, 155 156 // If set submenus are opened immediately, otherwise submenus are only 157 // openned after a timer fires. 158 SELECTION_UPDATE_IMMEDIATELY = 1 << 0, 159 160 // If set and the menu_item has a submenu, the submenu is shown. 161 SELECTION_OPEN_SUBMENU = 1 << 1, 162 163 // SetSelection is being invoked as the result exiting or cancelling the 164 // menu. This is used for debugging. 165 SELECTION_EXIT = 1 << 2, 166 }; 167 168 // Result type for SendAcceleratorToHotTrackedView 169 enum SendAcceleratorResultType { 170 // Accelerator is not sent because of no hot tracked views. 171 ACCELERATOR_NOT_PROCESSED, 172 173 // Accelerator is sent to the hot tracked views. 174 ACCELERATOR_PROCESSED, 175 176 // Same as above and the accelerator causes the exit of the menu. 177 ACCELERATOR_PROCESSED_EXIT 178 }; 179 180 // Tracks selection information. 181 struct State { 182 State(); 183 ~State(); 184 185 // The selected menu item. 186 MenuItemView* item; 187 188 // If item has a submenu this indicates if the submenu is showing. 189 bool submenu_open; 190 191 // Bounds passed to the run menu. Used for positioning the first menu. 192 gfx::Rect initial_bounds; 193 194 // Position of the initial menu. 195 MenuItemView::AnchorPosition anchor; 196 197 // The direction child menus have opened in. 198 std::list<bool> open_leading; 199 200 // Bounds for the monitor we're showing on. 201 gfx::Rect monitor_bounds; 202 203 // Is the current menu a context menu. 204 bool context_menu; 205 }; 206 207 // Used by GetMenuPart to indicate the menu part at a particular location. 208 struct MenuPart { 209 // Type of part. 210 enum Type { 211 NONE, 212 MENU_ITEM, 213 SCROLL_UP, 214 SCROLL_DOWN 215 }; 216 217 MenuPart() : type(NONE), menu(NULL), parent(NULL), submenu(NULL) {} 218 219 // Convenience for testing type == SCROLL_DOWN or type == SCROLL_UP. 220 bool is_scroll() const { return type == SCROLL_DOWN || type == SCROLL_UP; } 221 222 // Type of part. 223 Type type; 224 225 // If type is MENU_ITEM, this is the menu item the mouse is over, otherwise 226 // this is NULL. 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 230 // clicked on. 231 MenuItemView* menu; 232 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; 236 237 // This is the submenu the mouse is over. 238 SubmenuView* submenu; 239 }; 240 241 // Sets the selection to |menu_item|. A value of NULL unselects 242 // everything. |types| is a bitmask of |SetSelectionTypes|. 243 // 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); 249 250 void SetSelectionOnPointerDown(SubmenuView* source, 251 const ui::LocatedEvent& event); 252 void StartDrag(SubmenuView* source, const gfx::Point& location); 253 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 bool Dispatch(const base::NativeEvent& event) OVERRIDE; 257 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); 262 263 // Creates a MenuController. If |blocking| is true a nested message loop is 264 // started in |Run|. 265 MenuController(ui::NativeTheme* theme, 266 bool blocking, 267 internal::MenuControllerDelegate* delegate); 268 269 virtual ~MenuController(); 270 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 273 // menu). 274 void RunMessageLoop(bool nested_menu); 275 276 // AcceleratorPressed is invoked on the hot tracked view if it exists. 277 SendAcceleratorResultType SendAcceleratorToHotTrackedView(); 278 279 void UpdateInitialLocation(const gfx::Rect& bounds, 280 MenuItemView::AnchorPosition position, 281 bool context_menu); 282 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); 286 287 bool ShowSiblingMenu(SubmenuView* source, const gfx::Point& mouse_location); 288 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, 293 SubmenuView* source, 294 const ui::LocatedEvent& event, 295 ui::MenuSourceType source_type); 296 297 // Closes all menus, including any menus of nested invocations of Run. 298 void CloseAllNestedMenus(); 299 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); 305 306 // If there is an empty menu item at the specified location, it is returned. 307 MenuItemView* GetEmptyMenuItemAt(View* source, int x, int y); 308 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, 313 int x, 314 int y, 315 MenuPart::Type* part); 316 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); 320 321 // Returns the target for mouse events. The search is done through |item| and 322 // all its parents. 323 MenuPart GetMenuPartByScreenCoordinateUsingMenu(MenuItemView* item, 324 const gfx::Point& screen_loc); 325 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, 331 MenuPart* part); 332 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); 337 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(); 342 343 // If item has a submenu, it is closed. This does NOT update the selection 344 // in anyway. 345 void CloseMenu(MenuItemView* item); 346 347 // If item has a submenu, it is opened. This does NOT update the selection 348 // in anyway. 349 void OpenMenu(MenuItemView* item); 350 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); 354 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); 358 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 361 // two paths. 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); 367 368 // Builds the path for the specified item. 369 void BuildMenuItemPath(MenuItemView* item, std::vector<MenuItemView*>* path); 370 371 // Starts/stops the timer that commits the pending state to state 372 // (opens/closes submenus). 373 void StartShowTimer(); 374 void StopShowTimer(); 375 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(); 380 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, 384 bool prefer_leading, 385 bool* is_leading); 386 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, 390 bool prefer_leading, 391 bool* is_leading); 392 393 // Returns the depth of the menu. 394 static int MenuDepth(MenuItemView* item); 395 396 // Selects the next/previous menu item. 397 void IncrementSelection(int delta); 398 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 401 // NULL is returned. 402 MenuItemView* FindNextSelectableMenuItem(MenuItemView* parent, 403 int index, 404 int delta); 405 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(); 409 410 // If possible, closes the submenu. 411 void CloseSubmenu(); 412 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, 417 char16 key, 418 bool (*match_function)(MenuItemView* menu, char16 mnemonic)); 419 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); 424 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(char16 key); 428 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); 436 437 // Sets the drop target to new_item. 438 void SetDropMenuItem(MenuItemView* new_item, 439 MenuDelegate::DropPosition position); 440 441 // Starts/stops scrolling as appropriate. part gives the part the mouse is 442 // over. 443 void UpdateScrolling(const MenuPart& part); 444 445 // Stops scrolling. 446 void StopScrolling(); 447 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 451 // itself. 452 void UpdateActiveMouseView(SubmenuView* event_source, 453 const ui::MouseEvent& event, 454 View* target_menu); 455 456 // Sends a mouse release event to the current |active_mouse_view_| and sets 457 // it to null. 458 void SendMouseReleaseToActiveView(SubmenuView* event_source, 459 const ui::MouseEvent& event); 460 461 // Sends a mouse capture lost event to the current |active_mouse_view_| and 462 // sets it to null. 463 void SendMouseCaptureLostToActiveView(); 464 465 // Sets exit type. 466 void SetExitType(ExitType type); 467 468 // Returns true if SetExitType() should quit the message loop. 469 bool ShouldQuitNow() const; 470 471 // Handles the mouse location event on the submenu |source|. 472 void HandleMouseLocation(SubmenuView* source, 473 const gfx::Point& mouse_location); 474 475 // Retrieve an appropriate Screen. 476 gfx::Screen* GetScreen(); 477 478 // The active instance. 479 static MenuController* active_instance_; 480 481 // If true, Run blocks. If false, Run doesn't block and this is used for 482 // drag and drop. Note that the semantics for drag and drop are slightly 483 // different: cancel timer is kicked off any time the drag moves outside the 484 // menu, mouse events do nothing... 485 bool blocking_run_; 486 487 // If true, we're showing. 488 bool showing_; 489 490 // Indicates what to exit. 491 ExitType exit_type_; 492 493 // Whether we did a capture. We do a capture only if we're blocking and 494 // the mouse was down when Run. 495 bool did_capture_; 496 497 // As the user drags the mouse around pending_state_ changes immediately. 498 // When the user stops moving/dragging the mouse (or clicks the mouse) 499 // pending_state_ is committed to state_, potentially resulting in 500 // opening or closing submenus. This gives a slight delayed effect to 501 // submenus as the user moves the mouse around. This is done so that as the 502 // user moves the mouse all submenus don't immediately pop. 503 State pending_state_; 504 State state_; 505 506 // If the user accepted the selection, this is the result. 507 MenuItemView* result_; 508 509 // The event flags when the user selected the menu. 510 int accept_event_flags_; 511 512 // If not empty, it means we're nested. When Run is invoked from within 513 // Run, the current state (state_) is pushed onto menu_stack_. This allows 514 // MenuController to restore the state when the nested run returns. 515 std::list<State> menu_stack_; 516 517 // As the mouse moves around submenus are not opened immediately. Instead 518 // they open after this timer fires. 519 base::OneShotTimer<MenuController> show_timer_; 520 521 // Used to invoke CancelAll(). This is used during drag and drop to hide the 522 // menu after the mouse moves out of the of the menu. This is necessitated by 523 // the lack of an ability to detect when the drag has completed from the drop 524 // side. 525 base::OneShotTimer<MenuController> cancel_all_timer_; 526 527 // Drop target. 528 MenuItemView* drop_target_; 529 MenuDelegate::DropPosition drop_position_; 530 531 // Owner of child windows. 532 // WARNING: this may be NULL. 533 Widget* owner_; 534 535 // Indicates a possible drag operation. 536 bool possible_drag_; 537 538 // True when drag operation is in progress. 539 bool drag_in_progress_; 540 541 // Location the mouse was pressed at. Used to detect d&d. 542 gfx::Point press_pt_; 543 544 // We get a slew of drag updated messages as the mouse is over us. To avoid 545 // continually processing whether we can drop, we cache the coordinates. 546 bool valid_drop_coordinates_; 547 gfx::Point drop_pt_; 548 int last_drop_operation_; 549 550 // If true, we're in the middle of invoking ShowAt on a submenu. 551 bool showing_submenu_; 552 553 // Task for scrolling the menu. If non-null indicates a scroll is currently 554 // underway. 555 scoped_ptr<MenuScrollTask> scroll_task_; 556 557 MenuButton* menu_button_; 558 559 // If non-null mouse drag events are forwarded to this view. See 560 // UpdateActiveMouseView for details. 561 View* active_mouse_view_; 562 563 internal::MenuControllerDelegate* delegate_; 564 565 // How deep we are in nested message loops. This should be at most 2 (when 566 // showing a context menu from a menu). 567 int message_loop_depth_; 568 569 views::MenuConfig menu_config_; 570 571 // The timestamp of the event which closed the menu - or 0 otherwise. 572 base::TimeDelta closing_event_time_; 573 574 // Time when the menu is first shown. 575 base::TimeTicks menu_start_time_; 576 577 DISALLOW_COPY_AND_ASSIGN(MenuController); 578 }; 579 580 } // namespace views 581 582 #endif // UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_H_ 583