1// Copyright 2014 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_impl.h"
6
7#include "ui/native_theme/native_theme.h"
8#include "ui/views/controls/button/menu_button.h"
9#include "ui/views/controls/menu/menu_controller.h"
10#include "ui/views/controls/menu/menu_delegate.h"
11#include "ui/views/controls/menu/menu_item_view.h"
12#include "ui/views/controls/menu/menu_runner_impl_adapter.h"
13#include "ui/views/widget/widget.h"
14
15#if defined(OS_WIN)
16#include "base/win/win_util.h"
17#endif
18
19namespace views {
20namespace internal {
21
22#if !defined(OS_MACOSX)
23MenuRunnerImplInterface* MenuRunnerImplInterface::Create(
24    ui::MenuModel* menu_model,
25    int32 run_types) {
26  return new MenuRunnerImplAdapter(menu_model);
27}
28#endif
29
30MenuRunnerImpl::MenuRunnerImpl(MenuItemView* menu)
31    : menu_(menu),
32      running_(false),
33      delete_after_run_(false),
34      for_drop_(false),
35      controller_(NULL),
36      owns_controller_(false),
37      closing_event_time_(base::TimeDelta()),
38      weak_factory_(this) {
39}
40
41bool MenuRunnerImpl::IsRunning() const {
42  return running_;
43}
44
45void MenuRunnerImpl::Release() {
46  if (running_) {
47    if (delete_after_run_)
48      return;  // We already canceled.
49
50    // The menu is running a nested message loop, we can't delete it now
51    // otherwise the stack would be in a really bad state (many frames would
52    // have deleted objects on them). Instead cancel the menu, when it returns
53    // Holder will delete itself.
54    delete_after_run_ = true;
55
56    // Swap in a different delegate. That way we know the original MenuDelegate
57    // won't be notified later on (when it's likely already been deleted).
58    if (!empty_delegate_.get())
59      empty_delegate_.reset(new MenuDelegate());
60    menu_->set_delegate(empty_delegate_.get());
61
62    DCHECK(controller_);
63    // Release is invoked when MenuRunner is destroyed. Assume this is happening
64    // because the object referencing the menu has been destroyed and the menu
65    // button is no longer valid.
66    controller_->Cancel(MenuController::EXIT_DESTROYED);
67  } else {
68    delete this;
69  }
70}
71
72MenuRunner::RunResult MenuRunnerImpl::RunMenuAt(Widget* parent,
73                                                MenuButton* button,
74                                                const gfx::Rect& bounds,
75                                                MenuAnchorPosition anchor,
76                                                int32 run_types) {
77  closing_event_time_ = base::TimeDelta();
78  if (running_) {
79    // Ignore requests to show the menu while it's already showing. MenuItemView
80    // doesn't handle this very well (meaning it crashes).
81    return MenuRunner::NORMAL_EXIT;
82  }
83
84  MenuController* controller = MenuController::GetActiveInstance();
85  if (controller) {
86    if ((run_types & MenuRunner::IS_NESTED) != 0) {
87      if (!controller->IsBlockingRun()) {
88        controller->CancelAll();
89        controller = NULL;
90      }
91    } else {
92      // There's some other menu open and we're not nested. Cancel the menu.
93      controller->CancelAll();
94      if ((run_types & MenuRunner::FOR_DROP) == 0) {
95        // We can't open another menu, otherwise the message loop would become
96        // twice nested. This isn't necessarily a problem, but generally isn't
97        // expected.
98        return MenuRunner::NORMAL_EXIT;
99      }
100      // Drop menus don't block the message loop, so it's ok to create a new
101      // MenuController.
102      controller = NULL;
103    }
104  }
105
106  running_ = true;
107  for_drop_ = (run_types & MenuRunner::FOR_DROP) != 0;
108  bool has_mnemonics = (run_types & MenuRunner::HAS_MNEMONICS) != 0;
109  owns_controller_ = false;
110  if (!controller) {
111    // No menus are showing, show one.
112    ui::NativeTheme* theme =
113        parent ? parent->GetNativeTheme() : ui::NativeTheme::instance();
114    controller = new MenuController(theme, !for_drop_, this);
115    owns_controller_ = true;
116  }
117  controller->set_is_combobox((run_types & MenuRunner::COMBOBOX) != 0);
118  controller_ = controller;
119  menu_->set_controller(controller_);
120  menu_->PrepareForRun(owns_controller_,
121                       has_mnemonics,
122                       !for_drop_ && ShouldShowMnemonics(button));
123
124  // Run the loop.
125  int mouse_event_flags = 0;
126  MenuItemView* result =
127      controller->Run(parent,
128                      button,
129                      menu_,
130                      bounds,
131                      anchor,
132                      (run_types & MenuRunner::CONTEXT_MENU) != 0,
133                      (run_types & MenuRunner::NESTED_DRAG) != 0,
134                      &mouse_event_flags);
135  // Get the time of the event which closed this menu.
136  closing_event_time_ = controller->closing_event_time();
137  if (for_drop_) {
138    // Drop menus return immediately. We finish processing in DropMenuClosed.
139    return MenuRunner::NORMAL_EXIT;
140  }
141  return MenuDone(result, mouse_event_flags);
142}
143
144void MenuRunnerImpl::Cancel() {
145  if (running_)
146    controller_->Cancel(MenuController::EXIT_ALL);
147}
148
149base::TimeDelta MenuRunnerImpl::GetClosingEventTime() const {
150  return closing_event_time_;
151}
152
153void MenuRunnerImpl::DropMenuClosed(NotifyType type, MenuItemView* menu) {
154  MenuDone(NULL, 0);
155
156  if (type == NOTIFY_DELEGATE && menu->GetDelegate()) {
157    // Delegate is null when invoked from the destructor.
158    menu->GetDelegate()->DropMenuClosed(menu);
159  }
160}
161
162void MenuRunnerImpl::SiblingMenuCreated(MenuItemView* menu) {
163  if (menu != menu_ && sibling_menus_.count(menu) == 0)
164    sibling_menus_.insert(menu);
165}
166
167MenuRunnerImpl::~MenuRunnerImpl() {
168  delete menu_;
169  for (std::set<MenuItemView*>::iterator i = sibling_menus_.begin();
170       i != sibling_menus_.end();
171       ++i)
172    delete *i;
173}
174
175MenuRunner::RunResult MenuRunnerImpl::MenuDone(MenuItemView* result,
176                                               int mouse_event_flags) {
177  menu_->RemoveEmptyMenus();
178  menu_->set_controller(NULL);
179
180  if (owns_controller_) {
181    // We created the controller and need to delete it.
182    delete controller_;
183    owns_controller_ = false;
184  }
185  controller_ = NULL;
186  // Make sure all the windows we created to show the menus have been
187  // destroyed.
188  menu_->DestroyAllMenuHosts();
189  if (delete_after_run_) {
190    delete this;
191    return MenuRunner::MENU_DELETED;
192  }
193  running_ = false;
194  if (result && menu_->GetDelegate()) {
195    // Executing the command may also delete this.
196    base::WeakPtr<MenuRunnerImpl> ref(weak_factory_.GetWeakPtr());
197    menu_->GetDelegate()->ExecuteCommand(result->GetCommand(),
198                                         mouse_event_flags);
199    if (!ref)
200      return MenuRunner::MENU_DELETED;
201  }
202  return MenuRunner::NORMAL_EXIT;
203}
204
205bool MenuRunnerImpl::ShouldShowMnemonics(MenuButton* button) {
206  // Show mnemonics if the button has focus or alt is pressed.
207  bool show_mnemonics = button ? button->HasFocus() : false;
208#if defined(OS_WIN)
209  // This is only needed on Windows.
210  if (!show_mnemonics)
211    show_mnemonics = base::win::IsAltPressed();
212#endif
213  return show_mnemonics;
214}
215
216}  // namespace internal
217}  // namespace views
218