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_item_view.h"
16#include "ui/views/controls/menu/menu_model_adapter.h"
17#include "ui/views/controls/menu/menu_runner_handler.h"
18#include "ui/views/widget/widget.h"
19
20#if defined(OS_WIN)
21#include "base/win/win_util.h"
22#endif
23
24namespace views {
25
26namespace internal {
27
28// Manages the menu. To destroy a MenuRunnerImpl invoke Release(). Release()
29// deletes immediately if the menu isn't showing. If the menu is showing
30// Release() cancels the menu and when the nested RunMenuAt() call returns
31// deletes itself and the menu.
32class MenuRunnerImpl : public internal::MenuControllerDelegate {
33 public:
34  explicit MenuRunnerImpl(MenuItemView* menu);
35
36  MenuItemView* menu() { return menu_; }
37
38  bool running() const { return running_; }
39
40  // See description above class for details.
41  void Release();
42
43  // Runs the menu.
44  MenuRunner::RunResult RunMenuAt(Widget* parent,
45                                  MenuButton* button,
46                                  const gfx::Rect& bounds,
47                                  MenuAnchorPosition anchor,
48                                  int32 types) WARN_UNUSED_RESULT;
49
50  void Cancel();
51
52  // Returns the time from the event which closed the menu - or 0.
53  base::TimeDelta closing_event_time() const;
54
55  // MenuControllerDelegate:
56  virtual void DropMenuClosed(NotifyType type, MenuItemView* menu) OVERRIDE;
57  virtual void SiblingMenuCreated(MenuItemView* menu) OVERRIDE;
58
59 private:
60  virtual ~MenuRunnerImpl();
61
62  // Cleans up after the menu is no longer showing. |result| is the menu that
63  // the user selected, or NULL if nothing was selected.
64  MenuRunner::RunResult MenuDone(MenuItemView* result, int mouse_event_flags);
65
66  // Returns true if mnemonics should be shown in the menu.
67  bool ShouldShowMnemonics(MenuButton* button);
68
69  // The menu. We own this. We don't use scoped_ptr as the destructor is
70  // protected and we're a friend.
71  MenuItemView* menu_;
72
73  // Any sibling menus. Does not include |menu_|. We own these too.
74  std::set<MenuItemView*> sibling_menus_;
75
76  // Created and set as the delegate of the MenuItemView if Release() is
77  // invoked.  This is done to make sure the delegate isn't notified after
78  // Release() is invoked. We do this as we assume the delegate is no longer
79  // valid if MenuRunner has been deleted.
80  scoped_ptr<MenuDelegate> empty_delegate_;
81
82  // Are we in run waiting for it to return?
83  bool running_;
84
85  // Set if |running_| and Release() has been invoked.
86  bool delete_after_run_;
87
88  // Are we running for a drop?
89  bool for_drop_;
90
91  // The controller.
92  MenuController* controller_;
93
94  // Do we own the controller?
95  bool owns_controller_;
96
97  // The timestamp of the event which closed the menu - or 0.
98  base::TimeDelta closing_event_time_;
99
100  // Used to detect deletion of |this| when notifying delegate of success.
101  base::WeakPtrFactory<MenuRunnerImpl> weak_factory_;
102
103  DISALLOW_COPY_AND_ASSIGN(MenuRunnerImpl);
104};
105
106MenuRunnerImpl::MenuRunnerImpl(MenuItemView* menu)
107    : menu_(menu),
108      running_(false),
109      delete_after_run_(false),
110      for_drop_(false),
111      controller_(NULL),
112      owns_controller_(false),
113      closing_event_time_(base::TimeDelta()),
114      weak_factory_(this) {
115}
116
117void MenuRunnerImpl::Release() {
118  if (running_) {
119    if (delete_after_run_)
120      return;  // We already canceled.
121
122    // The menu is running a nested message loop, we can't delete it now
123    // otherwise the stack would be in a really bad state (many frames would
124    // have deleted objects on them). Instead cancel the menu, when it returns
125    // Holder will delete itself.
126    delete_after_run_ = true;
127
128    // Swap in a different delegate. That way we know the original MenuDelegate
129    // won't be notified later on (when it's likely already been deleted).
130    if (!empty_delegate_.get())
131      empty_delegate_.reset(new MenuDelegate());
132    menu_->set_delegate(empty_delegate_.get());
133
134    DCHECK(controller_);
135    // Release is invoked when MenuRunner is destroyed. Assume this is happening
136    // because the object referencing the menu has been destroyed and the menu
137    // button is no longer valid.
138    controller_->Cancel(MenuController::EXIT_DESTROYED);
139  } else {
140    delete this;
141  }
142}
143
144MenuRunner::RunResult MenuRunnerImpl::RunMenuAt(Widget* parent,
145                                                MenuButton* button,
146                                                const gfx::Rect& bounds,
147                                                MenuAnchorPosition 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_is_combobox((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
210void MenuRunnerImpl::Cancel() {
211  if (running_)
212    controller_->Cancel(MenuController::EXIT_ALL);
213}
214
215base::TimeDelta MenuRunnerImpl::closing_event_time() const {
216  return closing_event_time_;
217}
218
219void 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
228void MenuRunnerImpl::SiblingMenuCreated(MenuItemView* menu) {
229  if (menu != menu_ && sibling_menus_.count(menu) == 0)
230    sibling_menus_.insert(menu);
231}
232
233MenuRunnerImpl::~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
240MenuRunner::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
270bool 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}  // namespace internal
282
283MenuRunner::MenuRunner(ui::MenuModel* menu_model)
284    : menu_model_adapter_(new MenuModelAdapter(menu_model)),
285      holder_(new internal::MenuRunnerImpl(menu_model_adapter_->CreateMenu())) {
286}
287
288MenuRunner::MenuRunner(MenuItemView* menu)
289    : holder_(new internal::MenuRunnerImpl(menu)) {
290}
291
292MenuRunner::~MenuRunner() {
293  holder_->Release();
294}
295
296MenuItemView* MenuRunner::GetMenu() {
297  return holder_->menu();
298}
299
300MenuRunner::RunResult MenuRunner::RunMenuAt(Widget* parent,
301                                            MenuButton* button,
302                                            const gfx::Rect& bounds,
303                                            MenuAnchorPosition anchor,
304                                            ui::MenuSourceType source_type,
305                                            int32 types) {
306  if (runner_handler_.get()) {
307    return runner_handler_->RunMenuAt(parent, button, bounds, anchor,
308                                      source_type, types);
309  }
310
311  // The parent of the nested menu will have created a DisplayChangeListener, so
312  // we avoid creating a DisplayChangeListener if nested. Drop menus are
313  // transient, so we don't cancel in that case.
314  if ((types & (IS_NESTED | FOR_DROP)) == 0 && parent) {
315    display_change_listener_.reset(
316        internal::DisplayChangeListener::Create(parent, this));
317  }
318
319  if (types & CONTEXT_MENU) {
320    switch (source_type) {
321      case ui::MENU_SOURCE_NONE:
322      case ui::MENU_SOURCE_KEYBOARD:
323      case ui::MENU_SOURCE_MOUSE:
324        anchor = MENU_ANCHOR_TOPLEFT;
325        break;
326      case ui::MENU_SOURCE_TOUCH:
327      case ui::MENU_SOURCE_TOUCH_EDIT_MENU:
328        anchor = MENU_ANCHOR_BOTTOMCENTER;
329        break;
330      default:
331        break;
332    }
333  }
334
335  return holder_->RunMenuAt(parent, button, bounds, anchor, types);
336}
337
338bool MenuRunner::IsRunning() const {
339  return holder_->running();
340}
341
342void MenuRunner::Cancel() {
343  holder_->Cancel();
344}
345
346base::TimeDelta MenuRunner::closing_event_time() const {
347  return holder_->closing_event_time();
348}
349
350void MenuRunner::SetRunnerHandler(
351    scoped_ptr<MenuRunnerHandler> runner_handler) {
352  runner_handler_ = runner_handler.Pass();
353}
354
355}  // namespace views
356