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/bubble/bubble_delegate.h"
6
7#include "ui/base/accessibility/accessible_view_state.h"
8#include "ui/gfx/animation/slide_animation.h"
9#include "ui/gfx/color_utils.h"
10#include "ui/gfx/rect.h"
11#include "ui/native_theme/native_theme.h"
12#include "ui/views/bubble/bubble_frame_view.h"
13#include "ui/views/focus/view_storage.h"
14#include "ui/views/widget/widget.h"
15#include "ui/views/widget/widget_observer.h"
16
17#if defined(OS_WIN)
18#include "ui/base/win/shell.h"
19#endif
20
21// The duration of the fade animation in milliseconds.
22static const int kHideFadeDurationMS = 200;
23
24// The defaut margin between the content and the inside border, in pixels.
25static const int kDefaultMargin = 6;
26
27namespace views {
28
29namespace {
30
31// Create a widget to host the bubble.
32Widget* CreateBubbleWidget(BubbleDelegateView* bubble) {
33  Widget* bubble_widget = new Widget();
34  Widget::InitParams bubble_params(Widget::InitParams::TYPE_BUBBLE);
35  bubble_params.delegate = bubble;
36  bubble_params.opacity = Widget::InitParams::TRANSLUCENT_WINDOW;
37  bubble_params.accept_events = bubble->accept_events();
38  if (bubble->parent_window())
39    bubble_params.parent = bubble->parent_window();
40  else if (bubble->anchor_widget())
41    bubble_params.parent = bubble->anchor_widget()->GetNativeView();
42  bubble_params.can_activate = bubble->CanActivate();
43  bubble->OnBeforeBubbleWidgetInit(&bubble_params, bubble_widget);
44  bubble_widget->Init(bubble_params);
45  return bubble_widget;
46}
47
48}  // namespace
49
50BubbleDelegateView::BubbleDelegateView()
51    : close_on_esc_(true),
52      close_on_deactivate_(true),
53      anchor_view_storage_id_(ViewStorage::GetInstance()->CreateStorageID()),
54      anchor_widget_(NULL),
55      move_with_anchor_(false),
56      arrow_(BubbleBorder::TOP_LEFT),
57      shadow_(BubbleBorder::SMALL_SHADOW),
58      color_explicitly_set_(false),
59      margins_(kDefaultMargin, kDefaultMargin, kDefaultMargin, kDefaultMargin),
60      original_opacity_(255),
61      use_focusless_(false),
62      accept_events_(true),
63      border_accepts_events_(true),
64      adjust_if_offscreen_(true),
65      parent_window_(NULL) {
66  AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
67  UpdateColorsFromTheme(GetNativeTheme());
68}
69
70BubbleDelegateView::BubbleDelegateView(
71    View* anchor_view,
72    BubbleBorder::Arrow arrow)
73    : close_on_esc_(true),
74      close_on_deactivate_(true),
75      anchor_view_storage_id_(ViewStorage::GetInstance()->CreateStorageID()),
76      anchor_widget_(NULL),
77      move_with_anchor_(false),
78      arrow_(arrow),
79      shadow_(BubbleBorder::SMALL_SHADOW),
80      color_explicitly_set_(false),
81      margins_(kDefaultMargin, kDefaultMargin, kDefaultMargin, kDefaultMargin),
82      original_opacity_(255),
83      use_focusless_(false),
84      accept_events_(true),
85      border_accepts_events_(true),
86      adjust_if_offscreen_(true),
87      parent_window_(NULL) {
88  SetAnchorView(anchor_view);
89  AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
90  UpdateColorsFromTheme(GetNativeTheme());
91}
92
93BubbleDelegateView::~BubbleDelegateView() {
94  SetLayoutManager(NULL);
95  SetAnchorView(NULL);
96}
97
98// static
99Widget* BubbleDelegateView::CreateBubble(BubbleDelegateView* bubble_delegate) {
100  bubble_delegate->Init();
101  // Get the latest anchor widget from the anchor view at bubble creation time.
102  bubble_delegate->SetAnchorView(bubble_delegate->GetAnchorView());
103  Widget* bubble_widget = CreateBubbleWidget(bubble_delegate);
104
105#if defined(OS_WIN) && defined(USE_AURA)
106  // If glass is enabled, the bubble is allowed to extend outside the bounds of
107  // the parent frame and let DWM handle compositing.  If not, then we don't
108  // want to allow the bubble to extend the frame because it will be clipped.
109  bubble_delegate->set_adjust_if_offscreen(ui::win::IsAeroGlassEnabled());
110#endif
111
112  bubble_delegate->SizeToContents();
113  bubble_widget->AddObserver(bubble_delegate);
114  return bubble_widget;
115}
116
117BubbleDelegateView* BubbleDelegateView::AsBubbleDelegate() {
118  return this;
119}
120
121bool BubbleDelegateView::CanActivate() const {
122  return !use_focusless();
123}
124
125bool BubbleDelegateView::ShouldShowCloseButton() const {
126  return false;
127}
128
129View* BubbleDelegateView::GetContentsView() {
130  return this;
131}
132
133NonClientFrameView* BubbleDelegateView::CreateNonClientFrameView(
134    Widget* widget) {
135  BubbleFrameView* frame = new BubbleFrameView(margins());
136  BubbleBorder::Arrow adjusted_arrow = arrow();
137  if (base::i18n::IsRTL())
138    adjusted_arrow = BubbleBorder::horizontal_mirror(adjusted_arrow);
139  frame->SetBubbleBorder(new BubbleBorder(adjusted_arrow, shadow(), color()));
140  return frame;
141}
142
143void BubbleDelegateView::GetAccessibleState(ui::AccessibleViewState* state) {
144  state->role = ui::AccessibilityTypes::ROLE_DIALOG;
145}
146
147void BubbleDelegateView::OnWidgetDestroying(Widget* widget) {
148  if (anchor_widget() == widget)
149    SetAnchorView(NULL);
150}
151
152void BubbleDelegateView::OnWidgetVisibilityChanging(Widget* widget,
153                                                    bool visible) {
154#if defined(OS_WIN)
155  // On Windows we need to handle this before the bubble is visible or hidden.
156  // Please see the comment on the OnWidgetVisibilityChanging function. On
157  // other platforms it is fine to handle it after the bubble is shown/hidden.
158  HandleVisibilityChanged(widget, visible);
159#endif
160}
161
162void BubbleDelegateView::OnWidgetVisibilityChanged(Widget* widget,
163                                                   bool visible) {
164#if !defined(OS_WIN)
165  HandleVisibilityChanged(widget, visible);
166#endif
167}
168
169void BubbleDelegateView::OnWidgetActivationChanged(Widget* widget,
170                                                   bool active) {
171  if (close_on_deactivate() && widget == GetWidget() && !active)
172    GetWidget()->Close();
173}
174
175void BubbleDelegateView::OnWidgetBoundsChanged(Widget* widget,
176                                               const gfx::Rect& new_bounds) {
177  if (anchor_widget() == widget) {
178    if (move_with_anchor())
179      SizeToContents();
180    else
181      GetWidget()->Close();
182  }
183}
184
185View* BubbleDelegateView::GetAnchorView() const {
186  return ViewStorage::GetInstance()->RetrieveView(anchor_view_storage_id_);
187}
188
189gfx::Rect BubbleDelegateView::GetAnchorRect() {
190  if (!GetAnchorView())
191    return anchor_rect_;
192
193  anchor_rect_ = GetAnchorView()->GetBoundsInScreen();
194  anchor_rect_.Inset(anchor_view_insets_);
195  return anchor_rect_;
196}
197
198void BubbleDelegateView::OnBeforeBubbleWidgetInit(Widget::InitParams* params,
199                                                  Widget* widget) const {
200}
201
202void BubbleDelegateView::StartFade(bool fade_in) {
203#if defined(USE_AURA)
204  // Use AURA's window layer animation instead of fading. This ensures that
205  // hosts which rely on the layer animation callbacks to close the window
206  // work correctly.
207  if (fade_in)
208    GetWidget()->Show();
209  else
210    GetWidget()->Close();
211#else
212  fade_animation_.reset(new gfx::SlideAnimation(this));
213  fade_animation_->SetSlideDuration(GetFadeDuration());
214  fade_animation_->Reset(fade_in ? 0.0 : 1.0);
215  if (fade_in) {
216    original_opacity_ = 0;
217    GetWidget()->SetOpacity(original_opacity_);
218    GetWidget()->Show();
219    fade_animation_->Show();
220  } else {
221    original_opacity_ = 255;
222    fade_animation_->Hide();
223  }
224#endif
225}
226
227void BubbleDelegateView::ResetFade() {
228  fade_animation_.reset();
229  GetWidget()->SetOpacity(original_opacity_);
230}
231
232void BubbleDelegateView::SetAlignment(BubbleBorder::BubbleAlignment alignment) {
233  GetBubbleFrameView()->bubble_border()->set_alignment(alignment);
234  SizeToContents();
235}
236
237void BubbleDelegateView::SetArrowPaintType(
238    BubbleBorder::ArrowPaintType paint_type) {
239  GetBubbleFrameView()->bubble_border()->set_paint_arrow(paint_type);
240  SizeToContents();
241}
242
243void BubbleDelegateView::OnAnchorBoundsChanged() {
244  SizeToContents();
245}
246
247bool BubbleDelegateView::AcceleratorPressed(
248    const ui::Accelerator& accelerator) {
249  if (!close_on_esc() || accelerator.key_code() != ui::VKEY_ESCAPE)
250    return false;
251  if (fade_animation_.get())
252    fade_animation_->Reset();
253  GetWidget()->Close();
254  return true;
255}
256
257void BubbleDelegateView::OnNativeThemeChanged(const ui::NativeTheme* theme) {
258  UpdateColorsFromTheme(theme);
259}
260
261void BubbleDelegateView::AnimationEnded(const gfx::Animation* animation) {
262  if (animation != fade_animation_.get())
263    return;
264  bool closed = fade_animation_->GetCurrentValue() == 0;
265  fade_animation_->Reset();
266  if (closed)
267    GetWidget()->Close();
268}
269
270void BubbleDelegateView::AnimationProgressed(const gfx::Animation* animation) {
271  if (animation != fade_animation_.get())
272    return;
273  DCHECK(fade_animation_->is_animating());
274  GetWidget()->SetOpacity(fade_animation_->GetCurrentValue() * 255);
275}
276
277void BubbleDelegateView::Init() {}
278
279void BubbleDelegateView::SetAnchorView(View* anchor_view) {
280  // When the anchor view gets set the associated anchor widget might
281  // change as well.
282  if (!anchor_view || anchor_widget() != anchor_view->GetWidget()) {
283    if (anchor_widget()) {
284      anchor_widget_->RemoveObserver(this);
285      anchor_widget_ = NULL;
286    }
287    if (anchor_view) {
288      anchor_widget_ = anchor_view->GetWidget();
289      if (anchor_widget_)
290        anchor_widget_->AddObserver(this);
291    }
292  }
293
294  // Remove the old storage item and set the new (if there is one).
295  ViewStorage* view_storage = ViewStorage::GetInstance();
296  if (view_storage->RetrieveView(anchor_view_storage_id_))
297    view_storage->RemoveView(anchor_view_storage_id_);
298  if (anchor_view)
299    view_storage->StoreView(anchor_view_storage_id_, anchor_view);
300
301  if (GetWidget())
302    OnAnchorBoundsChanged();
303}
304
305void BubbleDelegateView::SetAnchorRect(const gfx::Rect& rect) {
306  anchor_rect_ = rect;
307  if (GetWidget())
308    OnAnchorBoundsChanged();
309}
310
311void BubbleDelegateView::SizeToContents() {
312  GetWidget()->SetBounds(GetBubbleBounds());
313}
314
315BubbleFrameView* BubbleDelegateView::GetBubbleFrameView() const {
316  const NonClientView* view =
317      GetWidget() ? GetWidget()->non_client_view() : NULL;
318  return view ? static_cast<BubbleFrameView*>(view->frame_view()) : NULL;
319}
320
321gfx::Rect BubbleDelegateView::GetBubbleBounds() {
322  // The argument rect has its origin at the bubble's arrow anchor point;
323  // its size is the preferred size of the bubble's client view (this view).
324  return GetBubbleFrameView()->GetUpdatedWindowBounds(GetAnchorRect(),
325      GetPreferredSize(), adjust_if_offscreen_);
326}
327
328int BubbleDelegateView::GetFadeDuration() {
329  return kHideFadeDurationMS;
330}
331
332void BubbleDelegateView::UpdateColorsFromTheme(const ui::NativeTheme* theme) {
333  if (!color_explicitly_set_) {
334    color_ = GetNativeTheme()->GetSystemColor(
335        ui::NativeTheme::kColorId_WindowBackground);
336  }
337  set_background(Background::CreateSolidBackground(color()));
338  BubbleFrameView* frame_view = GetBubbleFrameView();
339  if (frame_view)
340    frame_view->bubble_border()->set_background_color(color());
341}
342
343void BubbleDelegateView::HandleVisibilityChanged(Widget* widget, bool visible) {
344  if (widget == GetWidget() && visible && anchor_widget() &&
345      anchor_widget()->GetTopLevelWidget()) {
346    anchor_widget()->GetTopLevelWidget()->DisableInactiveRendering();
347  }
348}
349
350}  // namespace views
351