Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / views / notifications / balloon_view_views.cc
blob61cb591d97e9455b337a410393747b156b06c08f
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.
5 #include "chrome/browser/ui/views/notifications/balloon_view_views.h"
7 #include <algorithm>
8 #include <vector>
10 #include "base/bind.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/notifications/balloon_collection.h"
15 #include "chrome/browser/notifications/desktop_notification_service.h"
16 #include "chrome/browser/notifications/notification.h"
17 #include "chrome/browser/notifications/notification_options_menu_model.h"
18 #include "content/public/browser/notification_details.h"
19 #include "content/public/browser/notification_source.h"
20 #include "content/public/browser/notification_types.h"
21 #include "content/public/browser/render_view_host.h"
22 #include "content/public/browser/render_widget_host_view.h"
23 #include "content/public/browser/web_contents.h"
24 #include "grit/generated_resources.h"
25 #include "grit/theme_resources.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/base/resource/resource_bundle.h"
28 #include "ui/gfx/animation/slide_animation.h"
29 #include "ui/gfx/canvas.h"
30 #include "ui/gfx/native_widget_types.h"
31 #include "ui/gfx/path.h"
32 #include "ui/views/bubble/bubble_border.h"
33 #include "ui/views/controls/button/image_button.h"
34 #include "ui/views/controls/button/menu_button.h"
35 #include "ui/views/controls/button/text_button.h"
36 #include "ui/views/controls/label.h"
37 #include "ui/views/controls/menu/menu_item_view.h"
38 #include "ui/views/controls/menu/menu_runner.h"
39 #include "ui/views/controls/native/native_view_host.h"
40 #include "ui/views/widget/widget.h"
42 #if defined(OS_CHROMEOS)
43 #include "chrome/browser/chromeos/notifications/balloon_view_host_chromeos.h"
44 #else
45 #include "chrome/browser/ui/views/notifications/balloon_view_host.h"
46 #endif
48 namespace {
50 const int kTopMargin = 2;
51 const int kBottomMargin = 0;
52 const int kLeftMargin = 4;
53 const int kRightMargin = 4;
55 // Margin between various shelf buttons/label and the shelf border.
56 const int kShelfMargin = 2;
58 // Spacing between the options and close buttons.
59 const int kOptionsDismissSpacing = 4;
61 // Spacing between the options button and label text.
62 const int kLabelOptionsSpacing = 4;
64 // Margin between shelf border and title label.
65 const int kLabelLeftMargin = 6;
67 // Size of the drop shadow. The shadow is provided by BubbleBorder,
68 // not this class.
69 const int kLeftShadowWidth = 0;
70 const int kRightShadowWidth = 0;
71 const int kTopShadowWidth = 0;
72 const int kBottomShadowWidth = 6;
74 // Optional animation.
75 const bool kAnimateEnabled = true;
77 // Colors
78 const SkColor kControlBarBackgroundColor = SkColorSetRGB(245, 245, 245);
79 const SkColor kControlBarTextColor = SkColorSetRGB(125, 125, 125);
80 const SkColor kControlBarSeparatorLineColor = SkColorSetRGB(180, 180, 180);
82 } // namespace
84 // static
85 int BalloonView::GetHorizontalMargin() {
86 return kLeftMargin + kRightMargin + kLeftShadowWidth + kRightShadowWidth;
89 BalloonViewImpl::BalloonViewImpl(BalloonCollection* collection)
90 : balloon_(NULL),
91 collection_(collection),
92 frame_container_(NULL),
93 html_container_(NULL),
94 close_button_(NULL),
95 options_menu_button_(NULL),
96 enable_web_ui_(false),
97 closed_by_user_(false),
98 closed_(false) {
99 // We're owned by Balloon and don't want to be deleted by our parent View.
100 set_owned_by_client();
102 set_border(new views::BubbleBorder(views::BubbleBorder::FLOAT,
103 views::BubbleBorder::NO_SHADOW, SK_ColorWHITE));
106 BalloonViewImpl::~BalloonViewImpl() {
109 void BalloonViewImpl::Close(bool by_user) {
110 if (closed_)
111 return;
113 closed_ = true;
114 animation_->Stop();
115 html_contents_->Shutdown();
116 // Detach contents from the widget before they close.
117 // This is necessary because a widget may be deleted
118 // after this when chrome is shutting down.
119 html_container_->GetRootView()->RemoveAllChildViews(true);
120 html_container_->Close();
121 frame_container_->GetRootView()->RemoveAllChildViews(true);
122 frame_container_->Close();
123 closed_by_user_ = by_user;
124 // |frame_container_->::Close()| is async. When processed it'll call back to
125 // DeleteDelegate() and we'll cleanup.
128 gfx::Size BalloonViewImpl::GetSize() const {
129 // BalloonView has no size if it hasn't been shown yet (which is when
130 // balloon_ is set).
131 if (!balloon_)
132 return gfx::Size(0, 0);
134 return gfx::Size(GetTotalWidth(), GetTotalHeight());
137 BalloonHost* BalloonViewImpl::GetHost() const {
138 return html_contents_.get();
141 void BalloonViewImpl::OnMenuButtonClicked(views::View* source,
142 const gfx::Point& point) {
143 CreateOptionsMenu();
145 menu_runner_.reset(new views::MenuRunner(options_menu_model_.get()));
147 gfx::Point screen_location;
148 views::View::ConvertPointToScreen(options_menu_button_, &screen_location);
149 if (menu_runner_->RunMenuAt(
150 source->GetWidget()->GetTopLevelWidget(),
151 options_menu_button_,
152 gfx::Rect(screen_location, options_menu_button_->size()),
153 views::MenuItemView::TOPRIGHT,
154 ui::MENU_SOURCE_NONE,
155 views::MenuRunner::HAS_MNEMONICS) == views::MenuRunner::MENU_DELETED)
156 return;
159 void BalloonViewImpl::OnDisplayChanged() {
160 collection_->DisplayChanged();
163 void BalloonViewImpl::OnWorkAreaChanged() {
164 collection_->DisplayChanged();
167 void BalloonViewImpl::DeleteDelegate() {
168 balloon_->OnClose(closed_by_user_);
171 void BalloonViewImpl::ButtonPressed(views::Button* sender, const ui::Event&) {
172 // The only button currently is the close button.
173 DCHECK_EQ(close_button_, sender);
174 Close(true);
177 gfx::Size BalloonViewImpl::GetPreferredSize() {
178 return gfx::Size(1000, 1000);
181 void BalloonViewImpl::SizeContentsWindow() {
182 if (!html_container_ || !frame_container_)
183 return;
185 gfx::Rect contents_rect = GetContentsRectangle();
186 html_container_->SetBounds(contents_rect);
187 html_container_->StackAboveWidget(frame_container_);
189 gfx::Path path;
190 GetContentsMask(contents_rect, &path);
191 html_container_->SetShape(path.CreateNativeRegion());
193 close_button_->SetBoundsRect(GetCloseButtonBounds());
194 options_menu_button_->SetBoundsRect(GetOptionsButtonBounds());
195 source_label_->SetBoundsRect(GetLabelBounds());
198 void BalloonViewImpl::RepositionToBalloon() {
199 if (closed_)
200 return;
202 DCHECK(frame_container_);
203 DCHECK(html_container_);
204 DCHECK(balloon_);
206 if (!kAnimateEnabled) {
207 frame_container_->SetBounds(GetBoundsForFrameContainer());
208 gfx::Rect contents_rect = GetContentsRectangle();
209 html_container_->SetBounds(contents_rect);
210 html_contents_->SetPreferredSize(contents_rect.size());
211 content::RenderWidgetHostView* view =
212 html_contents_->web_contents()->GetRenderWidgetHostView();
213 if (view)
214 view->SetSize(contents_rect.size());
215 return;
218 anim_frame_end_ = GetBoundsForFrameContainer();
219 anim_frame_start_ = frame_container_->GetClientAreaBoundsInScreen();
220 animation_.reset(new gfx::SlideAnimation(this));
221 animation_->Show();
224 void BalloonViewImpl::Update() {
225 if (closed_)
226 return;
228 // Tls might get called before html_contents_ is set in Show() if more than
229 // one update with the same replace_id occurs, or if an update occurs after
230 // the ballon has been closed (e.g. during shutdown) but before this has been
231 // destroyed.
232 if (!html_contents_.get() || !html_contents_->web_contents())
233 return;
234 html_contents_->web_contents()->GetController().LoadURL(
235 balloon_->notification().content_url(), content::Referrer(),
236 content::PAGE_TRANSITION_LINK, std::string());
239 void BalloonViewImpl::AnimationProgressed(const gfx::Animation* animation) {
240 DCHECK_EQ(animation_.get(), animation);
242 // Linear interpolation from start to end position.
243 gfx::Rect frame_position(animation_->CurrentValueBetween(
244 anim_frame_start_, anim_frame_end_));
245 frame_container_->SetBounds(frame_position);
247 gfx::Path path;
248 gfx::Rect contents_rect = GetContentsRectangle();
249 html_container_->SetBounds(contents_rect);
250 GetContentsMask(contents_rect, &path);
251 html_container_->SetShape(path.CreateNativeRegion());
253 html_contents_->SetPreferredSize(contents_rect.size());
254 content::RenderWidgetHostView* view =
255 html_contents_->web_contents()->GetRenderWidgetHostView();
256 if (view)
257 view->SetSize(contents_rect.size());
260 gfx::Rect BalloonViewImpl::GetCloseButtonBounds() const {
261 gfx::Rect bounds(GetContentsBounds());
262 bounds.set_height(GetShelfHeight());
263 const gfx::Size& pref_size(close_button_->GetPreferredSize());
264 bounds.Inset(bounds.width() - kShelfMargin - pref_size.width(), 0,
265 kShelfMargin, 0);
266 bounds.ClampToCenteredSize(pref_size);
267 return bounds;
270 gfx::Rect BalloonViewImpl::GetOptionsButtonBounds() const {
271 gfx::Rect bounds(GetContentsBounds());
272 bounds.set_height(GetShelfHeight());
273 const gfx::Size& pref_size(options_menu_button_->GetPreferredSize());
274 bounds.set_x(GetCloseButtonBounds().x() - kOptionsDismissSpacing -
275 pref_size.width());
276 bounds.set_width(pref_size.width());
277 bounds.ClampToCenteredSize(pref_size);
278 return bounds;
281 gfx::Rect BalloonViewImpl::GetLabelBounds() const {
282 gfx::Rect bounds(GetContentsBounds());
283 bounds.set_height(GetShelfHeight());
284 gfx::Size pref_size(source_label_->GetPreferredSize());
285 bounds.Inset(kLabelLeftMargin, 0, bounds.width() -
286 GetOptionsButtonBounds().x() + kLabelOptionsSpacing, 0);
287 pref_size.set_width(bounds.width());
288 bounds.ClampToCenteredSize(pref_size);
289 return bounds;
292 void BalloonViewImpl::Show(Balloon* balloon) {
293 if (closed_)
294 return;
296 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
298 balloon_ = balloon;
300 const base::string16 source_label_text = l10n_util::GetStringFUTF16(
301 IDS_NOTIFICATION_BALLOON_SOURCE_LABEL,
302 balloon->notification().display_source());
304 source_label_ = new views::Label(source_label_text);
305 AddChildView(source_label_);
306 options_menu_button_ =
307 new views::MenuButton(NULL, base::string16(), this, false);
308 AddChildView(options_menu_button_);
309 #if defined(OS_CHROMEOS)
310 // Disable and hide the options menu on ChromeOS. This is a short term fix
311 // for a crash (long term we're redesigning notifications).
312 options_menu_button_->SetEnabled(false);
313 options_menu_button_->SetVisible(false);
314 #endif
315 close_button_ = new views::ImageButton(this);
316 close_button_->SetTooltipText(l10n_util::GetStringUTF16(
317 IDS_NOTIFICATION_BALLOON_DISMISS_LABEL));
318 AddChildView(close_button_);
320 // We have to create two windows: one for the contents and one for the
321 // frame. Why?
322 // * The contents is an html window which cannot be a
323 // layered window (because it may have child windows for instance).
324 // * The frame is a layered window so that we can have nicely rounded
325 // corners using alpha blending (and we may do other alpha blending
326 // effects).
327 // Unfortunately, layered windows cannot have child windows. (Well, they can
328 // but the child windows don't render).
330 // We carefully keep these two windows in sync to present the illusion of
331 // one window to the user.
333 // We don't let the OS manage the RTL layout of these widgets, because
334 // this code is already taking care of correctly reversing the layout.
335 #if defined(OS_CHROMEOS) && defined(USE_AURA)
336 html_contents_.reset(new chromeos::BalloonViewHost(balloon));
337 #else
338 html_contents_.reset(new BalloonViewHost(balloon));
339 #endif
340 html_contents_->SetPreferredSize(gfx::Size(10000, 10000));
341 if (enable_web_ui_)
342 html_contents_->EnableWebUI();
344 html_container_ = new views::Widget;
345 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
346 html_container_->Init(params);
347 html_container_->SetContentsView(html_contents_->view());
349 frame_container_ = new views::Widget;
350 params.delegate = this;
351 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
352 params.bounds = GetBoundsForFrameContainer();
353 frame_container_->Init(params);
354 frame_container_->SetContentsView(this);
355 frame_container_->StackAboveWidget(html_container_);
357 // GetContentsRectangle() is calculated relative to |frame_container_|. Make
358 // sure |frame_container_| has bounds before we ask for
359 // GetContentsRectangle().
360 html_container_->SetBounds(GetContentsRectangle());
362 // SetAlwaysOnTop should be called after StackAboveWidget because otherwise
363 // the top-most flag will be removed.
364 html_container_->SetAlwaysOnTop(true);
365 frame_container_->SetAlwaysOnTop(true);
367 close_button_->SetImage(views::CustomButton::STATE_NORMAL,
368 rb.GetImageSkiaNamed(IDR_CLOSE_1));
369 close_button_->SetImage(views::CustomButton::STATE_HOVERED,
370 rb.GetImageSkiaNamed(IDR_CLOSE_1_H));
371 close_button_->SetImage(views::CustomButton::STATE_PRESSED,
372 rb.GetImageSkiaNamed(IDR_CLOSE_1_P));
373 close_button_->SetBoundsRect(GetCloseButtonBounds());
374 close_button_->SetBackground(SK_ColorBLACK,
375 rb.GetImageSkiaNamed(IDR_CLOSE_1),
376 rb.GetImageSkiaNamed(IDR_CLOSE_1_MASK));
378 options_menu_button_->SetIcon(*rb.GetImageSkiaNamed(IDR_BALLOON_WRENCH));
379 options_menu_button_->SetHoverIcon(
380 *rb.GetImageSkiaNamed(IDR_BALLOON_WRENCH_H));
381 options_menu_button_->SetPushedIcon(*rb.GetImageSkiaNamed(
382 IDR_BALLOON_WRENCH_P));
383 options_menu_button_->set_alignment(views::TextButton::ALIGN_CENTER);
384 options_menu_button_->set_border(NULL);
385 options_menu_button_->SetBoundsRect(GetOptionsButtonBounds());
387 source_label_->SetFontList(rb.GetFontList(ui::ResourceBundle::SmallFont));
388 source_label_->SetBackgroundColor(kControlBarBackgroundColor);
389 source_label_->SetEnabledColor(kControlBarTextColor);
390 source_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
391 source_label_->SetBoundsRect(GetLabelBounds());
393 SizeContentsWindow();
394 html_container_->Show();
395 frame_container_->Show();
397 notification_registrar_.Add(
398 this, chrome::NOTIFICATION_NOTIFY_BALLOON_DISCONNECTED,
399 content::Source<Balloon>(balloon));
402 void BalloonViewImpl::CreateOptionsMenu() {
403 if (options_menu_model_.get())
404 return;
405 options_menu_model_.reset(new NotificationOptionsMenuModel(balloon_));
408 void BalloonViewImpl::GetContentsMask(const gfx::Rect& rect,
409 gfx::Path* path) const {
410 // This rounds the corners, and we also cut out a circle for the close
411 // button, since we can't guarantee the ordering of two top-most windows.
412 SkScalar radius = SkIntToScalar(views::BubbleBorder::GetCornerRadius());
413 SkScalar spline_radius = radius -
414 SkScalarMul(radius, (SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3);
415 SkScalar left = SkIntToScalar(0);
416 SkScalar top = SkIntToScalar(0);
417 SkScalar right = SkIntToScalar(rect.width());
418 SkScalar bottom = SkIntToScalar(rect.height());
420 path->moveTo(left, top);
421 path->lineTo(right, top);
422 path->lineTo(right, bottom - radius);
423 path->cubicTo(right, bottom - spline_radius,
424 right - spline_radius, bottom,
425 right - radius, bottom);
426 path->lineTo(left + radius, bottom);
427 path->cubicTo(left + spline_radius, bottom,
428 left, bottom - spline_radius,
429 left, bottom - radius);
430 path->lineTo(left, top);
431 path->close();
434 void BalloonViewImpl::GetFrameMask(const gfx::Rect& rect,
435 gfx::Path* path) const {
436 SkScalar radius = SkIntToScalar(views::BubbleBorder::GetCornerRadius());
437 SkScalar spline_radius = radius -
438 SkScalarMul(radius, (SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3);
439 SkScalar left = SkIntToScalar(rect.x());
440 SkScalar top = SkIntToScalar(rect.y());
441 SkScalar right = SkIntToScalar(rect.right());
442 SkScalar bottom = SkIntToScalar(rect.bottom());
444 path->moveTo(left, bottom);
445 path->lineTo(left, top + radius);
446 path->cubicTo(left, top + spline_radius,
447 left + spline_radius, top,
448 left + radius, top);
449 path->lineTo(right - radius, top);
450 path->cubicTo(right - spline_radius, top,
451 right, top + spline_radius,
452 right, top + radius);
453 path->lineTo(right, bottom);
454 path->lineTo(left, bottom);
455 path->close();
458 gfx::Point BalloonViewImpl::GetContentsOffset() const {
459 return gfx::Point(kLeftShadowWidth + kLeftMargin,
460 kTopShadowWidth + kTopMargin);
463 gfx::Rect BalloonViewImpl::GetBoundsForFrameContainer() const {
464 return gfx::Rect(balloon_->GetPosition().x(), balloon_->GetPosition().y(),
465 GetTotalWidth(), GetTotalHeight());
468 int BalloonViewImpl::GetShelfHeight() const {
469 // TODO(johnnyg): add scaling here.
470 int max_button_height = std::max(std::max(
471 close_button_->GetPreferredSize().height(),
472 options_menu_button_->GetPreferredSize().height()),
473 source_label_->GetPreferredSize().height());
474 return max_button_height + kShelfMargin * 2;
477 int BalloonViewImpl::GetBalloonFrameHeight() const {
478 return GetTotalHeight() - GetShelfHeight();
481 int BalloonViewImpl::GetTotalWidth() const {
482 return balloon_->content_size().width() +
483 kLeftMargin + kRightMargin + kLeftShadowWidth + kRightShadowWidth;
486 int BalloonViewImpl::GetTotalHeight() const {
487 return balloon_->content_size().height() +
488 kTopMargin + kBottomMargin + kTopShadowWidth + kBottomShadowWidth +
489 GetShelfHeight();
492 gfx::Rect BalloonViewImpl::GetContentsRectangle() const {
493 if (!frame_container_)
494 return gfx::Rect();
496 gfx::Size content_size = balloon_->content_size();
497 gfx::Point offset = GetContentsOffset();
498 gfx::Rect frame_rect = frame_container_->GetWindowBoundsInScreen();
499 return gfx::Rect(frame_rect.x() + offset.x(),
500 frame_rect.y() + GetShelfHeight() + offset.y(),
501 content_size.width(),
502 content_size.height());
505 void BalloonViewImpl::OnPaint(gfx::Canvas* canvas) {
506 DCHECK(canvas);
507 // Paint the menu bar area white, with proper rounded corners.
508 gfx::Path path;
509 gfx::Rect rect = GetContentsBounds();
510 rect.set_height(GetShelfHeight());
511 GetFrameMask(rect, &path);
513 SkPaint paint;
514 paint.setAntiAlias(true);
515 paint.setColor(kControlBarBackgroundColor);
516 canvas->DrawPath(path, paint);
518 // Draw a 1-pixel gray line between the content and the menu bar.
519 int line_width = GetTotalWidth() - kLeftMargin - kRightMargin;
520 canvas->FillRect(gfx::Rect(kLeftMargin, rect.bottom(), line_width, 1),
521 kControlBarSeparatorLineColor);
522 View::OnPaint(canvas);
523 OnPaintBorder(canvas);
526 void BalloonViewImpl::OnBoundsChanged(const gfx::Rect& previous_bounds) {
527 SizeContentsWindow();
530 void BalloonViewImpl::Observe(int type,
531 const content::NotificationSource& source,
532 const content::NotificationDetails& details) {
533 if (type != chrome::NOTIFICATION_NOTIFY_BALLOON_DISCONNECTED) {
534 NOTREACHED();
535 return;
538 // If the renderer process attached to this balloon is disconnected
539 // (e.g., because of a crash), we want to close the balloon.
540 notification_registrar_.Remove(
541 this, chrome::NOTIFICATION_NOTIFY_BALLOON_DISCONNECTED,
542 content::Source<Balloon>(balloon_));
543 Close(false);