Home | History | Annotate | Download | only in menu
      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