• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/notifications/balloon_view.h"
6 
7 #include <vector>
8 
9 #include "base/message_loop.h"
10 #include "base/utf_string_conversions.h"
11 #include "chrome/browser/notifications/balloon.h"
12 #include "chrome/browser/notifications/balloon_collection.h"
13 #include "chrome/browser/notifications/desktop_notification_service.h"
14 #include "chrome/browser/notifications/notification.h"
15 #include "chrome/browser/notifications/notification_options_menu_model.h"
16 #include "chrome/browser/ui/views/bubble/bubble_border.h"
17 #include "chrome/browser/ui/views/notifications/balloon_view_host.h"
18 #include "content/browser/renderer_host/render_view_host.h"
19 #include "content/browser/renderer_host/render_widget_host_view.h"
20 #include "content/common/notification_details.h"
21 #include "content/common/notification_source.h"
22 #include "content/common/notification_type.h"
23 #include "grit/generated_resources.h"
24 #include "grit/theme_resources.h"
25 #include "ui/base/animation/slide_animation.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/base/resource/resource_bundle.h"
28 #include "ui/gfx/canvas_skia.h"
29 #include "ui/gfx/insets.h"
30 #include "ui/gfx/native_widget_types.h"
31 #include "views/controls/button/button.h"
32 #include "views/controls/button/image_button.h"
33 #include "views/controls/button/text_button.h"
34 #include "views/controls/menu/menu_2.h"
35 #include "views/controls/native/native_view_host.h"
36 #include "views/painter.h"
37 #include "views/widget/root_view.h"
38 #if defined(OS_WIN)
39 #include "views/widget/widget_win.h"
40 #endif
41 #if defined(OS_LINUX)
42 #include "views/widget/widget_gtk.h"
43 #endif
44 
45 using views::Widget;
46 
47 namespace {
48 
49 const int kTopMargin = 2;
50 const int kBottomMargin = 0;
51 const int kLeftMargin = 4;
52 const int kRightMargin = 4;
53 const int kShelfBorderTopOverlap = 0;
54 
55 // Properties of the dismiss button.
56 const int kDismissButtonWidth = 14;
57 const int kDismissButtonHeight = 14;
58 const int kDismissButtonTopMargin = 6;
59 const int kDismissButtonRightMargin = 6;
60 
61 // Properties of the options menu.
62 const int kOptionsButtonWidth = 21;
63 const int kOptionsButtonHeight = 14;
64 const int kOptionsButtonTopMargin = 5;
65 const int kOptionsButtonRightMargin = 4;
66 
67 // Properties of the origin label.
68 const int kLabelLeftMargin = 10;
69 const int kLabelTopMargin = 6;
70 
71 // Size of the drop shadow.  The shadow is provided by BubbleBorder,
72 // not this class.
73 const int kLeftShadowWidth = 0;
74 const int kRightShadowWidth = 0;
75 const int kTopShadowWidth = 0;
76 const int kBottomShadowWidth = 6;
77 
78 // Optional animation.
79 const bool kAnimateEnabled = true;
80 
81 // The shelf height for the system default font size.  It is scaled
82 // with changes in the default font size.
83 const int kDefaultShelfHeight = 22;
84 
85 // Menu commands
86 const int kRevokePermissionCommand = 0;
87 
88 // Colors
89 const SkColor kControlBarBackgroundColor = SkColorSetRGB(245, 245, 245);
90 const SkColor kControlBarTextColor = SkColorSetRGB(125, 125, 125);
91 const SkColor kControlBarSeparatorLineColor = SkColorSetRGB(180, 180, 180);
92 
93 }  // namespace
94 
BalloonViewImpl(BalloonCollection * collection)95 BalloonViewImpl::BalloonViewImpl(BalloonCollection* collection)
96     : balloon_(NULL),
97       collection_(collection),
98       frame_container_(NULL),
99       html_container_(NULL),
100       html_contents_(NULL),
101       method_factory_(this),
102       close_button_(NULL),
103       animation_(NULL),
104       options_menu_model_(NULL),
105       options_menu_menu_(NULL),
106       options_menu_button_(NULL) {
107   // This object is not to be deleted by the views hierarchy,
108   // as it is owned by the balloon.
109   set_parent_owned(false);
110 
111   BubbleBorder* bubble_border = new BubbleBorder(BubbleBorder::FLOAT);
112   set_border(bubble_border);
113 }
114 
~BalloonViewImpl()115 BalloonViewImpl::~BalloonViewImpl() {
116 }
117 
Close(bool by_user)118 void BalloonViewImpl::Close(bool by_user) {
119   MessageLoop::current()->PostTask(FROM_HERE,
120       method_factory_.NewRunnableMethod(
121           &BalloonViewImpl::DelayedClose, by_user));
122 }
123 
GetSize() const124 gfx::Size BalloonViewImpl::GetSize() const {
125   // BalloonView has no size if it hasn't been shown yet (which is when
126   // balloon_ is set).
127   if (!balloon_)
128     return gfx::Size(0, 0);
129 
130   return gfx::Size(GetTotalWidth(), GetTotalHeight());
131 }
132 
GetHost() const133 BalloonHost* BalloonViewImpl::GetHost() const {
134   return html_contents_.get();
135 }
136 
RunMenu(views::View * source,const gfx::Point & pt)137 void BalloonViewImpl::RunMenu(views::View* source, const gfx::Point& pt) {
138   RunOptionsMenu(pt);
139 }
140 
OnDisplayChanged()141 void BalloonViewImpl::OnDisplayChanged() {
142   collection_->DisplayChanged();
143 }
144 
OnWorkAreaChanged()145 void BalloonViewImpl::OnWorkAreaChanged() {
146   collection_->DisplayChanged();
147 }
148 
ButtonPressed(views::Button * sender,const views::Event &)149 void BalloonViewImpl::ButtonPressed(views::Button* sender,
150                                     const views::Event&) {
151   // The only button currently is the close button.
152   DCHECK(sender == close_button_);
153   Close(true);
154 }
155 
DelayedClose(bool by_user)156 void BalloonViewImpl::DelayedClose(bool by_user) {
157   html_contents_->Shutdown();
158   html_container_->CloseNow();
159   // The BalloonViewImpl has to be detached from frame_container_ now
160   // because CloseNow on linux/views destroys the view hierachy
161   // asynchronously.
162   frame_container_->GetRootView()->RemoveAllChildViews(true);
163   frame_container_->CloseNow();
164   balloon_->OnClose(by_user);
165 }
166 
GetPreferredSize()167 gfx::Size BalloonViewImpl::GetPreferredSize() {
168   return gfx::Size(1000, 1000);
169 }
170 
SizeContentsWindow()171 void BalloonViewImpl::SizeContentsWindow() {
172   if (!html_container_ || !frame_container_)
173     return;
174 
175   gfx::Rect contents_rect = GetContentsRectangle();
176   html_container_->SetBounds(contents_rect);
177   html_container_->MoveAboveWidget(frame_container_);
178 
179   gfx::Path path;
180   GetContentsMask(contents_rect, &path);
181   html_container_->SetShape(path.CreateNativeRegion());
182 
183   close_button_->SetBoundsRect(GetCloseButtonBounds());
184   options_menu_button_->SetBoundsRect(GetOptionsButtonBounds());
185   source_label_->SetBoundsRect(GetLabelBounds());
186 }
187 
RepositionToBalloon()188 void BalloonViewImpl::RepositionToBalloon() {
189   DCHECK(frame_container_);
190   DCHECK(html_container_);
191   DCHECK(balloon_);
192 
193   if (!kAnimateEnabled) {
194     frame_container_->SetBounds(
195         gfx::Rect(balloon_->GetPosition().x(), balloon_->GetPosition().y(),
196                   GetTotalWidth(), GetTotalHeight()));
197     gfx::Rect contents_rect = GetContentsRectangle();
198     html_container_->SetBounds(contents_rect);
199     html_contents_->SetPreferredSize(contents_rect.size());
200     RenderWidgetHostView* view = html_contents_->render_view_host()->view();
201     if (view)
202       view->SetSize(contents_rect.size());
203     return;
204   }
205 
206   anim_frame_end_ = gfx::Rect(
207       balloon_->GetPosition().x(), balloon_->GetPosition().y(),
208       GetTotalWidth(), GetTotalHeight());
209   anim_frame_start_ = frame_container_->GetClientAreaScreenBounds();
210   animation_.reset(new ui::SlideAnimation(this));
211   animation_->Show();
212 }
213 
Update()214 void BalloonViewImpl::Update() {
215   DCHECK(html_contents_.get()) << "BalloonView::Update called before Show";
216   if (html_contents_->render_view_host())
217     html_contents_->render_view_host()->NavigateToURL(
218         balloon_->notification().content_url());
219 }
220 
AnimationProgressed(const ui::Animation * animation)221 void BalloonViewImpl::AnimationProgressed(const ui::Animation* animation) {
222   DCHECK(animation == animation_.get());
223 
224   // Linear interpolation from start to end position.
225   double e = animation->GetCurrentValue();
226   double s = (1.0 - e);
227 
228   gfx::Rect frame_position(
229     static_cast<int>(s * anim_frame_start_.x() +
230                      e * anim_frame_end_.x()),
231     static_cast<int>(s * anim_frame_start_.y() +
232                      e * anim_frame_end_.y()),
233     static_cast<int>(s * anim_frame_start_.width() +
234                      e * anim_frame_end_.width()),
235     static_cast<int>(s * anim_frame_start_.height() +
236                      e * anim_frame_end_.height()));
237   frame_container_->SetBounds(frame_position);
238 
239   gfx::Path path;
240   gfx::Rect contents_rect = GetContentsRectangle();
241   html_container_->SetBounds(contents_rect);
242   GetContentsMask(contents_rect, &path);
243   html_container_->SetShape(path.CreateNativeRegion());
244 
245   html_contents_->SetPreferredSize(contents_rect.size());
246   RenderWidgetHostView* view = html_contents_->render_view_host()->view();
247   if (view)
248     view->SetSize(contents_rect.size());
249 }
250 
GetCloseButtonBounds() const251 gfx::Rect BalloonViewImpl::GetCloseButtonBounds() const {
252   return gfx::Rect(
253       width() - kDismissButtonWidth -
254           kDismissButtonRightMargin - kRightShadowWidth,
255       kDismissButtonTopMargin,
256       kDismissButtonWidth,
257       kDismissButtonHeight);
258 }
259 
GetOptionsButtonBounds() const260 gfx::Rect BalloonViewImpl::GetOptionsButtonBounds() const {
261   gfx::Rect close_rect = GetCloseButtonBounds();
262 
263   return gfx::Rect(
264       close_rect.x() - kOptionsButtonWidth - kOptionsButtonRightMargin,
265       kOptionsButtonTopMargin,
266       kOptionsButtonWidth,
267       kOptionsButtonHeight);
268 }
269 
GetLabelBounds() const270 gfx::Rect BalloonViewImpl::GetLabelBounds() const {
271   return gfx::Rect(
272       kLeftShadowWidth + kLabelLeftMargin,
273       kLabelTopMargin,
274       std::max(0, width() - kOptionsButtonWidth -
275                kRightMargin),
276       kOptionsButtonHeight);
277 }
278 
Show(Balloon * balloon)279 void BalloonViewImpl::Show(Balloon* balloon) {
280   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
281 
282   balloon_ = balloon;
283 
284   SetBounds(balloon_->GetPosition().x(), balloon_->GetPosition().y(),
285             GetTotalWidth(), GetTotalHeight());
286 
287   const string16 source_label_text = l10n_util::GetStringFUTF16(
288       IDS_NOTIFICATION_BALLOON_SOURCE_LABEL,
289       balloon->notification().display_source());
290 
291   source_label_ = new views::Label(UTF16ToWide(source_label_text));
292   AddChildView(source_label_);
293   options_menu_button_ = new views::MenuButton(NULL, L"", this, false);
294   AddChildView(options_menu_button_);
295   close_button_ = new views::ImageButton(this);
296   close_button_->SetTooltipText(UTF16ToWide(l10n_util::GetStringUTF16(
297       IDS_NOTIFICATION_BALLOON_DISMISS_LABEL)));
298   AddChildView(close_button_);
299 
300   // We have to create two windows: one for the contents and one for the
301   // frame.  Why?
302   // * The contents is an html window which cannot be a
303   //   layered window (because it may have child windows for instance).
304   // * The frame is a layered window so that we can have nicely rounded
305   //   corners using alpha blending (and we may do other alpha blending
306   //   effects).
307   // Unfortunately, layered windows cannot have child windows. (Well, they can
308   // but the child windows don't render).
309   //
310   // We carefully keep these two windows in sync to present the illusion of
311   // one window to the user.
312   //
313   // We don't let the OS manage the RTL layout of these widgets, because
314   // this code is already taking care of correctly reversing the layout.
315   gfx::Rect contents_rect = GetContentsRectangle();
316   html_contents_.reset(new BalloonViewHost(balloon));
317   html_contents_->SetPreferredSize(gfx::Size(10000, 10000));
318   Widget::CreateParams params(Widget::CreateParams::TYPE_POPUP);
319   params.mirror_origin_in_rtl = false;
320   html_container_ = Widget::CreateWidget(params);
321   html_container_->SetAlwaysOnTop(true);
322   html_container_->Init(NULL, contents_rect);
323   html_container_->SetContentsView(html_contents_->view());
324 
325   gfx::Rect balloon_rect(x(), y(), GetTotalWidth(), GetTotalHeight());
326   params.transparent = true;
327   frame_container_ = Widget::CreateWidget(params);
328   frame_container_->set_widget_delegate(this);
329   frame_container_->SetAlwaysOnTop(true);
330   frame_container_->Init(NULL, balloon_rect);
331   frame_container_->SetContentsView(this);
332   frame_container_->MoveAboveWidget(html_container_);
333 
334   close_button_->SetImage(views::CustomButton::BS_NORMAL,
335                           rb.GetBitmapNamed(IDR_TAB_CLOSE));
336   close_button_->SetImage(views::CustomButton::BS_HOT,
337                           rb.GetBitmapNamed(IDR_TAB_CLOSE_H));
338   close_button_->SetImage(views::CustomButton::BS_PUSHED,
339                           rb.GetBitmapNamed(IDR_TAB_CLOSE_P));
340   close_button_->SetBoundsRect(GetCloseButtonBounds());
341   close_button_->SetBackground(SK_ColorBLACK,
342                                rb.GetBitmapNamed(IDR_TAB_CLOSE),
343                                rb.GetBitmapNamed(IDR_TAB_CLOSE_MASK));
344 
345   options_menu_button_->SetIcon(*rb.GetBitmapNamed(IDR_BALLOON_WRENCH));
346   options_menu_button_->SetHoverIcon(*rb.GetBitmapNamed(IDR_BALLOON_WRENCH_H));
347   options_menu_button_->SetPushedIcon(*rb.GetBitmapNamed(IDR_BALLOON_WRENCH_P));
348   options_menu_button_->set_alignment(views::TextButton::ALIGN_CENTER);
349   options_menu_button_->set_border(NULL);
350   options_menu_button_->SetBoundsRect(GetOptionsButtonBounds());
351 
352   source_label_->SetFont(rb.GetFont(ResourceBundle::SmallFont));
353   source_label_->SetColor(kControlBarTextColor);
354   source_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
355   source_label_->SetBoundsRect(GetLabelBounds());
356 
357   SizeContentsWindow();
358   html_container_->Show();
359   frame_container_->Show();
360 
361   notification_registrar_.Add(this,
362     NotificationType::NOTIFY_BALLOON_DISCONNECTED, Source<Balloon>(balloon));
363 }
364 
RunOptionsMenu(const gfx::Point & pt)365 void BalloonViewImpl::RunOptionsMenu(const gfx::Point& pt) {
366   CreateOptionsMenu();
367   options_menu_menu_->RunMenuAt(pt, views::Menu2::ALIGN_TOPRIGHT);
368 }
369 
CreateOptionsMenu()370 void BalloonViewImpl::CreateOptionsMenu() {
371   if (options_menu_model_.get())
372     return;
373 
374   options_menu_model_.reset(new NotificationOptionsMenuModel(balloon_));
375   options_menu_menu_.reset(new views::Menu2(options_menu_model_.get()));
376 }
377 
GetContentsMask(const gfx::Rect & rect,gfx::Path * path) const378 void BalloonViewImpl::GetContentsMask(const gfx::Rect& rect,
379                                       gfx::Path* path) const {
380   // This rounds the corners, and we also cut out a circle for the close
381   // button, since we can't guarantee the ordering of two top-most windows.
382   SkScalar radius = SkIntToScalar(BubbleBorder::GetCornerRadius());
383   SkScalar spline_radius = radius -
384       SkScalarMul(radius, (SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3);
385   SkScalar left = SkIntToScalar(0);
386   SkScalar top = SkIntToScalar(0);
387   SkScalar right = SkIntToScalar(rect.width());
388   SkScalar bottom = SkIntToScalar(rect.height());
389 
390   path->moveTo(left, top);
391   path->lineTo(right, top);
392   path->lineTo(right, bottom - radius);
393   path->cubicTo(right, bottom - spline_radius,
394                 right - spline_radius, bottom,
395                 right - radius, bottom);
396   path->lineTo(left + radius, bottom);
397   path->cubicTo(left + spline_radius, bottom,
398                 left, bottom - spline_radius,
399                 left, bottom - radius);
400   path->lineTo(left, top);
401   path->close();
402 }
403 
GetFrameMask(const gfx::Rect & rect,gfx::Path * path) const404 void BalloonViewImpl::GetFrameMask(const gfx::Rect& rect,
405                                    gfx::Path* path) const {
406   SkScalar radius = SkIntToScalar(BubbleBorder::GetCornerRadius());
407   SkScalar spline_radius = radius -
408       SkScalarMul(radius, (SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3);
409   SkScalar left = SkIntToScalar(rect.x());
410   SkScalar top = SkIntToScalar(rect.y());
411   SkScalar right = SkIntToScalar(rect.right());
412   SkScalar bottom = SkIntToScalar(rect.bottom());
413 
414   path->moveTo(left, bottom);
415   path->lineTo(left, top + radius);
416   path->cubicTo(left, top + spline_radius,
417                 left + spline_radius, top,
418                 left + radius, top);
419   path->lineTo(right - radius, top);
420   path->cubicTo(right - spline_radius, top,
421                 right, top + spline_radius,
422                 right, top + radius);
423   path->lineTo(right, bottom);
424   path->lineTo(left, bottom);
425   path->close();
426 }
427 
GetContentsOffset() const428 gfx::Point BalloonViewImpl::GetContentsOffset() const {
429   return gfx::Point(kLeftShadowWidth + kLeftMargin,
430                     kTopShadowWidth + kTopMargin);
431 }
432 
GetShelfHeight() const433 int BalloonViewImpl::GetShelfHeight() const {
434   // TODO(johnnyg): add scaling here.
435   return kDefaultShelfHeight;
436 }
437 
GetBalloonFrameHeight() const438 int BalloonViewImpl::GetBalloonFrameHeight() const {
439   return GetTotalHeight() - GetShelfHeight();
440 }
441 
GetTotalWidth() const442 int BalloonViewImpl::GetTotalWidth() const {
443   return balloon_->content_size().width()
444       + kLeftMargin + kRightMargin + kLeftShadowWidth + kRightShadowWidth;
445 }
446 
GetTotalHeight() const447 int BalloonViewImpl::GetTotalHeight() const {
448   return balloon_->content_size().height()
449       + kTopMargin + kBottomMargin + kTopShadowWidth + kBottomShadowWidth
450       + GetShelfHeight();
451 }
452 
GetContentsRectangle() const453 gfx::Rect BalloonViewImpl::GetContentsRectangle() const {
454   if (!frame_container_)
455     return gfx::Rect();
456 
457   gfx::Size content_size = balloon_->content_size();
458   gfx::Point offset = GetContentsOffset();
459   gfx::Rect frame_rect = frame_container_->GetWindowScreenBounds();
460   return gfx::Rect(frame_rect.x() + offset.x(),
461                    frame_rect.y() + GetShelfHeight() + offset.y(),
462                    content_size.width(),
463                    content_size.height());
464 }
465 
OnPaint(gfx::Canvas * canvas)466 void BalloonViewImpl::OnPaint(gfx::Canvas* canvas) {
467   DCHECK(canvas);
468   // Paint the menu bar area white, with proper rounded corners.
469   gfx::Path path;
470   gfx::Rect rect = GetContentsBounds();
471   rect.set_height(GetShelfHeight());
472   GetFrameMask(rect, &path);
473 
474   SkPaint paint;
475   paint.setAntiAlias(true);
476   paint.setColor(kControlBarBackgroundColor);
477   canvas->AsCanvasSkia()->drawPath(path, paint);
478 
479   // Draw a 1-pixel gray line between the content and the menu bar.
480   int line_width = GetTotalWidth() - kLeftMargin - kRightMargin;
481   canvas->FillRectInt(kControlBarSeparatorLineColor,
482       kLeftMargin, 1 + GetShelfHeight(), line_width, 1);
483 
484   View::OnPaint(canvas);
485   OnPaintBorder(canvas);
486 }
487 
OnBoundsChanged(const gfx::Rect & previous_bounds)488 void BalloonViewImpl::OnBoundsChanged(const gfx::Rect& previous_bounds) {
489   SizeContentsWindow();
490 }
491 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)492 void BalloonViewImpl::Observe(NotificationType type,
493                               const NotificationSource& source,
494                               const NotificationDetails& details) {
495   if (type != NotificationType::NOTIFY_BALLOON_DISCONNECTED) {
496     NOTREACHED();
497     return;
498   }
499 
500   // If the renderer process attached to this balloon is disconnected
501   // (e.g., because of a crash), we want to close the balloon.
502   notification_registrar_.Remove(this,
503       NotificationType::NOTIFY_BALLOON_DISCONNECTED, Source<Balloon>(balloon_));
504   Close(false);
505 }
506