// Copyright (c) 2012 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_host.h" #include "base/auto_reset.h" #include "base/logging.h" #include "base/trace_event/trace_event.h" #include "build/build_config.h" #include "ui/aura/window_observer.h" #include "ui/events/gestures/gesture_recognizer.h" #include "ui/gfx/path.h" #include "ui/native_theme/native_theme.h" #include "ui/views/controls/menu/menu_controller.h" #include "ui/views/controls/menu/menu_host_root_view.h" #include "ui/views/controls/menu/menu_item_view.h" #include "ui/views/controls/menu/menu_scroll_view_container.h" #include "ui/views/controls/menu/submenu_view.h" #include "ui/views/round_rect_painter.h" #include "ui/views/widget/native_widget_private.h" #include "ui/views/widget/widget.h" #if !defined(OS_MACOSX) #include "ui/aura/window.h" #endif namespace views { namespace internal { #if !defined(OS_MACOSX) // This class adds itself as the pre target handler for the |window| // passed in. It currently handles touch events and forwards them to the // controller. Reason for this approach is views does not get raw touch // events which we need to determine if a touch happened outside the bounds // of the menu. class PreMenuEventDispatchHandler : public ui::EventHandler, aura::WindowObserver { public: PreMenuEventDispatchHandler(const MenuController* controller, SubmenuView* submenu, aura::Window* window) : menu_controller_(const_cast(controller)), submenu_(submenu), window_(window) { window_->AddPreTargetHandler(this); window_->AddObserver(this); } ~PreMenuEventDispatchHandler() override { StopObserving(); } // ui::EventHandler overrides. void OnTouchEvent(ui::TouchEvent* event) override { menu_controller_->OnTouchEvent(submenu_, event); } // aura::WindowObserver overrides. void OnWindowDestroying(aura::Window* window) override { DCHECK(window_ == window); StopObserving(); } private: void StopObserving() { if (!window_) return; window_->RemovePreTargetHandler(this); window_->RemoveObserver(this); window_ = nullptr; } MenuController* menu_controller_; SubmenuView* submenu_; aura::Window* window_; DISALLOW_COPY_AND_ASSIGN(PreMenuEventDispatchHandler); }; #endif // OS_MACOSX } // namespace internal //////////////////////////////////////////////////////////////////////////////// // MenuHost, public: MenuHost::MenuHost(SubmenuView* submenu) : submenu_(submenu), destroying_(false), ignore_capture_lost_(false) { set_auto_release_capture(false); } MenuHost::~MenuHost() { } void MenuHost::InitMenuHost(Widget* parent, const gfx::Rect& bounds, View* contents_view, bool do_capture) { TRACE_EVENT0("views", "MenuHost::InitMenuHost"); Widget::InitParams params(Widget::InitParams::TYPE_MENU); const MenuController* menu_controller = submenu_->GetMenuItem()->GetMenuController(); const MenuConfig& menu_config = MenuConfig::instance(); bool rounded_border = menu_controller && menu_config.corner_radius > 0; bool bubble_border = submenu_->GetScrollViewContainer() && submenu_->GetScrollViewContainer()->HasBubbleBorder(); params.shadow_type = bubble_border ? Widget::InitParams::SHADOW_TYPE_NONE : Widget::InitParams::SHADOW_TYPE_DROP; params.opacity = (bubble_border || rounded_border) ? Widget::InitParams::TRANSLUCENT_WINDOW : Widget::InitParams::OPAQUE_WINDOW; params.parent = parent ? parent->GetNativeView() : NULL; params.bounds = bounds; #if defined(OS_WIN) // On Windows use the software compositor to ensure that we don't block // the UI thread blocking issue during command buffer creation. We can // revert this change once http://crbug.com/125248 is fixed. params.force_software_compositing = true; #endif Init(params); #if !defined(OS_MACOSX) pre_dispatch_handler_.reset(new internal::PreMenuEventDispatchHandler( menu_controller, submenu_, GetNativeView())); #endif SetContentsView(contents_view); ShowMenuHost(do_capture); } bool MenuHost::IsMenuHostVisible() { return IsVisible(); } void MenuHost::ShowMenuHost(bool do_capture) { // Doing a capture may make us get capture lost. Ignore it while we're in the // process of showing. base::AutoReset reseter(&ignore_capture_lost_, true); ShowInactive(); if (do_capture) { // Cancel existing touches, so we don't miss some touch release/cancel // events due to the menu taking capture. ui::GestureRecognizer::Get()->CancelActiveTouchesExcept(nullptr); native_widget_private()->SetCapture(); } } void MenuHost::HideMenuHost() { ignore_capture_lost_ = true; ReleaseMenuHostCapture(); Hide(); ignore_capture_lost_ = false; } void MenuHost::DestroyMenuHost() { HideMenuHost(); destroying_ = true; static_cast(GetRootView())->ClearSubmenu(); #if !defined(OS_MACOSX) pre_dispatch_handler_.reset(); #endif Close(); } void MenuHost::SetMenuHostBounds(const gfx::Rect& bounds) { SetBounds(bounds); } void MenuHost::ReleaseMenuHostCapture() { if (native_widget_private()->HasCapture()) native_widget_private()->ReleaseCapture(); } //////////////////////////////////////////////////////////////////////////////// // MenuHost, Widget overrides: internal::RootView* MenuHost::CreateRootView() { return new MenuHostRootView(this, submenu_); } void MenuHost::OnMouseCaptureLost() { if (destroying_ || ignore_capture_lost_) return; MenuController* menu_controller = submenu_->GetMenuItem()->GetMenuController(); if (menu_controller && !menu_controller->drag_in_progress()) menu_controller->CancelAll(); Widget::OnMouseCaptureLost(); } void MenuHost::OnNativeWidgetDestroyed() { if (!destroying_) { // We weren't explicitly told to destroy ourselves, which means the menu was // deleted out from under us (the window we're parented to was closed). Tell // the SubmenuView to drop references to us. submenu_->MenuHostDestroyed(); } Widget::OnNativeWidgetDestroyed(); } void MenuHost::OnOwnerClosing() { if (destroying_) return; MenuController* menu_controller = submenu_->GetMenuItem()->GetMenuController(); if (menu_controller && !menu_controller->drag_in_progress()) menu_controller->CancelAll(); } void MenuHost::OnDragWillStart() { MenuController* menu_controller = submenu_->GetMenuItem()->GetMenuController(); DCHECK(menu_controller); menu_controller->OnDragWillStart(); } void MenuHost::OnDragComplete() { MenuController* menu_controller = submenu_->GetMenuItem()->GetMenuController(); if (destroying_ || !menu_controller) return; bool should_close = true; // If the view came from outside menu code (i.e., not a MenuItemView), we // should consult the MenuDelegate to determine whether or not to close on // exit. if (!menu_controller->did_initiate_drag()) { MenuDelegate* menu_delegate = submenu_->GetMenuItem()->GetDelegate(); should_close = menu_delegate ? menu_delegate->ShouldCloseOnDragComplete() : should_close; } menu_controller->OnDragComplete(should_close); // We may have lost capture in the drag and drop, but are remaining open. // Return capture so we get MouseCaptureLost events. if (!should_close) native_widget_private()->SetCapture(); } } // namespace views