bubble_delegate.cc revision 3240926e260ce088908e02ac07a6cf7b0c0cbf44
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/animation/slide_animation.h"
8#include "ui/gfx/color_utils.h"
9#include "ui/gfx/rect.h"
10#include "ui/native_theme/native_theme.h"
11#include "ui/views/bubble/bubble_frame_view.h"
12#include "ui/views/widget/widget.h"
13#include "ui/views/widget/widget_observer.h"
14
15#if defined(OS_WIN)
16#include "ui/base/win/shell.h"
17#endif
18
19// The duration of the fade animation in milliseconds.
20static const int kHideFadeDurationMS = 200;
21
22// The defaut margin between the content and the inside border, in pixels.
23static const int kDefaultMargin = 6;
24
25namespace views {
26
27namespace {
28
29// Create a widget to host the bubble.
30Widget* CreateBubbleWidget(BubbleDelegateView* bubble) {
31  Widget* bubble_widget = new Widget();
32  Widget::InitParams bubble_params(Widget::InitParams::TYPE_BUBBLE);
33  bubble_params.delegate = bubble;
34  bubble_params.opacity = Widget::InitParams::TRANSLUCENT_WINDOW;
35  bubble_params.accept_events = bubble->accept_events();
36  if (bubble->parent_window())
37    bubble_params.parent = bubble->parent_window();
38  else if (bubble->anchor_widget())
39    bubble_params.parent = bubble->anchor_widget()->GetNativeView();
40  bubble_params.can_activate = bubble->CanActivate();
41#if defined(OS_WIN) && !defined(USE_AURA)
42  bubble_params.type = Widget::InitParams::TYPE_WINDOW_FRAMELESS;
43  bubble_params.opacity = Widget::InitParams::OPAQUE_WINDOW;
44#endif
45  bubble_widget->Init(bubble_params);
46  return bubble_widget;
47}
48
49#if defined(OS_WIN) && !defined(USE_AURA)
50// Windows uses two widgets and some extra complexity to host partially
51// transparent native controls and use per-pixel HWND alpha on the border.
52// TODO(msw): Clean these up when Windows native controls are no longer needed.
53class BubbleBorderDelegate : public WidgetDelegate,
54                             public WidgetObserver {
55 public:
56  BubbleBorderDelegate(BubbleDelegateView* bubble, Widget* widget)
57      : bubble_(bubble),
58        widget_(widget) {
59    bubble_->GetWidget()->AddObserver(this);
60  }
61
62  virtual ~BubbleBorderDelegate() {
63    if (bubble_ && bubble_->GetWidget())
64      bubble_->GetWidget()->RemoveObserver(this);
65  }
66
67  // WidgetDelegate overrides:
68  virtual bool CanActivate() const OVERRIDE { return false; }
69  virtual string16 GetWindowTitle() const OVERRIDE {
70    return bubble_->GetWindowTitle();
71  }
72  virtual bool ShouldShowCloseButton() const OVERRIDE {
73    return bubble_->ShouldShowCloseButton();
74  }
75  virtual void DeleteDelegate() OVERRIDE { delete this; }
76  virtual Widget* GetWidget() OVERRIDE { return widget_; }
77  virtual const Widget* GetWidget() const OVERRIDE { return widget_; }
78  virtual NonClientFrameView* CreateNonClientFrameView(
79      Widget* widget) OVERRIDE {
80    return bubble_->CreateNonClientFrameView(widget);
81  }
82
83  // WidgetObserver overrides:
84  virtual void OnWidgetDestroying(Widget* widget) OVERRIDE {
85    bubble_ = NULL;
86    widget_->Close();
87  }
88
89 private:
90  BubbleDelegateView* bubble_;
91  Widget* widget_;
92
93  DISALLOW_COPY_AND_ASSIGN(BubbleBorderDelegate);
94};
95
96// Create a widget to host the bubble's border.
97Widget* CreateBorderWidget(BubbleDelegateView* bubble) {
98  Widget* border_widget = new Widget();
99  Widget::InitParams border_params(Widget::InitParams::TYPE_BUBBLE);
100  border_params.delegate = new BubbleBorderDelegate(bubble, border_widget);
101  border_params.opacity = Widget::InitParams::TRANSLUCENT_WINDOW;
102  border_params.parent = bubble->GetWidget()->GetNativeView();
103  border_params.can_activate = false;
104  border_params.accept_events = bubble->border_accepts_events();
105  border_widget->Init(border_params);
106  border_widget->set_focus_on_creation(false);
107  return border_widget;
108}
109#endif
110
111}  // namespace
112
113BubbleDelegateView::BubbleDelegateView()
114    : close_on_esc_(true),
115      close_on_deactivate_(true),
116      anchor_view_(NULL),
117      anchor_widget_(NULL),
118      move_with_anchor_(false),
119      arrow_(BubbleBorder::TOP_LEFT),
120      shadow_(BubbleBorder::SMALL_SHADOW),
121      color_explicitly_set_(false),
122      margins_(kDefaultMargin, kDefaultMargin, kDefaultMargin, kDefaultMargin),
123      original_opacity_(255),
124      border_widget_(NULL),
125      use_focusless_(false),
126      accept_events_(true),
127      border_accepts_events_(true),
128      adjust_if_offscreen_(true),
129      parent_window_(NULL) {
130  AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
131  UpdateColorsFromTheme(GetNativeTheme());
132}
133
134BubbleDelegateView::BubbleDelegateView(
135    View* anchor_view,
136    BubbleBorder::Arrow arrow)
137    : close_on_esc_(true),
138      close_on_deactivate_(true),
139      anchor_view_(anchor_view),
140      anchor_widget_(NULL),
141      move_with_anchor_(false),
142      arrow_(arrow),
143      shadow_(BubbleBorder::SMALL_SHADOW),
144      color_explicitly_set_(false),
145      margins_(kDefaultMargin, kDefaultMargin, kDefaultMargin, kDefaultMargin),
146      original_opacity_(255),
147      border_widget_(NULL),
148      use_focusless_(false),
149      accept_events_(true),
150      border_accepts_events_(true),
151      adjust_if_offscreen_(true),
152      parent_window_(NULL) {
153  AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
154  UpdateColorsFromTheme(GetNativeTheme());
155}
156
157BubbleDelegateView::~BubbleDelegateView() {
158  if (anchor_widget() != NULL)
159    anchor_widget()->RemoveObserver(this);
160  anchor_widget_ = NULL;
161  anchor_view_ = NULL;
162}
163
164// static
165Widget* BubbleDelegateView::CreateBubble(BubbleDelegateView* bubble_delegate) {
166  bubble_delegate->Init();
167  // Determine the anchor widget from the anchor view at bubble creation time.
168  bubble_delegate->anchor_widget_ = bubble_delegate->anchor_view() ?
169      bubble_delegate->anchor_view()->GetWidget() : NULL;
170  if (bubble_delegate->anchor_widget())
171    bubble_delegate->anchor_widget()->AddObserver(bubble_delegate);
172
173  Widget* bubble_widget = CreateBubbleWidget(bubble_delegate);
174
175#if defined(OS_WIN)
176#if defined(USE_AURA)
177  // If glass is enabled, the bubble is allowed to extend outside the bounds of
178  // the parent frame and let DWM handle compositing.  If not, then we don't
179  // want to allow the bubble to extend the frame because it will be clipped.
180  bubble_delegate->set_adjust_if_offscreen(ui::win::IsAeroGlassEnabled());
181#else
182  // First set the contents view to initialize view bounds for widget sizing.
183  bubble_widget->SetContentsView(bubble_delegate->GetContentsView());
184  bubble_delegate->border_widget_ = CreateBorderWidget(bubble_delegate);
185#endif
186#endif
187
188  bubble_delegate->SizeToContents();
189  bubble_widget->AddObserver(bubble_delegate);
190  return bubble_widget;
191}
192
193BubbleDelegateView* BubbleDelegateView::AsBubbleDelegate() {
194  return this;
195}
196
197bool BubbleDelegateView::CanActivate() const {
198  return !use_focusless();
199}
200
201bool BubbleDelegateView::ShouldShowCloseButton() const {
202  return false;
203}
204
205View* BubbleDelegateView::GetContentsView() {
206  return this;
207}
208
209NonClientFrameView* BubbleDelegateView::CreateNonClientFrameView(
210    Widget* widget) {
211  BubbleFrameView* frame = new BubbleFrameView(margins());
212  const BubbleBorder::Arrow adjusted_arrow = base::i18n::IsRTL() ?
213      BubbleBorder::horizontal_mirror(arrow()) : arrow();
214  frame->SetBubbleBorder(new BubbleBorder(adjusted_arrow, shadow(), color()));
215  return frame;
216}
217
218void BubbleDelegateView::OnWidgetDestroying(Widget* widget) {
219  if (anchor_widget() == widget) {
220    anchor_widget_->RemoveObserver(this);
221    anchor_view_ = NULL;
222    anchor_widget_ = NULL;
223  }
224}
225
226void BubbleDelegateView::OnWidgetVisibilityChanged(Widget* widget,
227                                                   bool visible) {
228  if (widget != GetWidget())
229    return;
230
231  if (visible) {
232    if (border_widget_)
233      border_widget_->ShowInactive();
234    if (anchor_widget() && anchor_widget()->GetTopLevelWidget())
235      anchor_widget()->GetTopLevelWidget()->DisableInactiveRendering();
236  } else {
237    if (border_widget_)
238      border_widget_->Hide();
239  }
240}
241
242void BubbleDelegateView::OnWidgetActivationChanged(Widget* widget,
243                                                   bool active) {
244  if (close_on_deactivate() && widget == GetWidget() && !active)
245    GetWidget()->Close();
246}
247
248void BubbleDelegateView::OnWidgetBoundsChanged(Widget* widget,
249                                               const gfx::Rect& new_bounds) {
250  if (move_with_anchor() && anchor_widget() == widget)
251    SizeToContents();
252}
253
254gfx::Rect BubbleDelegateView::GetAnchorRect() {
255  if (!anchor_view())
256    return anchor_rect_;
257  gfx::Rect anchor_bounds = anchor_view()->GetBoundsInScreen();
258  anchor_bounds.Inset(anchor_view_insets_);
259  return anchor_bounds;
260}
261
262void BubbleDelegateView::StartFade(bool fade_in) {
263#if defined(USE_AURA)
264  // Use AURA's window layer animation instead of fading. This ensures that
265  // hosts which rely on the layer animation callbacks to close the window
266  // work correctly.
267  if (fade_in)
268    GetWidget()->Show();
269  else
270    GetWidget()->Close();
271#else
272  fade_animation_.reset(new ui::SlideAnimation(this));
273  fade_animation_->SetSlideDuration(GetFadeDuration());
274  fade_animation_->Reset(fade_in ? 0.0 : 1.0);
275  if (fade_in) {
276    original_opacity_ = 0;
277    if (border_widget_)
278      border_widget_->SetOpacity(original_opacity_);
279    GetWidget()->SetOpacity(original_opacity_);
280    GetWidget()->Show();
281    fade_animation_->Show();
282  } else {
283    original_opacity_ = 255;
284    fade_animation_->Hide();
285  }
286#endif
287}
288
289void BubbleDelegateView::ResetFade() {
290  fade_animation_.reset();
291  if (border_widget_)
292    border_widget_->SetOpacity(original_opacity_);
293  GetWidget()->SetOpacity(original_opacity_);
294}
295
296void BubbleDelegateView::SetAlignment(BubbleBorder::BubbleAlignment alignment) {
297  GetBubbleFrameView()->bubble_border()->set_alignment(alignment);
298  SizeToContents();
299}
300
301void BubbleDelegateView::SetArrowPaintType(
302    BubbleBorder::ArrowPaintType paint_type) {
303  GetBubbleFrameView()->bubble_border()->set_paint_arrow(paint_type);
304  SizeToContents();
305}
306
307void BubbleDelegateView::OnAnchorViewBoundsChanged() {
308  SizeToContents();
309}
310
311bool BubbleDelegateView::AcceleratorPressed(
312    const ui::Accelerator& accelerator) {
313  if (!close_on_esc() || accelerator.key_code() != ui::VKEY_ESCAPE)
314    return false;
315  if (fade_animation_.get())
316    fade_animation_->Reset();
317  GetWidget()->Close();
318  return true;
319}
320
321void BubbleDelegateView::OnNativeThemeChanged(const ui::NativeTheme* theme) {
322  UpdateColorsFromTheme(theme);
323}
324
325void BubbleDelegateView::AnimationEnded(const ui::Animation* animation) {
326  if (animation != fade_animation_.get())
327    return;
328  bool closed = fade_animation_->GetCurrentValue() == 0;
329  fade_animation_->Reset();
330  if (closed)
331    GetWidget()->Close();
332}
333
334void BubbleDelegateView::AnimationProgressed(const ui::Animation* animation) {
335  if (animation != fade_animation_.get())
336    return;
337  DCHECK(fade_animation_->is_animating());
338  unsigned char opacity = fade_animation_->GetCurrentValue() * 255;
339#if defined(OS_WIN) && !defined(USE_AURA)
340  // Explicitly set the content Widget's layered style and set transparency via
341  // SetLayeredWindowAttributes. This is done because initializing the Widget as
342  // transparent and setting opacity via UpdateLayeredWindow doesn't support
343  // hosting child native Windows controls.
344  const HWND hwnd = GetWidget()->GetNativeView();
345  const DWORD style = GetWindowLong(hwnd, GWL_EXSTYLE);
346  if ((opacity == 255) == !!(style & WS_EX_LAYERED))
347    SetWindowLong(hwnd, GWL_EXSTYLE, style ^ WS_EX_LAYERED);
348  SetLayeredWindowAttributes(hwnd, 0, opacity, LWA_ALPHA);
349  // Update the border widget's opacity.
350  border_widget_->SetOpacity(opacity);
351#endif
352  GetWidget()->SetOpacity(opacity);
353}
354
355void BubbleDelegateView::Init() {}
356
357void BubbleDelegateView::SizeToContents() {
358#if defined(OS_WIN) && !defined(USE_AURA)
359  border_widget_->SetBounds(GetBubbleBounds());
360  GetWidget()->SetBounds(GetBubbleClientBounds());
361
362  // Update the local client bounds clipped out by the border widget background.
363  // Used to correctly display overlapping semi-transparent widgets on Windows.
364  GetBubbleFrameView()->bubble_border()->set_client_bounds(
365      GetBubbleFrameView()->GetBoundsForClientView());
366#else
367  GetWidget()->SetBounds(GetBubbleBounds());
368#endif
369}
370
371BubbleFrameView* BubbleDelegateView::GetBubbleFrameView() const {
372  const Widget* widget = border_widget_ ? border_widget_ : GetWidget();
373  const NonClientView* view = widget ? widget->non_client_view() : NULL;
374  return view ? static_cast<BubbleFrameView*>(view->frame_view()) : NULL;
375}
376
377gfx::Rect BubbleDelegateView::GetBubbleBounds() {
378  // The argument rect has its origin at the bubble's arrow anchor point;
379  // its size is the preferred size of the bubble's client view (this view).
380  return GetBubbleFrameView()->GetUpdatedWindowBounds(GetAnchorRect(),
381      GetPreferredSize(), adjust_if_offscreen_);
382}
383
384int BubbleDelegateView::GetFadeDuration() {
385  return kHideFadeDurationMS;
386}
387
388void BubbleDelegateView::UpdateColorsFromTheme(const ui::NativeTheme* theme) {
389  if (!color_explicitly_set_) {
390    color_ = GetNativeTheme()->GetSystemColor(
391        ui::NativeTheme::kColorId_WindowBackground);
392  }
393  set_background(Background::CreateSolidBackground(color()));
394  BubbleFrameView* frame_view = GetBubbleFrameView();
395  if (frame_view)
396    frame_view->bubble_border()->set_background_color(color());
397}
398
399#if defined(OS_WIN) && !defined(USE_AURA)
400gfx::Rect BubbleDelegateView::GetBubbleClientBounds() const {
401  gfx::Rect client_bounds(GetBubbleFrameView()->GetBoundsForClientView());
402  client_bounds.Offset(
403      border_widget_->GetWindowBoundsInScreen().OffsetFromOrigin());
404  return client_bounds;
405}
406#endif
407
408}  // namespace views
409