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.
5 #include "ui/views/controls/menu/menu_runner.h"
9 #include "base/memory/weak_ptr.h"
10 #include "base/metrics/histogram.h"
11 #include "ui/base/models/menu_model.h"
12 #include "ui/views/controls/button/menu_button.h"
13 #include "ui/views/controls/menu/menu_controller.h"
14 #include "ui/views/controls/menu/menu_controller_delegate.h"
15 #include "ui/views/controls/menu/menu_delegate.h"
16 #include "ui/views/controls/menu/menu_model_adapter.h"
17 #include "ui/views/controls/menu/submenu_view.h"
18 #include "ui/views/widget/widget.h"
21 #include "base/win/win_util.h"
28 void RecordSelectedIndexes(const MenuItemView* menu_item) {
31 const MenuItemView* parent = menu_item->GetParentMenuItem();
35 SubmenuView* submenu = parent->GetSubmenu();
36 for (int i = 0; i < submenu->GetMenuItemCount(); ++i) {
37 if (submenu->GetMenuItemAt(i) == menu_item) {
38 UMA_HISTOGRAM_COUNTS_100("MenuSelection.Index", i);
43 RecordSelectedIndexes(parent);
46 void RecordMenuStats(MenuItemView* result, base::TimeDelta time_elapsed) {
47 // Report if user made a selection.
48 UMA_HISTOGRAM_BOOLEAN("MenuSelection.Result", result != NULL);
51 // Report how much time it took to make a selection.
52 UMA_HISTOGRAM_TIMES("MenuSelection.Time", time_elapsed);
53 RecordSelectedIndexes(result);
57 // Manages the menu. To destroy a MenuRunnerImpl invoke Release(). Release()
58 // deletes immediately if the menu isn't showing. If the menu is showing
59 // Release() cancels the menu and when the nested RunMenuAt() call returns
60 // deletes itself and the menu.
61 class MenuRunnerImpl : public internal::MenuControllerDelegate {
63 explicit MenuRunnerImpl(MenuItemView* menu);
65 MenuItemView* menu() { return menu_; }
67 bool running() const { return running_; }
69 // See description above class for details.
73 MenuRunner::RunResult RunMenuAt(Widget* parent,
75 const gfx::Rect& bounds,
76 MenuItemView::AnchorPosition anchor,
77 int32 types) WARN_UNUSED_RESULT;
81 // Returns the time from the event which closed the menu - or 0.
82 base::TimeDelta closing_event_time() const;
84 // MenuControllerDelegate:
85 virtual void DropMenuClosed(NotifyType type, MenuItemView* menu) OVERRIDE;
86 virtual void SiblingMenuCreated(MenuItemView* menu) OVERRIDE;
89 virtual ~MenuRunnerImpl();
91 // Cleans up after the menu is no longer showing. |result| is the menu that
92 // the user selected, or NULL if nothing was selected.
93 MenuRunner::RunResult MenuDone(MenuItemView* result, int mouse_event_flags);
95 // Returns true if mnemonics should be shown in the menu.
96 bool ShouldShowMnemonics(MenuButton* button);
98 // The menu. We own this. We don't use scoped_ptr as the destructor is
99 // protected and we're a friend.
102 // Any sibling menus. Does not include |menu_|. We own these too.
103 std::set<MenuItemView*> sibling_menus_;
105 // Created and set as the delegate of the MenuItemView if Release() is
106 // invoked. This is done to make sure the delegate isn't notified after
107 // Release() is invoked. We do this as we assume the delegate is no longer
108 // valid if MenuRunner has been deleted.
109 scoped_ptr<MenuDelegate> empty_delegate_;
111 // Are we in run waiting for it to return?
114 // Set if |running_| and Release() has been invoked.
115 bool delete_after_run_;
117 // Are we running for a drop?
121 MenuController* controller_;
123 // Do we own the controller?
124 bool owns_controller_;
126 // The timestamp of the event which closed the menu - or 0.
127 base::TimeDelta closing_event_time_;
129 // Used to detect deletion of |this| when notifying delegate of success.
130 base::WeakPtrFactory<MenuRunnerImpl> weak_factory_;
132 DISALLOW_COPY_AND_ASSIGN(MenuRunnerImpl);
135 MenuRunnerImpl::MenuRunnerImpl(MenuItemView* menu)
138 delete_after_run_(false),
141 owns_controller_(false),
142 closing_event_time_(base::TimeDelta()),
143 weak_factory_(this) {
146 void MenuRunnerImpl::Release() {
148 if (delete_after_run_)
149 return; // We already canceled.
151 // The menu is running a nested message loop, we can't delete it now
152 // otherwise the stack would be in a really bad state (many frames would
153 // have deleted objects on them). Instead cancel the menu, when it returns
154 // Holder will delete itself.
155 delete_after_run_ = true;
157 // Swap in a different delegate. That way we know the original MenuDelegate
158 // won't be notified later on (when it's likely already been deleted).
159 if (!empty_delegate_.get())
160 empty_delegate_.reset(new MenuDelegate());
161 menu_->set_delegate(empty_delegate_.get());
164 // Release is invoked when MenuRunner is destroyed. Assume this is happening
165 // because the object referencing the menu has been destroyed and the menu
166 // button is no longer valid.
167 controller_->Cancel(MenuController::EXIT_DESTROYED);
173 MenuRunner::RunResult MenuRunnerImpl::RunMenuAt(
176 const gfx::Rect& bounds,
177 MenuItemView::AnchorPosition anchor,
179 closing_event_time_ = base::TimeDelta();
181 // Ignore requests to show the menu while it's already showing. MenuItemView
182 // doesn't handle this very well (meaning it crashes).
183 return MenuRunner::NORMAL_EXIT;
186 MenuController* controller = MenuController::GetActiveInstance();
188 if ((types & MenuRunner::IS_NESTED) != 0) {
189 if (!controller->IsBlockingRun()) {
190 controller->CancelAll();
194 // There's some other menu open and we're not nested. Cancel the menu.
195 controller->CancelAll();
196 if ((types & MenuRunner::FOR_DROP) == 0) {
197 // We can't open another menu, otherwise the message loop would become
198 // twice nested. This isn't necessarily a problem, but generally isn't
200 return MenuRunner::NORMAL_EXIT;
202 // Drop menus don't block the message loop, so it's ok to create a new
209 for_drop_ = (types & MenuRunner::FOR_DROP) != 0;
210 bool has_mnemonics = (types & MenuRunner::HAS_MNEMONICS) != 0 && !for_drop_;
211 owns_controller_ = false;
213 // No menus are showing, show one.
214 ui::NativeTheme* theme = parent ? parent->GetNativeTheme() :
215 ui::NativeTheme::instance();
216 controller = new MenuController(theme, !for_drop_, this);
217 owns_controller_ = true;
219 controller->set_accept_on_f4((types & MenuRunner::COMBOBOX) != 0);
220 controller_ = controller;
221 menu_->set_controller(controller_);
222 menu_->PrepareForRun(owns_controller_,
224 !for_drop_ && ShouldShowMnemonics(button));
227 base::TimeTicks start_time = base::TimeTicks::Now();
228 int mouse_event_flags = 0;
229 MenuItemView* result = controller->Run(parent, button, menu_, bounds, anchor,
230 (types & MenuRunner::CONTEXT_MENU) != 0,
232 // Get the time of the event which closed this menu.
233 closing_event_time_ = controller->closing_event_time();
235 // Drop menus return immediately. We finish processing in DropMenuClosed.
236 return MenuRunner::NORMAL_EXIT;
238 RecordMenuStats(result, base::TimeTicks::Now() - start_time);
239 return MenuDone(result, mouse_event_flags);
242 void MenuRunnerImpl::Cancel() {
244 controller_->Cancel(MenuController::EXIT_ALL);
247 base::TimeDelta MenuRunnerImpl::closing_event_time() const {
248 return closing_event_time_;
251 void MenuRunnerImpl::DropMenuClosed(NotifyType type, MenuItemView* menu) {
254 if (type == NOTIFY_DELEGATE && menu->GetDelegate()) {
255 // Delegate is null when invoked from the destructor.
256 menu->GetDelegate()->DropMenuClosed(menu);
260 void MenuRunnerImpl::SiblingMenuCreated(MenuItemView* menu) {
261 if (menu != menu_ && sibling_menus_.count(menu) == 0)
262 sibling_menus_.insert(menu);
265 MenuRunnerImpl::~MenuRunnerImpl() {
267 for (std::set<MenuItemView*>::iterator i = sibling_menus_.begin();
268 i != sibling_menus_.end(); ++i)
272 MenuRunner::RunResult MenuRunnerImpl::MenuDone(MenuItemView* result,
273 int mouse_event_flags) {
274 menu_->RemoveEmptyMenus();
275 menu_->set_controller(NULL);
277 if (owns_controller_) {
278 // We created the controller and need to delete it.
280 owns_controller_ = false;
283 // Make sure all the windows we created to show the menus have been
285 menu_->DestroyAllMenuHosts();
286 if (delete_after_run_) {
288 return MenuRunner::MENU_DELETED;
291 if (result && menu_->GetDelegate()) {
292 // Executing the command may also delete this.
293 base::WeakPtr<MenuRunnerImpl> ref(weak_factory_.GetWeakPtr());
294 menu_->GetDelegate()->ExecuteCommand(result->GetCommand(),
297 return MenuRunner::MENU_DELETED;
299 return MenuRunner::NORMAL_EXIT;
302 bool MenuRunnerImpl::ShouldShowMnemonics(MenuButton* button) {
303 // Show mnemonics if the button has focus or alt is pressed.
304 bool show_mnemonics = button ? button->HasFocus() : false;
306 // This is only needed on Windows.
308 show_mnemonics = base::win::IsAltPressed();
310 return show_mnemonics;
313 // In theory we could implement this every where, but for now we're only
314 // implementing it on aura.
315 #if !defined(USE_AURA)
317 DisplayChangeListener* DisplayChangeListener::Create(Widget* widget,
318 MenuRunner* runner) {
323 } // namespace internal
325 MenuRunner::MenuRunner(ui::MenuModel* menu_model)
326 : menu_model_adapter_(new MenuModelAdapter(menu_model)),
327 holder_(new internal::MenuRunnerImpl(menu_model_adapter_->CreateMenu())) {
330 MenuRunner::MenuRunner(MenuItemView* menu)
331 : holder_(new internal::MenuRunnerImpl(menu)) {
334 MenuRunner::~MenuRunner() {
338 MenuItemView* MenuRunner::GetMenu() {
339 return holder_->menu();
342 MenuRunner::RunResult MenuRunner::RunMenuAt(Widget* parent,
344 const gfx::Rect& bounds,
345 MenuItemView::AnchorPosition anchor,
346 ui::MenuSourceType source_type,
348 // The parent of the nested menu will have created a DisplayChangeListener, so
349 // we avoid creating a DisplayChangeListener if nested. Drop menus are
350 // transient, so we don't cancel in that case.
351 if ((types & (IS_NESTED | FOR_DROP)) == 0 && parent) {
352 display_change_listener_.reset(
353 internal::DisplayChangeListener::Create(parent, this));
356 if (types & CONTEXT_MENU) {
357 switch (source_type) {
358 case ui::MENU_SOURCE_NONE:
359 case ui::MENU_SOURCE_KEYBOARD:
360 case ui::MENU_SOURCE_MOUSE:
361 anchor = MenuItemView::TOPLEFT;
363 case ui::MENU_SOURCE_TOUCH:
364 case ui::MENU_SOURCE_TOUCH_EDIT_MENU:
365 anchor = MenuItemView::BOTTOMCENTER;
372 return holder_->RunMenuAt(parent, button, bounds, anchor, types);
375 bool MenuRunner::IsRunning() const {
376 return holder_->running();
379 void MenuRunner::Cancel() {
383 base::TimeDelta MenuRunner::closing_event_time() const {
384 return holder_->closing_event_time();