1// Copyright (c) 2011 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 "chrome/browser/ui/views/bubble/bubble.h"
6
7#include <vector>
8
9#include "chrome/browser/ui/views/bubble/border_contents.h"
10#include "content/common/notification_service.h"
11#include "ui/base/animation/slide_animation.h"
12#include "ui/base/keycodes/keyboard_codes.h"
13#include "ui/gfx/color_utils.h"
14#include "views/layout/fill_layout.h"
15#include "views/widget/root_view.h"
16#include "views/widget/widget.h"
17#include "views/window/client_view.h"
18#include "views/window/window.h"
19
20#if defined(OS_CHROMEOS)
21#include "chrome/browser/chromeos/wm_ipc.h"
22#include "third_party/cros/chromeos_wm_ipc_enums.h"
23#endif
24
25#if defined(OS_WIN)
26#include "chrome/browser/ui/views/bubble/border_widget_win.h"
27#endif
28
29using std::vector;
30
31// How long the fade should last for.
32static const int kHideFadeDurationMS = 200;
33
34// Background color of the bubble.
35#if defined(OS_WIN)
36const SkColor Bubble::kBackgroundColor =
37    color_utils::GetSysSkColor(COLOR_WINDOW);
38#else
39// TODO(beng): source from theme provider.
40const SkColor Bubble::kBackgroundColor = SK_ColorWHITE;
41#endif
42
43// BubbleDelegate ---------------------------------------------------------
44
45std::wstring BubbleDelegate::accessible_name() {
46  return L"";
47}
48
49// Bubble -----------------------------------------------------------------
50
51// static
52Bubble* Bubble::Show(views::Widget* parent,
53                     const gfx::Rect& position_relative_to,
54                     BubbleBorder::ArrowLocation arrow_location,
55                     views::View* contents,
56                     BubbleDelegate* delegate) {
57  Bubble* bubble = new Bubble;
58  bubble->InitBubble(parent, position_relative_to, arrow_location,
59                     contents, delegate);
60  return bubble;
61}
62
63#if defined(OS_CHROMEOS)
64// static
65Bubble* Bubble::ShowFocusless(
66    views::Widget* parent,
67    const gfx::Rect& position_relative_to,
68    BubbleBorder::ArrowLocation arrow_location,
69    views::View* contents,
70    BubbleDelegate* delegate,
71    bool show_while_screen_is_locked) {
72  Bubble* bubble = new Bubble(views::WidgetGtk::TYPE_POPUP,
73                              show_while_screen_is_locked);
74  bubble->InitBubble(parent, position_relative_to, arrow_location,
75                     contents, delegate);
76  return bubble;
77}
78#endif
79
80void Bubble::Close() {
81  if (show_status_ != kOpen)
82    return;
83
84  show_status_ = kClosing;
85
86  if (fade_away_on_close_)
87    FadeOut();
88  else
89    DoClose(false);
90}
91
92void Bubble::AnimationEnded(const ui::Animation* animation) {
93  if (static_cast<int>(animation_->GetCurrentValue()) == 0) {
94    // When fading out we just need to close the bubble at the end
95    DoClose(false);
96  } else {
97#if defined(OS_WIN)
98    // When fading in we need to remove the layered window style flag, since
99    // that style prevents some bubble content from working properly.
100    SetWindowLong(GWL_EXSTYLE, GetWindowLong(GWL_EXSTYLE) & ~WS_EX_LAYERED);
101#endif
102  }
103}
104
105void Bubble::AnimationProgressed(const ui::Animation* animation) {
106#if defined(OS_WIN)
107  // Set the opacity for the main contents window.
108  unsigned char opacity = static_cast<unsigned char>(
109      animation_->GetCurrentValue() * 255);
110  SetLayeredWindowAttributes(GetNativeView(), 0,
111      static_cast<byte>(opacity), LWA_ALPHA);
112  contents_->SchedulePaint();
113
114  // Also fade in/out the bubble border window.
115  border_->SetOpacity(opacity);
116  border_->border_contents()->SchedulePaint();
117#else
118  NOTIMPLEMENTED();
119#endif
120}
121
122Bubble::Bubble()
123    :
124#if defined(OS_LINUX)
125      WidgetGtk(TYPE_WINDOW),
126      border_contents_(NULL),
127#elif defined(OS_WIN)
128      border_(NULL),
129#endif
130      delegate_(NULL),
131      show_status_(kOpen),
132      fade_away_on_close_(false),
133#if defined(OS_CHROMEOS)
134      show_while_screen_is_locked_(false),
135#endif
136      arrow_location_(BubbleBorder::NONE),
137      contents_(NULL) {
138}
139
140#if defined(OS_CHROMEOS)
141Bubble::Bubble(views::WidgetGtk::Type type, bool show_while_screen_is_locked)
142    : WidgetGtk(type),
143      border_contents_(NULL),
144      delegate_(NULL),
145      show_status_(kOpen),
146      fade_away_on_close_(false),
147      show_while_screen_is_locked_(show_while_screen_is_locked),
148      arrow_location_(BubbleBorder::NONE),
149      contents_(NULL) {
150}
151#endif
152
153Bubble::~Bubble() {
154}
155
156void Bubble::InitBubble(views::Widget* parent,
157                        const gfx::Rect& position_relative_to,
158                        BubbleBorder::ArrowLocation arrow_location,
159                        views::View* contents,
160                        BubbleDelegate* delegate) {
161  delegate_ = delegate;
162  position_relative_to_ = position_relative_to;
163  arrow_location_ = arrow_location;
164  contents_ = contents;
165
166  // Create the main window.
167#if defined(OS_WIN)
168  views::Window* parent_window = parent->GetWindow();
169  if (parent_window)
170    parent_window->DisableInactiveRendering();
171  set_window_style(WS_POPUP | WS_CLIPCHILDREN);
172  int extended_style = WS_EX_TOOLWINDOW;
173  // During FadeIn we need to turn on the layered window style to deal with
174  // transparency. This flag needs to be reset after fading in is complete.
175  bool fade_in = delegate_ && delegate_->FadeInOnShow();
176  if (fade_in)
177    extended_style |= WS_EX_LAYERED;
178  set_window_ex_style(extended_style);
179
180  DCHECK(!border_);
181  border_ = new BorderWidgetWin();
182
183  if (fade_in) {
184    border_->SetOpacity(0);
185    SetOpacity(0);
186  }
187
188  border_->Init(CreateBorderContents(), parent->GetNativeView());
189  border_->border_contents()->SetBackgroundColor(kBackgroundColor);
190
191  // We make the BorderWidgetWin the owner of the Bubble HWND, so that the
192  // latter is displayed on top of the former.
193  WidgetWin::Init(border_->GetNativeView(), gfx::Rect());
194
195  SetWindowText(GetNativeView(), delegate_->accessible_name().c_str());
196#elif defined(OS_LINUX)
197  MakeTransparent();
198  make_transient_to_parent();
199  WidgetGtk::InitWithWidget(parent, gfx::Rect());
200#if defined(OS_CHROMEOS)
201  {
202    vector<int> params;
203    params.push_back(show_while_screen_is_locked_ ? 1 : 0);
204    chromeos::WmIpc::instance()->SetWindowType(
205        GetNativeView(),
206        chromeos::WM_IPC_WINDOW_CHROME_INFO_BUBBLE,
207        &params);
208  }
209#endif
210#endif
211
212  // Create a View to hold the contents of the main window.
213  views::View* contents_view = new views::View;
214  // We add |contents_view| to ourselves before the AddChildView() call below so
215  // that when |contents| gets added, it will already have a widget, and thus
216  // any NativeButtons it creates in ViewHierarchyChanged() will be functional
217  // (e.g. calling SetChecked() on checkboxes is safe).
218  SetContentsView(contents_view);
219  // Adding |contents| as a child has to be done before we call
220  // contents->GetPreferredSize() below, since some supplied views don't
221  // actually initialize themselves until they're added to a hierarchy.
222  contents_view->AddChildView(contents);
223
224  // Calculate and set the bounds for all windows and views.
225  gfx::Rect window_bounds;
226
227#if defined(OS_WIN)
228  // Initialize and position the border window.
229  window_bounds = border_->SizeAndGetBounds(position_relative_to,
230                                            arrow_location,
231                                            contents->GetPreferredSize());
232
233  // Make |contents| take up the entire contents view.
234  contents_view->SetLayoutManager(new views::FillLayout);
235
236  // Paint the background color behind the contents.
237  contents_view->set_background(
238      views::Background::CreateSolidBackground(kBackgroundColor));
239#else
240  // Create a view to paint the border and background.
241  border_contents_ = CreateBorderContents();
242  border_contents_->Init();
243  border_contents_->SetBackgroundColor(kBackgroundColor);
244  gfx::Rect contents_bounds;
245  border_contents_->SizeAndGetBounds(position_relative_to,
246      arrow_location, false, contents->GetPreferredSize(),
247      &contents_bounds, &window_bounds);
248  // This new view must be added before |contents| so it will paint under it.
249  contents_view->AddChildViewAt(border_contents_, 0);
250
251  // |contents_view| has no layout manager, so we have to explicitly position
252  // its children.
253  border_contents_->SetBoundsRect(
254      gfx::Rect(gfx::Point(), window_bounds.size()));
255  contents->SetBoundsRect(contents_bounds);
256#endif
257  SetBounds(window_bounds);
258
259  // Register the Escape accelerator for closing.
260  GetFocusManager()->RegisterAccelerator(
261      views::Accelerator(ui::VKEY_ESCAPE, false, false, false), this);
262
263  // Done creating the bubble.
264  NotificationService::current()->Notify(NotificationType::INFO_BUBBLE_CREATED,
265                                         Source<Bubble>(this),
266                                         NotificationService::NoDetails());
267
268  // Show the window.
269#if defined(OS_WIN)
270  border_->ShowWindow(SW_SHOW);
271  ShowWindow(SW_SHOW);
272  if (fade_in)
273    FadeIn();
274#elif defined(OS_LINUX)
275  views::WidgetGtk::Show();
276#endif
277}
278
279BorderContents* Bubble::CreateBorderContents() {
280  return new BorderContents();
281}
282
283void Bubble::SizeToContents() {
284  gfx::Rect window_bounds;
285
286#if defined(OS_WIN)
287  // Initialize and position the border window.
288  window_bounds = border_->SizeAndGetBounds(position_relative_to_,
289                                            arrow_location_,
290                                            contents_->GetPreferredSize());
291#else
292  gfx::Rect contents_bounds;
293  border_contents_->SizeAndGetBounds(position_relative_to_,
294      arrow_location_, false, contents_->GetPreferredSize(),
295      &contents_bounds, &window_bounds);
296  // |contents_view| has no layout manager, so we have to explicitly position
297  // its children.
298  border_contents_->SetBoundsRect(
299      gfx::Rect(gfx::Point(), window_bounds.size()));
300  contents_->SetBoundsRect(contents_bounds);
301#endif
302  SetBounds(window_bounds);
303}
304
305#if defined(OS_WIN)
306void Bubble::OnActivate(UINT action, BOOL minimized, HWND window) {
307  // The popup should close when it is deactivated.
308  if (action == WA_INACTIVE) {
309    Close();
310  } else if (action == WA_ACTIVE) {
311    DCHECK(GetRootView()->has_children());
312    GetRootView()->GetChildViewAt(0)->RequestFocus();
313  }
314}
315#elif defined(OS_LINUX)
316void Bubble::IsActiveChanged() {
317  if (!IsActive())
318    Close();
319}
320#endif
321
322void Bubble::DoClose(bool closed_by_escape) {
323  if (show_status_ == kClosed)
324    return;
325
326  GetFocusManager()->UnregisterAccelerator(
327      views::Accelerator(ui::VKEY_ESCAPE, false, false, false), this);
328  if (delegate_)
329    delegate_->BubbleClosing(this, closed_by_escape);
330  show_status_ = kClosed;
331#if defined(OS_WIN)
332  border_->Close();
333  WidgetWin::Close();
334#elif defined(OS_LINUX)
335  WidgetGtk::Close();
336#endif
337}
338
339void Bubble::FadeIn() {
340  Fade(true);  // |fade_in|.
341}
342
343void Bubble::FadeOut() {
344#if defined(OS_WIN)
345  // The contents window cannot have the layered flag on by default, since its
346  // content doesn't always work inside a layered window, but when animating it
347  // is ok to set that style on the window for the purpose of fading it out.
348  SetWindowLong(GWL_EXSTYLE, GetWindowLong(GWL_EXSTYLE) | WS_EX_LAYERED);
349  // This must be the very next call, otherwise we can get flicker on close.
350  SetLayeredWindowAttributes(GetNativeView(), 0,
351      static_cast<byte>(255), LWA_ALPHA);
352#endif
353
354  Fade(false);  // |fade_in|.
355}
356
357void Bubble::Fade(bool fade_in) {
358  animation_.reset(new ui::SlideAnimation(this));
359  animation_->SetSlideDuration(kHideFadeDurationMS);
360  animation_->SetTweenType(ui::Tween::LINEAR);
361
362  animation_->Reset(fade_in ? 0.0 : 1.0);
363  if (fade_in)
364    animation_->Show();
365  else
366    animation_->Hide();
367}
368
369bool Bubble::AcceleratorPressed(const views::Accelerator& accelerator) {
370  if (!delegate_ || delegate_->CloseOnEscape()) {
371    DoClose(true);
372    return true;
373  }
374  return false;
375}
376