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_host.h"
6
7#include "base/auto_reset.h"
8#include "base/debug/trace_event.h"
9#include "ui/events/gestures/gesture_recognizer.h"
10#include "ui/gfx/path.h"
11#include "ui/native_theme/native_theme.h"
12#include "ui/views/controls/menu/menu_controller.h"
13#include "ui/views/controls/menu/menu_host_root_view.h"
14#include "ui/views/controls/menu/menu_item_view.h"
15#include "ui/views/controls/menu/menu_scroll_view_container.h"
16#include "ui/views/controls/menu/submenu_view.h"
17#include "ui/views/round_rect_painter.h"
18#include "ui/views/widget/native_widget_private.h"
19#include "ui/views/widget/widget.h"
20
21namespace views {
22
23////////////////////////////////////////////////////////////////////////////////
24// MenuHost, public:
25
26MenuHost::MenuHost(SubmenuView* submenu)
27    : submenu_(submenu),
28      destroying_(false),
29      ignore_capture_lost_(false) {
30  set_auto_release_capture(false);
31}
32
33MenuHost::~MenuHost() {
34}
35
36void MenuHost::InitMenuHost(Widget* parent,
37                            const gfx::Rect& bounds,
38                            View* contents_view,
39                            bool do_capture) {
40  TRACE_EVENT0("views", "MenuHost::InitMenuHost");
41  Widget::InitParams params(Widget::InitParams::TYPE_MENU);
42  const MenuController* menu_controller =
43      submenu_->GetMenuItem()->GetMenuController();
44  const MenuConfig& menu_config = submenu_->GetMenuItem()->GetMenuConfig();
45  bool rounded_border = menu_controller && menu_config.corner_radius > 0;
46  bool bubble_border = submenu_->GetScrollViewContainer() &&
47                       submenu_->GetScrollViewContainer()->HasBubbleBorder();
48  params.shadow_type = bubble_border ? Widget::InitParams::SHADOW_TYPE_NONE
49                                     : Widget::InitParams::SHADOW_TYPE_DROP;
50  params.opacity = (bubble_border || rounded_border) ?
51      Widget::InitParams::TRANSLUCENT_WINDOW :
52      Widget::InitParams::OPAQUE_WINDOW;
53  params.parent = parent ? parent->GetNativeView() : NULL;
54  params.bounds = bounds;
55  Init(params);
56
57  SetContentsView(contents_view);
58  ShowMenuHost(do_capture);
59}
60
61bool MenuHost::IsMenuHostVisible() {
62  return IsVisible();
63}
64
65void MenuHost::ShowMenuHost(bool do_capture) {
66  // Doing a capture may make us get capture lost. Ignore it while we're in the
67  // process of showing.
68  base::AutoReset<bool> reseter(&ignore_capture_lost_, true);
69  ShowInactive();
70  if (do_capture) {
71    // Cancel existing touches, so we don't miss some touch release/cancel
72    // events due to the menu taking capture.
73    ui::GestureRecognizer::Get()->TransferEventsTo(NULL, NULL);
74    native_widget_private()->SetCapture();
75  }
76}
77
78void MenuHost::HideMenuHost() {
79  ignore_capture_lost_ = true;
80  ReleaseMenuHostCapture();
81  Hide();
82  ignore_capture_lost_ = false;
83}
84
85void MenuHost::DestroyMenuHost() {
86  HideMenuHost();
87  destroying_ = true;
88  static_cast<MenuHostRootView*>(GetRootView())->ClearSubmenu();
89  Close();
90}
91
92void MenuHost::SetMenuHostBounds(const gfx::Rect& bounds) {
93  SetBounds(bounds);
94}
95
96void MenuHost::ReleaseMenuHostCapture() {
97  if (native_widget_private()->HasCapture())
98    native_widget_private()->ReleaseCapture();
99}
100
101////////////////////////////////////////////////////////////////////////////////
102// MenuHost, Widget overrides:
103
104internal::RootView* MenuHost::CreateRootView() {
105  return new MenuHostRootView(this, submenu_);
106}
107
108void MenuHost::OnMouseCaptureLost() {
109  if (destroying_ || ignore_capture_lost_)
110    return;
111  MenuController* menu_controller =
112      submenu_->GetMenuItem()->GetMenuController();
113  if (menu_controller && !menu_controller->drag_in_progress())
114    menu_controller->CancelAll();
115  Widget::OnMouseCaptureLost();
116}
117
118void MenuHost::OnNativeWidgetDestroyed() {
119  if (!destroying_) {
120    // We weren't explicitly told to destroy ourselves, which means the menu was
121    // deleted out from under us (the window we're parented to was closed). Tell
122    // the SubmenuView to drop references to us.
123    submenu_->MenuHostDestroyed();
124  }
125  Widget::OnNativeWidgetDestroyed();
126}
127
128void MenuHost::OnOwnerClosing() {
129  if (destroying_)
130    return;
131
132  MenuController* menu_controller =
133      submenu_->GetMenuItem()->GetMenuController();
134  if (menu_controller && !menu_controller->drag_in_progress())
135    menu_controller->CancelAll();
136}
137
138void MenuHost::OnDragWillStart() {
139  MenuController* menu_controller =
140      submenu_->GetMenuItem()->GetMenuController();
141  DCHECK(menu_controller);
142  menu_controller->OnDragWillStart();
143}
144
145void MenuHost::OnDragComplete() {
146  MenuController* menu_controller =
147      submenu_->GetMenuItem()->GetMenuController();
148  if (destroying_ || !menu_controller)
149    return;
150
151  bool should_close = true;
152  // If the view came from outside menu code (i.e., not a MenuItemView), we
153  // should consult the MenuDelegate to determine whether or not to close on
154  // exit.
155  if (!menu_controller->did_initiate_drag()) {
156    MenuDelegate* menu_delegate = submenu_->GetMenuItem()->GetDelegate();
157    should_close =
158      menu_delegate ? menu_delegate->ShouldCloseOnDragComplete() : should_close;
159  }
160  menu_controller->OnDragComplete(should_close);
161
162  // We may have lost capture in the drag and drop, but are remaining open.
163  // Return capture so we get MouseCaptureLost events.
164  if (!should_close)
165    native_widget_private()->SetCapture();
166}
167
168}  // namespace views
169