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_message_loop_aura.h"
6
7#if defined(OS_WIN)
8#include <windowsx.h>
9#endif
10
11#include "base/run_loop.h"
12#include "ui/aura/client/screen_position_client.h"
13#include "ui/aura/env.h"
14#include "ui/aura/window.h"
15#include "ui/aura/window_event_dispatcher.h"
16#include "ui/aura/window_tree_host.h"
17#include "ui/events/event.h"
18#include "ui/events/platform/platform_event_source.h"
19#include "ui/events/platform/scoped_event_dispatcher.h"
20#include "ui/views/controls/menu/menu_controller.h"
21#include "ui/views/widget/widget.h"
22#include "ui/wm/public/activation_change_observer.h"
23#include "ui/wm/public/activation_client.h"
24#include "ui/wm/public/dispatcher_client.h"
25#include "ui/wm/public/drag_drop_client.h"
26
27#if defined(OS_WIN)
28#include "ui/base/win/internal_constants.h"
29#include "ui/views/controls/menu/menu_message_pump_dispatcher_win.h"
30#include "ui/views/win/hwnd_util.h"
31#else
32#include "ui/views/controls/menu/menu_event_dispatcher_linux.h"
33#endif
34
35using aura::client::ScreenPositionClient;
36
37namespace views {
38
39namespace {
40
41aura::Window* GetOwnerRootWindow(views::Widget* owner) {
42  return owner ? owner->GetNativeWindow()->GetRootWindow() : NULL;
43}
44
45// ActivationChangeObserverImpl is used to observe activation changes and close
46// the menu. Additionally it listens for the root window to be destroyed and
47// cancel the menu as well.
48class ActivationChangeObserverImpl
49    : public aura::client::ActivationChangeObserver,
50      public aura::WindowObserver,
51      public ui::EventHandler {
52 public:
53  ActivationChangeObserverImpl(MenuController* controller, aura::Window* root)
54      : controller_(controller), root_(root) {
55    aura::client::GetActivationClient(root_)->AddObserver(this);
56    root_->AddObserver(this);
57    root_->AddPreTargetHandler(this);
58  }
59
60  virtual ~ActivationChangeObserverImpl() { Cleanup(); }
61
62  // aura::client::ActivationChangeObserver:
63  virtual void OnWindowActivated(aura::Window* gained_active,
64                                 aura::Window* lost_active) OVERRIDE {
65    if (!controller_->drag_in_progress())
66      controller_->CancelAll();
67  }
68
69  // aura::WindowObserver:
70  virtual void OnWindowDestroying(aura::Window* window) OVERRIDE { Cleanup(); }
71
72  // ui::EventHandler:
73  virtual void OnCancelMode(ui::CancelModeEvent* event) OVERRIDE {
74    controller_->CancelAll();
75  }
76
77 private:
78  void Cleanup() {
79    if (!root_)
80      return;
81    // The ActivationClient may have been destroyed by the time we get here.
82    aura::client::ActivationClient* client =
83        aura::client::GetActivationClient(root_);
84    if (client)
85      client->RemoveObserver(this);
86    root_->RemovePreTargetHandler(this);
87    root_->RemoveObserver(this);
88    root_ = NULL;
89  }
90
91  MenuController* controller_;
92  aura::Window* root_;
93
94  DISALLOW_COPY_AND_ASSIGN(ActivationChangeObserverImpl);
95};
96
97}  // namespace
98
99// static
100MenuMessageLoop* MenuMessageLoop::Create() {
101  return new MenuMessageLoopAura;
102}
103
104MenuMessageLoopAura::MenuMessageLoopAura() : owner_(NULL) {
105}
106
107MenuMessageLoopAura::~MenuMessageLoopAura() {
108}
109
110void MenuMessageLoopAura::RepostEventToWindow(const ui::LocatedEvent& event,
111                                              gfx::NativeWindow window,
112                                              const gfx::Point& screen_loc) {
113  aura::Window* root = window->GetRootWindow();
114  ScreenPositionClient* spc = aura::client::GetScreenPositionClient(root);
115  if (!spc)
116    return;
117
118  gfx::Point root_loc(screen_loc);
119  spc->ConvertPointFromScreen(root, &root_loc);
120
121  ui::MouseEvent clone(static_cast<const ui::MouseEvent&>(event));
122  clone.set_location(root_loc);
123  clone.set_root_location(root_loc);
124  root->GetHost()->dispatcher()->RepostEvent(clone);
125}
126
127void MenuMessageLoopAura::Run(MenuController* controller,
128                              Widget* owner,
129                              bool nested_menu) {
130  // |owner_| may be NULL.
131  owner_ = owner;
132  aura::Window* root = GetOwnerRootWindow(owner_);
133  // It is possible for the same MenuMessageLoopAura to start a nested
134  // message-loop while it is already running a nested loop. So make sure the
135  // quit-closure gets reset to the outer loop's quit-closure once the innermost
136  // loop terminates.
137  base::AutoReset<base::Closure> reset_quit_closure(&message_loop_quit_,
138                                                    base::Closure());
139
140#if defined(OS_WIN)
141  internal::MenuMessagePumpDispatcher nested_dispatcher(controller);
142  if (root) {
143    scoped_ptr<ActivationChangeObserverImpl> observer;
144    if (!nested_menu)
145      observer.reset(new ActivationChangeObserverImpl(controller, root));
146    aura::client::DispatcherRunLoop run_loop(
147        aura::client::GetDispatcherClient(root), &nested_dispatcher);
148    message_loop_quit_ = run_loop.QuitClosure();
149    run_loop.Run();
150  } else {
151    base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
152    base::MessageLoop::ScopedNestableTaskAllower allow(loop);
153    base::RunLoop run_loop(&nested_dispatcher);
154    message_loop_quit_ = run_loop.QuitClosure();
155    run_loop.Run();
156  }
157#else
158  internal::MenuEventDispatcher event_dispatcher(controller);
159  scoped_ptr<ui::ScopedEventDispatcher> old_dispatcher =
160      nested_dispatcher_.Pass();
161  if (ui::PlatformEventSource::GetInstance()) {
162    nested_dispatcher_ =
163        ui::PlatformEventSource::GetInstance()->OverrideDispatcher(
164            &event_dispatcher);
165  }
166  if (root) {
167    scoped_ptr<ActivationChangeObserverImpl> observer;
168    if (!nested_menu)
169      observer.reset(new ActivationChangeObserverImpl(controller, root));
170    aura::client::DispatcherRunLoop run_loop(
171        aura::client::GetDispatcherClient(root), NULL);
172    message_loop_quit_ = run_loop.QuitClosure();
173    run_loop.Run();
174  } else {
175    base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
176    base::MessageLoop::ScopedNestableTaskAllower allow(loop);
177    base::RunLoop run_loop;
178    message_loop_quit_ = run_loop.QuitClosure();
179    run_loop.Run();
180  }
181  nested_dispatcher_ = old_dispatcher.Pass();
182#endif
183}
184
185bool MenuMessageLoopAura::ShouldQuitNow() const {
186  aura::Window* root = GetOwnerRootWindow(owner_);
187  return !aura::client::GetDragDropClient(root) ||
188         !aura::client::GetDragDropClient(root)->IsDragDropInProgress();
189}
190
191void MenuMessageLoopAura::QuitNow() {
192  CHECK(!message_loop_quit_.is_null());
193  message_loop_quit_.Run();
194  // Restore the previous dispatcher.
195  nested_dispatcher_.reset();
196}
197
198void MenuMessageLoopAura::ClearOwner() {
199  owner_ = NULL;
200}
201
202}  // namespace views
203