blob: 01822393894954441ad0a48228e9293217f972a4 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/controls/menu/menu_message_loop_aura.h"
#include "base/run_loop.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/event.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/events/platform/scoped_event_dispatcher.h"
#include "ui/views/controls/menu/menu_controller.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/public/activation_change_observer.h"
#include "ui/wm/public/activation_client.h"
#include "ui/wm/public/dispatcher_client.h"
#include "ui/wm/public/drag_drop_client.h"
#if defined(OS_WIN)
#include "ui/base/win/internal_constants.h"
#include "ui/views/controls/menu/menu_message_pump_dispatcher_win.h"
#include "ui/views/win/hwnd_util.h"
#else
#include "ui/views/controls/menu/menu_key_event_handler.h"
#endif
using aura::client::ScreenPositionClient;
namespace views {
namespace {
aura::Window* GetOwnerRootWindow(views::Widget* owner) {
return owner ? owner->GetNativeWindow()->GetRootWindow() : NULL;
}
// ActivationChangeObserverImpl is used to observe activation changes and close
// the menu. Additionally it listens for the root window to be destroyed and
// cancel the menu as well.
class ActivationChangeObserverImpl
: public aura::client::ActivationChangeObserver,
public aura::WindowObserver,
public ui::EventHandler {
public:
ActivationChangeObserverImpl(MenuController* controller, aura::Window* root)
: controller_(controller), root_(root) {
aura::client::GetActivationClient(root_)->AddObserver(this);
root_->AddObserver(this);
root_->AddPreTargetHandler(this);
}
~ActivationChangeObserverImpl() override { Cleanup(); }
// aura::client::ActivationChangeObserver:
void OnWindowActivated(
aura::client::ActivationChangeObserver::ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) override {
if (!controller_->drag_in_progress())
controller_->CancelAll();
}
// aura::WindowObserver:
void OnWindowDestroying(aura::Window* window) override { Cleanup(); }
// ui::EventHandler:
void OnCancelMode(ui::CancelModeEvent* event) override {
controller_->CancelAll();
}
private:
void Cleanup() {
if (!root_)
return;
// The ActivationClient may have been destroyed by the time we get here.
aura::client::ActivationClient* client =
aura::client::GetActivationClient(root_);
if (client)
client->RemoveObserver(this);
root_->RemovePreTargetHandler(this);
root_->RemoveObserver(this);
root_ = NULL;
}
MenuController* controller_;
aura::Window* root_;
DISALLOW_COPY_AND_ASSIGN(ActivationChangeObserverImpl);
};
} // namespace
// static
MenuMessageLoop* MenuMessageLoop::Create() {
return new MenuMessageLoopAura;
}
// static
void MenuMessageLoop::RepostEventToWindow(const ui::LocatedEvent& event,
gfx::NativeWindow window,
const gfx::Point& screen_loc) {
aura::Window* root = window->GetRootWindow();
ScreenPositionClient* spc = aura::client::GetScreenPositionClient(root);
if (!spc)
return;
gfx::Point root_loc(screen_loc);
spc->ConvertPointFromScreen(root, &root_loc);
ui::MouseEvent clone(static_cast<const ui::MouseEvent&>(event));
clone.set_location(root_loc);
clone.set_root_location(root_loc);
root->GetHost()->dispatcher()->RepostEvent(clone);
}
MenuMessageLoopAura::MenuMessageLoopAura() : owner_(nullptr) {}
MenuMessageLoopAura::~MenuMessageLoopAura() {}
void MenuMessageLoopAura::Run(MenuController* controller,
Widget* owner,
bool nested_menu) {
// |owner_| may be NULL.
owner_ = owner;
aura::Window* root = GetOwnerRootWindow(owner_);
// It is possible for the same MenuMessageLoopAura to start a nested
// message-loop while it is already running a nested loop. So make sure the
// quit-closure gets reset to the outer loop's quit-closure once the innermost
// loop terminates.
base::AutoReset<base::Closure> reset_quit_closure(&message_loop_quit_,
base::Closure());
#if defined(OS_WIN)
internal::MenuMessagePumpDispatcher nested_dispatcher(controller);
if (root) {
scoped_ptr<ActivationChangeObserverImpl> observer;
if (!nested_menu)
observer.reset(new ActivationChangeObserverImpl(controller, root));
aura::client::DispatcherRunLoop run_loop(
aura::client::GetDispatcherClient(root), &nested_dispatcher);
message_loop_quit_ = run_loop.QuitClosure();
run_loop.Run();
} else {
base::MessageLoop* loop = base::MessageLoop::current();
base::MessageLoop::ScopedNestableTaskAllower allow(loop);
base::RunLoop run_loop(&nested_dispatcher);
message_loop_quit_ = run_loop.QuitClosure();
run_loop.Run();
}
#else
scoped_ptr<ActivationChangeObserverImpl> observer;
if (root) {
if (!nested_menu)
observer.reset(new ActivationChangeObserverImpl(controller, root));
}
scoped_ptr<MenuKeyEventHandler> menu_event_filter;
if (!nested_menu) {
// If this is a nested menu, then the MenuKeyEventHandler would have been
// created already in the top parent menu. So no need to recreate it here.
menu_event_filter.reset(new MenuKeyEventHandler);
}
base::MessageLoop* loop = base::MessageLoop::current();
base::MessageLoop::ScopedNestableTaskAllower allow(loop);
base::RunLoop run_loop;
message_loop_quit_ = run_loop.QuitClosure();
run_loop.Run();
#endif // defined(OS_WIN)
}
void MenuMessageLoopAura::QuitNow() {
CHECK(!message_loop_quit_.is_null());
message_loop_quit_.Run();
#if !defined(OS_WIN)
// Ask PlatformEventSource to stop dispatching events in this message loop
// iteration. We want our menu's loop to return before the next event.
if (ui::PlatformEventSource::GetInstance())
ui::PlatformEventSource::GetInstance()->StopCurrentEventStream();
#endif
}
void MenuMessageLoopAura::ClearOwner() {
owner_ = NULL;
}
} // namespace views