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