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 #include "ui/views/controls/menu/menu_runner.h"
      6 
      7 #include <set>
      8 
      9 #include "base/metrics/histogram.h"
     10 #include "ui/base/models/menu_model.h"
     11 #include "ui/views/controls/button/menu_button.h"
     12 #include "ui/views/controls/menu/menu_controller.h"
     13 #include "ui/views/controls/menu/menu_controller_delegate.h"
     14 #include "ui/views/controls/menu/menu_delegate.h"
     15 #include "ui/views/controls/menu/menu_model_adapter.h"
     16 #include "ui/views/controls/menu/submenu_view.h"
     17 #include "ui/views/widget/widget.h"
     18 
     19 #if defined(OS_WIN)
     20 #include "base/win/win_util.h"
     21 #endif
     22 
     23 namespace views {
     24 
     25 namespace internal {
     26 
     27 void RecordSelectedIndexes(const MenuItemView* menu_item) {
     28   if (!menu_item)
     29     return;
     30   const MenuItemView* parent = menu_item->GetParentMenuItem();
     31   if (!parent)
     32     return;
     33 
     34   SubmenuView* submenu = parent->GetSubmenu();
     35   for (int i = 0; i < submenu->GetMenuItemCount(); ++i) {
     36     if (submenu->GetMenuItemAt(i) == menu_item) {
     37       UMA_HISTOGRAM_COUNTS_100("MenuSelection.Index", i);
     38       break;
     39     }
     40   }
     41 
     42   RecordSelectedIndexes(parent);
     43 }
     44 
     45 void RecordMenuStats(MenuItemView* result, base::TimeDelta time_elapsed) {
     46   // Report if user made a selection.
     47   UMA_HISTOGRAM_BOOLEAN("MenuSelection.Result", result != NULL);
     48 
     49   if (result) {
     50     // Report how much time it took to make a selection.
     51     UMA_HISTOGRAM_TIMES("MenuSelection.Time", time_elapsed);
     52     RecordSelectedIndexes(result);
     53   }
     54 }
     55 
     56 // Manages the menu. To destroy a MenuRunnerImpl invoke Release(). Release()
     57 // deletes immediately if the menu isn't showing. If the menu is showing
     58 // Release() cancels the menu and when the nested RunMenuAt() call returns
     59 // deletes itself and the menu.
     60 class MenuRunnerImpl : public internal::MenuControllerDelegate {
     61  public:
     62   explicit MenuRunnerImpl(MenuItemView* menu);
     63 
     64   MenuItemView* menu() { return menu_; }
     65 
     66   bool running() const { return running_; }
     67 
     68   // See description above class for details.
     69   void Release();
     70 
     71   // Runs the menu.
     72   MenuRunner::RunResult RunMenuAt(Widget* parent,
     73                                   MenuButton* button,
     74                                   const gfx::Rect& bounds,
     75                                   MenuItemView::AnchorPosition anchor,
     76                                   int32 types) WARN_UNUSED_RESULT;
     77 
     78   void Cancel();
     79 
     80   // Returns the time from the event which closed the menu - or 0.
     81   base::TimeDelta closing_event_time() const;
     82 
     83   // MenuControllerDelegate:
     84   virtual void DropMenuClosed(NotifyType type, MenuItemView* menu) OVERRIDE;
     85   virtual void SiblingMenuCreated(MenuItemView* menu) OVERRIDE;
     86 
     87  private:
     88   virtual ~MenuRunnerImpl();
     89 
     90   // Cleans up after the menu is no longer showing. |result| is the menu that
     91   // the user selected, or NULL if nothing was selected.
     92   MenuRunner::RunResult MenuDone(MenuItemView* result, int mouse_event_flags);
     93 
     94   // Returns true if mnemonics should be shown in the menu.
     95   bool ShouldShowMnemonics(MenuButton* button);
     96 
     97   // The menu. We own this. We don't use scoped_ptr as the destructor is
     98   // protected and we're a friend.
     99   MenuItemView* menu_;
    100 
    101   // Any sibling menus. Does not include |menu_|. We own these too.
    102   std::set<MenuItemView*> sibling_menus_;
    103 
    104   // Created and set as the delegate of the MenuItemView if Release() is
    105   // invoked.  This is done to make sure the delegate isn't notified after
    106   // Release() is invoked. We do this as we assume the delegate is no longer
    107   // valid if MenuRunner has been deleted.
    108   scoped_ptr<MenuDelegate> empty_delegate_;
    109 
    110   // Are we in run waiting for it to return?
    111   bool running_;
    112 
    113   // Set if |running_| and Release() has been invoked.
    114   bool delete_after_run_;
    115 
    116   // Are we running for a drop?
    117   bool for_drop_;
    118 
    119   // The controller.
    120   MenuController* controller_;
    121 
    122   // Do we own the controller?
    123   bool owns_controller_;
    124 
    125   // The timestamp of the event which closed the menu - or 0.
    126   base::TimeDelta closing_event_time_;
    127 
    128   DISALLOW_COPY_AND_ASSIGN(MenuRunnerImpl);
    129 };
    130 
    131 MenuRunnerImpl::MenuRunnerImpl(MenuItemView* menu)
    132     : menu_(menu),
    133       running_(false),
    134       delete_after_run_(false),
    135       for_drop_(false),
    136       controller_(NULL),
    137       owns_controller_(false),
    138       closing_event_time_(base::TimeDelta()) {
    139 }
    140 
    141 void MenuRunnerImpl::Release() {
    142   if (running_) {
    143     if (delete_after_run_)
    144       return;  // We already canceled.
    145 
    146     // The menu is running a nested message loop, we can't delete it now
    147     // otherwise the stack would be in a really bad state (many frames would
    148     // have deleted objects on them). Instead cancel the menu, when it returns
    149     // Holder will delete itself.
    150     delete_after_run_ = true;
    151 
    152     // Swap in a different delegate. That way we know the original MenuDelegate
    153     // won't be notified later on (when it's likely already been deleted).
    154     if (!empty_delegate_.get())
    155       empty_delegate_.reset(new MenuDelegate());
    156     menu_->set_delegate(empty_delegate_.get());
    157 
    158     DCHECK(controller_);
    159     // Release is invoked when MenuRunner is destroyed. Assume this is happening
    160     // because the object referencing the menu has been destroyed and the menu
    161     // button is no longer valid.
    162     controller_->Cancel(MenuController::EXIT_DESTROYED);
    163   } else {
    164     delete this;
    165   }
    166 }
    167 
    168 MenuRunner::RunResult MenuRunnerImpl::RunMenuAt(
    169     Widget* parent,
    170     MenuButton* button,
    171     const gfx::Rect& bounds,
    172     MenuItemView::AnchorPosition anchor,
    173     int32 types) {
    174   closing_event_time_ = base::TimeDelta();
    175   if (running_) {
    176     // Ignore requests to show the menu while it's already showing. MenuItemView
    177     // doesn't handle this very well (meaning it crashes).
    178     return MenuRunner::NORMAL_EXIT;
    179   }
    180 
    181   MenuController* controller = MenuController::GetActiveInstance();
    182   if (controller) {
    183     if ((types & MenuRunner::IS_NESTED) != 0) {
    184       if (!controller->IsBlockingRun()) {
    185         controller->CancelAll();
    186         controller = NULL;
    187       }
    188     } else {
    189       // There's some other menu open and we're not nested. Cancel the menu.
    190       controller->CancelAll();
    191       if ((types & MenuRunner::FOR_DROP) == 0) {
    192         // We can't open another menu, otherwise the message loop would become
    193         // twice nested. This isn't necessarily a problem, but generally isn't
    194         // expected.
    195         return MenuRunner::NORMAL_EXIT;
    196       }
    197       // Drop menus don't block the message loop, so it's ok to create a new
    198       // MenuController.
    199       controller = NULL;
    200     }
    201   }
    202 
    203   running_ = true;
    204   for_drop_ = (types & MenuRunner::FOR_DROP) != 0;
    205   bool has_mnemonics = (types & MenuRunner::HAS_MNEMONICS) != 0 && !for_drop_;
    206   owns_controller_ = false;
    207   if (!controller) {
    208     // No menus are showing, show one.
    209     ui::NativeTheme* theme = parent ? parent->GetNativeTheme() :
    210         ui::NativeTheme::instance();
    211     controller = new MenuController(theme, !for_drop_, this);
    212     owns_controller_ = true;
    213   }
    214   controller_ = controller;
    215   menu_->set_controller(controller_);
    216   menu_->PrepareForRun(owns_controller_,
    217                        has_mnemonics,
    218                        !for_drop_ && ShouldShowMnemonics(button));
    219 
    220   // Run the loop.
    221   base::TimeTicks start_time = base::TimeTicks::Now();
    222   int mouse_event_flags = 0;
    223   MenuItemView* result = controller->Run(parent, button, menu_, bounds, anchor,
    224                                         (types & MenuRunner::CONTEXT_MENU) != 0,
    225                                          &mouse_event_flags);
    226   // Get the time of the event which closed this menu.
    227   closing_event_time_ = controller->closing_event_time();
    228   if (for_drop_) {
    229     // Drop menus return immediately. We finish processing in DropMenuClosed.
    230     return MenuRunner::NORMAL_EXIT;
    231   }
    232   RecordMenuStats(result, base::TimeTicks::Now() - start_time);
    233   return MenuDone(result, mouse_event_flags);
    234 }
    235 
    236 void MenuRunnerImpl::Cancel() {
    237   if (running_)
    238     controller_->Cancel(MenuController::EXIT_ALL);
    239 }
    240 
    241 base::TimeDelta MenuRunnerImpl::closing_event_time() const {
    242   return closing_event_time_;
    243 }
    244 
    245 void MenuRunnerImpl::DropMenuClosed(NotifyType type, MenuItemView* menu) {
    246   MenuDone(NULL, 0);
    247 
    248   if (type == NOTIFY_DELEGATE && menu->GetDelegate()) {
    249     // Delegate is null when invoked from the destructor.
    250     menu->GetDelegate()->DropMenuClosed(menu);
    251   }
    252 }
    253 
    254 void MenuRunnerImpl::SiblingMenuCreated(MenuItemView* menu) {
    255   if (menu != menu_ && sibling_menus_.count(menu) == 0)
    256     sibling_menus_.insert(menu);
    257 }
    258 
    259 MenuRunnerImpl::~MenuRunnerImpl() {
    260   delete menu_;
    261   for (std::set<MenuItemView*>::iterator i = sibling_menus_.begin();
    262        i != sibling_menus_.end(); ++i)
    263     delete *i;
    264 }
    265 
    266 MenuRunner::RunResult MenuRunnerImpl::MenuDone(MenuItemView* result,
    267                                                int mouse_event_flags) {
    268   menu_->RemoveEmptyMenus();
    269   menu_->set_controller(NULL);
    270 
    271   if (owns_controller_) {
    272     // We created the controller and need to delete it.
    273     delete controller_;
    274     owns_controller_ = false;
    275   }
    276   controller_ = NULL;
    277   // Make sure all the windows we created to show the menus have been
    278   // destroyed.
    279   menu_->DestroyAllMenuHosts();
    280   if (delete_after_run_) {
    281     delete this;
    282     return MenuRunner::MENU_DELETED;
    283   }
    284   running_ = false;
    285   if (result && menu_->GetDelegate()) {
    286     menu_->GetDelegate()->ExecuteCommand(result->GetCommand(),
    287                                          mouse_event_flags);
    288   }
    289   return MenuRunner::NORMAL_EXIT;
    290 }
    291 
    292 bool MenuRunnerImpl::ShouldShowMnemonics(MenuButton* button) {
    293   // Show mnemonics if the button has focus or alt is pressed.
    294   bool show_mnemonics = button ? button->HasFocus() : false;
    295 #if defined(OS_WIN)
    296   // This is only needed on Windows.
    297   if (!show_mnemonics)
    298     show_mnemonics = base::win::IsAltPressed();
    299 #endif
    300   return show_mnemonics;
    301 }
    302 
    303 // In theory we could implement this every where, but for now we're only
    304 // implementing it on aura.
    305 #if !defined(USE_AURA)
    306 // static
    307 DisplayChangeListener* DisplayChangeListener::Create(Widget* widget,
    308                                                      MenuRunner* runner) {
    309   return NULL;
    310 }
    311 #endif
    312 
    313 }  // namespace internal
    314 
    315 MenuRunner::MenuRunner(ui::MenuModel* menu_model)
    316     : menu_model_adapter_(new MenuModelAdapter(menu_model)),
    317       holder_(new internal::MenuRunnerImpl(menu_model_adapter_->CreateMenu())) {
    318 }
    319 
    320 MenuRunner::MenuRunner(MenuItemView* menu)
    321     : holder_(new internal::MenuRunnerImpl(menu)) {
    322 }
    323 
    324 MenuRunner::~MenuRunner() {
    325   holder_->Release();
    326 }
    327 
    328 MenuItemView* MenuRunner::GetMenu() {
    329   return holder_->menu();
    330 }
    331 
    332 MenuRunner::RunResult MenuRunner::RunMenuAt(Widget* parent,
    333                                             MenuButton* button,
    334                                             const gfx::Rect& bounds,
    335                                             MenuItemView::AnchorPosition anchor,
    336                                             ui::MenuSourceType source_type,
    337                                             int32 types) {
    338   // The parent of the nested menu will have created a DisplayChangeListener, so
    339   // we avoid creating a DisplayChangeListener if nested. Drop menus are
    340   // transient, so we don't cancel in that case.
    341   if ((types & (IS_NESTED | FOR_DROP)) == 0 && parent) {
    342     display_change_listener_.reset(
    343         internal::DisplayChangeListener::Create(parent, this));
    344   }
    345 
    346   if (types & CONTEXT_MENU) {
    347     switch (source_type) {
    348       case ui::MENU_SOURCE_NONE:
    349       case ui::MENU_SOURCE_KEYBOARD:
    350       case ui::MENU_SOURCE_MOUSE:
    351         anchor = MenuItemView::TOPLEFT;
    352         break;
    353       case ui::MENU_SOURCE_TOUCH:
    354       case ui::MENU_SOURCE_TOUCH_EDIT_MENU:
    355         anchor = MenuItemView::BOTTOMCENTER;
    356         break;
    357       default:
    358         break;
    359     }
    360   }
    361 
    362   return holder_->RunMenuAt(parent, button, bounds, anchor, types);
    363 }
    364 
    365 bool MenuRunner::IsRunning() const {
    366   return holder_->running();
    367 }
    368 
    369 void MenuRunner::Cancel() {
    370   holder_->Cancel();
    371 }
    372 
    373 base::TimeDelta MenuRunner::closing_event_time() const {
    374   return holder_->closing_event_time();
    375 }
    376 
    377 }  // namespace views
    378