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