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 "ui/message_center/views/notification_view.h"
7 #include "base/command_line.h"
8 #include "base/stl_util.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "components/url_formatter/elide_url.h"
12 #include "third_party/skia/include/core/SkPaint.h"
13 #include "third_party/skia/include/core/SkPath.h"
14 #include "ui/base/cursor/cursor.h"
15 #include "ui/base/layout.h"
16 #include "ui/gfx/canvas.h"
17 #include "ui/gfx/geometry/size.h"
18 #include "ui/gfx/skia_util.h"
19 #include "ui/gfx/text_elider.h"
20 #include "ui/message_center/message_center.h"
21 #include "ui/message_center/message_center_style.h"
22 #include "ui/message_center/notification.h"
23 #include "ui/message_center/notification_types.h"
24 #include "ui/message_center/views/bounded_label.h"
25 #include "ui/message_center/views/constants.h"
26 #include "ui/message_center/views/message_center_controller.h"
27 #include "ui/message_center/views/notification_button.h"
28 #include "ui/message_center/views/padded_button.h"
29 #include "ui/message_center/views/proportional_image_view.h"
30 #include "ui/native_theme/native_theme.h"
31 #include "ui/resources/grit/ui_resources.h"
32 #include "ui/strings/grit/ui_strings.h"
33 #include "ui/views/background.h"
34 #include "ui/views/border.h"
35 #include "ui/views/controls/button/image_button.h"
36 #include "ui/views/controls/image_view.h"
37 #include "ui/views/controls/label.h"
38 #include "ui/views/controls/progress_bar.h"
39 #include "ui/views/layout/box_layout.h"
40 #include "ui/views/layout/fill_layout.h"
41 #include "ui/views/native_cursor.h"
42 #include "ui/views/painter.h"
43 #include "ui/views/view_targeter.h"
44 #include "ui/views/widget/widget.h"
50 const int kProgressBarWidth
= message_center::kNotificationWidth
-
51 message_center::kTextLeftPadding
- message_center::kTextRightPadding
;
52 const int kProgressBarBottomPadding
= 0;
55 scoped_ptr
<views::Border
> MakeEmptyBorder(int top
,
59 return views::Border::CreateEmptyBorder(top
, left
, bottom
, right
);
63 scoped_ptr
<views::Border
> MakeTextBorder(int padding
, int top
, int bottom
) {
64 // Split the padding between the top and the bottom, then add the extra space.
65 return MakeEmptyBorder(padding
/ 2 + top
,
66 message_center::kTextLeftPadding
,
67 (padding
+ 1) / 2 + bottom
,
68 message_center::kTextRightPadding
);
72 scoped_ptr
<views::Border
> MakeProgressBarBorder(int top
, int bottom
) {
73 return MakeEmptyBorder(top
,
74 message_center::kTextLeftPadding
,
76 message_center::kTextRightPadding
);
80 scoped_ptr
<views::Border
> MakeSeparatorBorder(int top
,
83 return views::Border::CreateSolidSidedBorder(top
, left
, 0, 0, color
);
87 // Return true if and only if the image is null or has alpha.
88 bool HasAlpha(gfx::ImageSkia
& image
, views::Widget
* widget
) {
89 // Determine which bitmap to use.
92 factor
= ui::GetScaleFactorForNativeView(widget
->GetNativeView());
94 // Extract that bitmap's alpha and look for a non-opaque pixel there.
95 SkBitmap bitmap
= image
.GetRepresentation(factor
).sk_bitmap();
96 if (!bitmap
.isNull()) {
98 bitmap
.extractAlpha(&alpha
);
99 for (int y
= 0; y
< bitmap
.height(); ++y
) {
100 for (int x
= 0; x
< bitmap
.width(); ++x
) {
101 if (alpha
.getColor(x
, y
) != SK_ColorBLACK
) {
108 // If no opaque pixel was found, return false unless the bitmap is empty.
109 return bitmap
.isNull();
112 // ItemView ////////////////////////////////////////////////////////////////////
114 // ItemViews are responsible for drawing each list notification item's title and
115 // message next to each other within a single column.
116 class ItemView
: public views::View
{
118 ItemView(const message_center::NotificationItem
& item
);
119 ~ItemView() override
;
121 // Overridden from views::View:
122 void SetVisible(bool visible
) override
;
125 DISALLOW_COPY_AND_ASSIGN(ItemView
);
128 ItemView::ItemView(const message_center::NotificationItem
& item
) {
129 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal
,
130 0, 0, message_center::kItemTitleToMessagePadding
));
132 views::Label
* title
= new views::Label(item
.title
);
133 title
->set_collapse_when_hidden(true);
134 title
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
135 title
->SetEnabledColor(message_center::kRegularTextColor
);
136 title
->SetBackgroundColor(message_center::kRegularTextBackgroundColor
);
139 views::Label
* message
= new views::Label(item
.message
);
140 message
->set_collapse_when_hidden(true);
141 message
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
142 message
->SetEnabledColor(message_center::kDimTextColor
);
143 message
->SetBackgroundColor(message_center::kDimTextBackgroundColor
);
144 AddChildView(message
);
146 PreferredSizeChanged();
150 ItemView::~ItemView() {
153 void ItemView::SetVisible(bool visible
) {
154 views::View::SetVisible(visible
);
155 for (int i
= 0; i
< child_count(); ++i
)
156 child_at(i
)->SetVisible(visible
);
159 // NotificationProgressBar /////////////////////////////////////////////////////
161 class NotificationProgressBar
: public views::ProgressBar
{
163 NotificationProgressBar();
164 ~NotificationProgressBar() override
;
167 // Overriden from View
168 gfx::Size
GetPreferredSize() const override
;
169 void OnPaint(gfx::Canvas
* canvas
) override
;
171 DISALLOW_COPY_AND_ASSIGN(NotificationProgressBar
);
174 NotificationProgressBar::NotificationProgressBar() {
177 NotificationProgressBar::~NotificationProgressBar() {
180 gfx::Size
NotificationProgressBar::GetPreferredSize() const {
181 gfx::Size
pref_size(kProgressBarWidth
, message_center::kProgressBarThickness
);
182 gfx::Insets insets
= GetInsets();
183 pref_size
.Enlarge(insets
.width(), insets
.height());
187 void NotificationProgressBar::OnPaint(gfx::Canvas
* canvas
) {
188 gfx::Rect content_bounds
= GetContentsBounds();
191 SkPath background_path
;
192 background_path
.addRoundRect(gfx::RectToSkRect(content_bounds
),
193 message_center::kProgressBarCornerRadius
,
194 message_center::kProgressBarCornerRadius
);
195 SkPaint background_paint
;
196 background_paint
.setStyle(SkPaint::kFill_Style
);
197 background_paint
.setFlags(SkPaint::kAntiAlias_Flag
);
198 background_paint
.setColor(message_center::kProgressBarBackgroundColor
);
199 canvas
->DrawPath(background_path
, background_paint
);
202 const int slice_width
=
203 static_cast<int>(content_bounds
.width() * GetNormalizedValue() + 0.5);
207 gfx::Rect slice_bounds
= content_bounds
;
208 slice_bounds
.set_width(slice_width
);
210 slice_path
.addRoundRect(gfx::RectToSkRect(slice_bounds
),
211 message_center::kProgressBarCornerRadius
,
212 message_center::kProgressBarCornerRadius
);
214 slice_paint
.setStyle(SkPaint::kFill_Style
);
215 slice_paint
.setFlags(SkPaint::kAntiAlias_Flag
);
216 slice_paint
.setColor(message_center::kProgressBarSliceColor
);
217 canvas
->DrawPath(slice_path
, slice_paint
);
222 namespace message_center
{
224 // NotificationView ////////////////////////////////////////////////////////////
227 NotificationView
* NotificationView::Create(MessageCenterController
* controller
,
228 const Notification
& notification
,
230 switch (notification
.type()) {
231 case NOTIFICATION_TYPE_BASE_FORMAT
:
232 case NOTIFICATION_TYPE_IMAGE
:
233 case NOTIFICATION_TYPE_MULTIPLE
:
234 case NOTIFICATION_TYPE_SIMPLE
:
235 case NOTIFICATION_TYPE_PROGRESS
:
238 // If the caller asks for an unrecognized kind of view (entirely possible
239 // if an application is running on an older version of this code that
240 // doesn't have the requested kind of notification template), we'll fall
241 // back to a notification instance that will provide at least basic
243 LOG(WARNING
) << "Unable to fulfill request for unrecognized "
244 << "notification type " << notification
.type() << ". "
245 << "Falling back to simple notification type.";
248 // Currently all roads lead to the generic NotificationView.
249 NotificationView
* notification_view
=
250 new NotificationView(controller
, notification
);
252 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
253 // Don't create shadows for notification toasts on linux wih aura.
255 return notification_view
;
258 notification_view
->CreateShadowBorder();
259 return notification_view
;
262 views::View
* NotificationView::TargetForRect(views::View
* root
,
263 const gfx::Rect
& rect
) {
264 CHECK_EQ(root
, this);
266 // TODO(tdanderson): Modify this function to support rect-based event
267 // targeting. Using the center point of |rect| preserves this function's
268 // expected behavior for the time being.
269 gfx::Point point
= rect
.CenterPoint();
271 // Want to return this for underlying views, otherwise GetCursor is not
272 // called. But buttons are exceptions, they'll have their own event handlings.
273 std::vector
<views::View
*> buttons(action_buttons_
.begin(),
274 action_buttons_
.end());
275 buttons
.push_back(close_button());
277 for (size_t i
= 0; i
< buttons
.size(); ++i
) {
278 gfx::Point point_in_child
= point
;
279 ConvertPointToTarget(this, buttons
[i
], &point_in_child
);
280 if (buttons
[i
]->HitTestPoint(point_in_child
))
281 return buttons
[i
]->GetEventHandlerForPoint(point_in_child
);
287 void NotificationView::CreateOrUpdateViews(const Notification
& notification
) {
288 CreateOrUpdateTitleView(notification
);
289 CreateOrUpdateMessageView(notification
);
290 CreateOrUpdateProgressBarView(notification
);
291 CreateOrUpdateListItemViews(notification
);
292 CreateOrUpdateIconView(notification
);
293 CreateOrUpdateImageView(notification
);
294 CreateOrUpdateContextMessageView(notification
);
295 CreateOrUpdateActionButtonViews(notification
);
298 void NotificationView::SetAccessibleName(const Notification
& notification
) {
299 std::vector
<base::string16
> accessible_lines
;
300 accessible_lines
.push_back(notification
.title());
301 accessible_lines
.push_back(notification
.message());
302 accessible_lines
.push_back(notification
.context_message());
303 std::vector
<NotificationItem
> items
= notification
.items();
304 for (size_t i
= 0; i
< items
.size() && i
< kNotificationMaximumItems
; ++i
) {
305 accessible_lines
.push_back(items
[i
].title
+ base::ASCIIToUTF16(" ") +
309 base::JoinString(accessible_lines
, base::ASCIIToUTF16("\n")));
312 NotificationView::NotificationView(MessageCenterController
* controller
,
313 const Notification
& notification
)
316 notification
.notifier_id(),
317 notification
.small_image().AsImageSkia(),
318 notification
.display_source()),
319 controller_(controller
),
320 clickable_(notification
.clickable()),
324 context_message_view_(NULL
),
327 image_container_(NULL
),
329 progress_bar_view_(NULL
) {
330 // Create the top_view_, which collects into a vertical box all content
331 // at the top of the notification (to the right of the icon) except for the
333 top_view_
= new views::View();
334 top_view_
->SetLayoutManager(
335 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
336 top_view_
->SetBorder(
337 MakeEmptyBorder(kTextTopPadding
- 8, 0, kTextBottomPadding
- 5, 0));
338 AddChildView(top_view_
);
339 // Create the bottom_view_, which collects into a vertical box all content
340 // below the notification icon.
341 bottom_view_
= new views::View();
342 bottom_view_
->SetLayoutManager(
343 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
344 AddChildView(bottom_view_
);
346 CreateOrUpdateViews(notification
);
348 // Put together the different content and control views. Layering those allows
349 // for proper layout logic and it also allows the close button and small
350 // image to overlap the content as needed to provide large enough click and
351 // touch areas (<http://crbug.com/168822> and <http://crbug.com/168856>).
352 AddChildView(small_image());
353 AddChildView(close_button());
354 SetAccessibleName(notification
);
357 scoped_ptr
<views::ViewTargeter
>(new views::ViewTargeter(this)));
360 NotificationView::~NotificationView() {
363 gfx::Size
NotificationView::GetPreferredSize() const {
364 int top_width
= top_view_
->GetPreferredSize().width() +
365 icon_view_
->GetPreferredSize().width();
366 int bottom_width
= bottom_view_
->GetPreferredSize().width();
367 int preferred_width
= std::max(top_width
, bottom_width
) + GetInsets().width();
368 return gfx::Size(preferred_width
, GetHeightForWidth(preferred_width
));
371 int NotificationView::GetHeightForWidth(int width
) const {
372 // Get the height assuming no line limit changes.
373 int content_width
= width
- GetInsets().width();
374 int top_height
= top_view_
->GetHeightForWidth(content_width
);
375 int bottom_height
= bottom_view_
->GetHeightForWidth(content_width
);
377 // <http://crbug.com/230448> Fix: Adjust the height when the message_view's
378 // line limit would be different for the specified width than it currently is.
379 // TODO(dharcourt): Avoid BoxLayout and directly compute the correct height.
383 title_lines
= title_view_
->GetLinesForWidthAndLimit(width
,
386 int used_limit
= message_view_
->GetLineLimit();
387 int correct_limit
= GetMessageLineLimit(title_lines
, width
);
388 if (used_limit
!= correct_limit
) {
389 top_height
-= GetMessageHeight(content_width
, used_limit
);
390 top_height
+= GetMessageHeight(content_width
, correct_limit
);
394 int content_height
= std::max(top_height
, kIconSize
) + bottom_height
;
396 // Adjust the height to make sure there is at least 16px of space below the
397 // icon if there is any space there (<http://crbug.com/232966>).
398 if (content_height
> kIconSize
)
399 content_height
= std::max(content_height
,
400 kIconSize
+ message_center::kIconBottomPadding
);
402 return content_height
+ GetInsets().height();
405 void NotificationView::Layout() {
406 MessageView::Layout();
407 gfx::Insets insets
= GetInsets();
408 int content_width
= width() - insets
.width();
410 // Before any resizing, set or adjust the number of message lines.
414 title_view_
->GetLinesForWidthAndLimit(width(), kMaxTitleLines
);
417 message_view_
->SetLineLimit(GetMessageLineLimit(title_lines
, width()));
420 int top_height
= top_view_
->GetHeightForWidth(content_width
);
421 top_view_
->SetBounds(insets
.left(), insets
.top(), content_width
, top_height
);
424 icon_view_
->SetBounds(insets
.left(), insets
.top(), kIconSize
, kIconSize
);
427 int bottom_y
= insets
.top() + std::max(top_height
, kIconSize
);
428 int bottom_height
= bottom_view_
->GetHeightForWidth(content_width
);
429 bottom_view_
->SetBounds(insets
.left(), bottom_y
,
430 content_width
, bottom_height
);
433 void NotificationView::OnFocus() {
434 MessageView::OnFocus();
435 ScrollRectToVisible(GetLocalBounds());
438 void NotificationView::ScrollRectToVisible(const gfx::Rect
& rect
) {
439 // Notification want to show the whole notification when a part of it (like
440 // a button) gets focused.
441 views::View::ScrollRectToVisible(GetLocalBounds());
444 gfx::NativeCursor
NotificationView::GetCursor(const ui::MouseEvent
& event
) {
445 if (!clickable_
|| !controller_
->HasClickedListener(notification_id()))
446 return views::View::GetCursor(event
);
448 return views::GetNativeHandCursor();
451 void NotificationView::UpdateWithNotification(
452 const Notification
& notification
) {
453 MessageView::UpdateWithNotification(notification
);
455 CreateOrUpdateViews(notification
);
456 SetAccessibleName(notification
);
461 void NotificationView::ButtonPressed(views::Button
* sender
,
462 const ui::Event
& event
) {
463 // Certain operations can cause |this| to be destructed, so copy the members
464 // we send to other parts of the code.
465 // TODO(dewittj): Remove this hack.
466 std::string
id(notification_id());
467 // See if the button pressed was an action button.
468 for (size_t i
= 0; i
< action_buttons_
.size(); ++i
) {
469 if (sender
== action_buttons_
[i
]) {
470 controller_
->ClickOnNotificationButton(id
, i
);
475 // Let the superclass handled anything other than action buttons.
476 // Warning: This may cause the NotificationView itself to be deleted,
477 // so don't do anything afterwards.
478 MessageView::ButtonPressed(sender
, event
);
481 void NotificationView::ClickOnNotification(const std::string
& notification_id
) {
482 controller_
->ClickOnNotification(notification_id
);
485 void NotificationView::RemoveNotification(const std::string
& notification_id
,
487 controller_
->RemoveNotification(notification_id
, by_user
);
490 void NotificationView::CreateOrUpdateTitleView(
491 const Notification
& notification
) {
492 if (notification
.title().empty()) {
494 // Deletion will also remove |title_view_| from its parent.
501 DCHECK(top_view_
!= NULL
);
503 const gfx::FontList
& font_list
=
504 views::Label().font_list().DeriveWithSizeDelta(2);
506 int title_character_limit
=
507 kNotificationWidth
* kMaxTitleLines
/ kMinPixelsPerTitleCharacter
;
509 base::string16 title
= gfx::TruncateString(notification
.title(),
510 title_character_limit
,
513 int padding
= kTitleLineHeight
- font_list
.GetHeight();
515 title_view_
= new BoundedLabel(title
, font_list
);
516 title_view_
->SetLineHeight(kTitleLineHeight
);
517 title_view_
->SetLineLimit(kMaxTitleLines
);
518 title_view_
->SetColors(message_center::kRegularTextColor
,
519 kRegularTextBackgroundColor
);
520 title_view_
->SetBorder(MakeTextBorder(padding
, 3, 0));
521 top_view_
->AddChildView(title_view_
);
523 title_view_
->SetText(title
);
527 void NotificationView::CreateOrUpdateMessageView(
528 const Notification
& notification
) {
529 if (notification
.message().empty()) {
531 // Deletion will also remove |message_view_| from its parent.
532 delete message_view_
;
533 message_view_
= NULL
;
538 DCHECK(top_view_
!= NULL
);
540 base::string16 text
= gfx::TruncateString(notification
.message(),
541 kMessageCharacterLimit
,
543 if (!message_view_
) {
544 int padding
= kMessageLineHeight
- views::Label().font_list().GetHeight();
545 message_view_
= new BoundedLabel(text
);
546 message_view_
->SetLineHeight(kMessageLineHeight
);
547 message_view_
->SetColors(message_center::kRegularTextColor
,
548 kDimTextBackgroundColor
);
549 message_view_
->SetBorder(MakeTextBorder(padding
, 4, 0));
550 top_view_
->AddChildView(message_view_
);
552 message_view_
->SetText(text
);
555 message_view_
->SetVisible(!notification
.items().size());
558 base::string16
NotificationView::FormatContextMessage(
559 const Notification
& notification
) const {
560 if (notification
.UseOriginAsContextMessage()) {
561 const GURL url
= notification
.origin_url();
562 DCHECK(url
.is_valid());
563 return url_formatter::ElideHost(url
, views::Label().font_list(),
564 kContextMessageViewWidth
);
567 return gfx::TruncateString(notification
.context_message(),
568 kContextMessageCharacterLimit
, gfx::WORD_BREAK
);
571 void NotificationView::CreateOrUpdateContextMessageView(
572 const Notification
& notification
) {
573 if (notification
.context_message().empty() &&
574 !notification
.UseOriginAsContextMessage()) {
575 if (context_message_view_
) {
576 // Deletion will also remove |context_message_view_| from its parent.
577 delete context_message_view_
;
578 context_message_view_
= NULL
;
583 DCHECK(top_view_
!= NULL
);
585 base::string16 message
= FormatContextMessage(notification
);
587 if (!context_message_view_
) {
588 int padding
= kMessageLineHeight
- views::Label().font_list().GetHeight();
589 context_message_view_
= new BoundedLabel(message
);
590 context_message_view_
->SetLineLimit(
591 message_center::kContextMessageLineLimit
);
592 context_message_view_
->SetLineHeight(kMessageLineHeight
);
593 context_message_view_
->SetColors(message_center::kDimTextColor
,
594 kContextTextBackgroundColor
);
595 context_message_view_
->SetBorder(MakeTextBorder(padding
, 4, 0));
596 top_view_
->AddChildView(context_message_view_
);
598 context_message_view_
->SetText(message
);
602 void NotificationView::CreateOrUpdateProgressBarView(
603 const Notification
& notification
) {
604 if (notification
.type() != NOTIFICATION_TYPE_PROGRESS
) {
605 if (progress_bar_view_
) {
606 // Deletion will also remove |progress_bar_view_| from its parent.
607 delete progress_bar_view_
;
608 progress_bar_view_
= NULL
;
613 DCHECK(top_view_
!= NULL
);
615 if (!progress_bar_view_
) {
616 progress_bar_view_
= new NotificationProgressBar();
617 progress_bar_view_
->SetBorder(MakeProgressBarBorder(
618 message_center::kProgressBarTopPadding
, kProgressBarBottomPadding
));
619 top_view_
->AddChildView(progress_bar_view_
);
622 progress_bar_view_
->SetValue(notification
.progress() / 100.0);
623 progress_bar_view_
->SetVisible(!notification
.items().size());
626 void NotificationView::CreateOrUpdateListItemViews(
627 const Notification
& notification
) {
628 for (size_t i
= 0; i
< item_views_
.size(); ++i
)
629 delete item_views_
[i
];
632 int padding
= kMessageLineHeight
- views::Label().font_list().GetHeight();
633 std::vector
<NotificationItem
> items
= notification
.items();
635 if (items
.size() == 0)
639 for (size_t i
= 0; i
< items
.size() && i
< kNotificationMaximumItems
; ++i
) {
640 ItemView
* item_view
= new ItemView(items
[i
]);
641 item_view
->SetBorder(MakeTextBorder(padding
, i
? 0 : 4, 0));
642 item_views_
.push_back(item_view
);
643 top_view_
->AddChildView(item_view
);
647 void NotificationView::CreateOrUpdateIconView(
648 const Notification
& notification
) {
650 icon_view_
= new ProportionalImageView(gfx::Size(kIconSize
, kIconSize
));
651 AddChildView(icon_view_
);
654 gfx::ImageSkia icon
= notification
.icon().AsImageSkia();
655 if (notification
.adjust_icon()) {
656 icon_view_
->set_background(
657 views::Background::CreateSolidBackground(kIconBackgroundColor
));
658 gfx::Size max_image_size
=
659 notification
.type() == NOTIFICATION_TYPE_SIMPLE
&&
660 (icon
.width() < kIconSize
|| icon
.height() < kIconSize
||
661 HasAlpha(icon
, GetWidget()))
662 ? gfx::Size(kLegacyIconSize
, kLegacyIconSize
)
663 : gfx::Size(kIconSize
, kIconSize
);
664 icon_view_
->SetImage(icon
, max_image_size
);
666 icon_view_
->SetImage(icon
, icon
.size());
667 icon_view_
->set_background(nullptr);
671 void NotificationView::CreateOrUpdateImageView(
672 const Notification
& notification
) {
673 // |image_view_| is the view representing the area covered by the
674 // notification's image, including background and border. Its size can be
675 // specified in advance and images will be scaled to fit including a border if
677 if (notification
.image().IsEmpty()) {
678 delete image_container_
;
679 image_container_
= NULL
;
684 gfx::Size
ideal_size(kNotificationPreferredImageWidth
,
685 kNotificationPreferredImageHeight
);
687 if (!image_container_
) {
688 DCHECK(!image_view_
);
689 DCHECK(bottom_view_
);
690 DCHECK_EQ(this, bottom_view_
->parent());
692 image_container_
= new views::View();
693 image_container_
->SetLayoutManager(new views::FillLayout());
694 image_container_
->set_background(views::Background::CreateSolidBackground(
695 message_center::kImageBackgroundColor
));
697 image_view_
= new message_center::ProportionalImageView(ideal_size
);
698 image_container_
->AddChildView(image_view_
);
699 bottom_view_
->AddChildViewAt(image_container_
, 0);
703 image_view_
->SetImage(notification
.image().AsImageSkia(), ideal_size
);
705 gfx::Size scaled_size
= message_center::GetImageSizeForContainerSize(
706 ideal_size
, notification
.image().Size());
707 image_view_
->SetBorder(ideal_size
!= scaled_size
708 ? views::Border::CreateSolidBorder(
709 message_center::kNotificationImageBorderSize
,
714 void NotificationView::CreateOrUpdateActionButtonViews(
715 const Notification
& notification
) {
716 std::vector
<ButtonInfo
> buttons
= notification
.buttons();
717 bool new_buttons
= action_buttons_
.size() != buttons
.size();
719 if (new_buttons
|| buttons
.size() == 0) {
720 // STLDeleteElements also clears the container.
721 STLDeleteElements(&separators_
);
722 STLDeleteElements(&action_buttons_
);
725 DCHECK(bottom_view_
);
726 DCHECK_EQ(this, bottom_view_
->parent());
728 for (size_t i
= 0; i
< buttons
.size(); ++i
) {
729 ButtonInfo button_info
= buttons
[i
];
731 views::View
* separator
= new views::ImageView();
732 separator
->SetBorder(MakeSeparatorBorder(1, 0, kButtonSeparatorColor
));
733 separators_
.push_back(separator
);
734 bottom_view_
->AddChildView(separator
);
735 NotificationButton
* button
= new NotificationButton(this);
736 button
->SetTitle(button_info
.title
);
737 button
->SetIcon(button_info
.icon
.AsImageSkia());
738 action_buttons_
.push_back(button
);
739 bottom_view_
->AddChildView(button
);
741 action_buttons_
[i
]->SetTitle(button_info
.title
);
742 action_buttons_
[i
]->SetIcon(button_info
.icon
.AsImageSkia());
743 action_buttons_
[i
]->SchedulePaint();
744 action_buttons_
[i
]->Layout();
750 views::Widget
* widget
= GetWidget();
751 if (widget
!= NULL
) {
752 widget
->SetSize(widget
->GetContentsView()->GetPreferredSize());
753 GetWidget()->SynthesizeMouseMoveEvent();
758 int NotificationView::GetMessageLineLimit(int title_lines
, int width
) const {
759 // Image notifications require that the image must be kept flush against
760 // their icons, but we can allow more text if no image.
761 int effective_title_lines
= std::max(0, title_lines
- 1);
762 int line_reduction_from_title
= (image_view_
? 1 : 2) * effective_title_lines
;
764 // Title lines are counted as twice as big as message lines for the purpose
765 // of this calculation.
766 // The effect from the title reduction here should be:
767 // * 0 title lines: 5 max lines message.
768 // * 1 title line: 5 max lines message.
769 // * 2 title lines: 3 max lines message.
772 message_center::kMessageExpandedLineLimit
- line_reduction_from_title
);
775 int message_line_limit
= message_center::kMessageCollapsedLineLimit
;
777 // Subtract any lines taken by the context message.
778 if (context_message_view_
) {
779 message_line_limit
-= context_message_view_
->GetLinesForWidthAndLimit(
781 message_center::kContextMessageLineLimit
);
784 // The effect from the title reduction here should be:
785 // * 0 title lines: 2 max lines message + context message.
786 // * 1 title line: 2 max lines message + context message.
787 // * 2 title lines: 1 max lines message + context message.
789 std::max(0, message_line_limit
- line_reduction_from_title
);
791 return message_line_limit
;
794 int NotificationView::GetMessageHeight(int width
, int limit
) const {
795 return message_view_
?
796 message_view_
->GetSizeForWidthAndLines(width
, limit
).height() : 0;
799 } // namespace message_center