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 "ui/base/cursor/cursor.h"
12 #include "ui/base/layout.h"
13 #include "ui/gfx/canvas.h"
14 #include "ui/gfx/size.h"
15 #include "ui/gfx/skia_util.h"
16 #include "ui/gfx/text_elider.h"
17 #include "ui/message_center/message_center.h"
18 #include "ui/message_center/message_center_style.h"
19 #include "ui/message_center/notification.h"
20 #include "ui/message_center/notification_types.h"
21 #include "ui/message_center/views/bounded_label.h"
22 #include "ui/message_center/views/constants.h"
23 #include "ui/message_center/views/message_center_controller.h"
24 #include "ui/message_center/views/notification_button.h"
25 #include "ui/message_center/views/padded_button.h"
26 #include "ui/message_center/views/proportional_image_view.h"
27 #include "ui/native_theme/native_theme.h"
28 #include "ui/resources/grit/ui_resources.h"
29 #include "ui/strings/grit/ui_strings.h"
30 #include "ui/views/background.h"
31 #include "ui/views/border.h"
32 #include "ui/views/controls/button/image_button.h"
33 #include "ui/views/controls/image_view.h"
34 #include "ui/views/controls/label.h"
35 #include "ui/views/controls/progress_bar.h"
36 #include "ui/views/layout/box_layout.h"
37 #include "ui/views/layout/fill_layout.h"
38 #include "ui/views/native_cursor.h"
39 #include "ui/views/painter.h"
40 #include "ui/views/view_targeter.h"
41 #include "ui/views/widget/widget.h"
46 const int kProgressBarWidth
= message_center::kNotificationWidth
-
47 message_center::kTextLeftPadding
- message_center::kTextRightPadding
;
48 const int kProgressBarBottomPadding
= 0;
51 scoped_ptr
<views::Border
> MakeEmptyBorder(int top
,
55 return views::Border::CreateEmptyBorder(top
, left
, bottom
, right
);
59 scoped_ptr
<views::Border
> MakeTextBorder(int padding
, int top
, int bottom
) {
60 // Split the padding between the top and the bottom, then add the extra space.
61 return MakeEmptyBorder(padding
/ 2 + top
,
62 message_center::kTextLeftPadding
,
63 (padding
+ 1) / 2 + bottom
,
64 message_center::kTextRightPadding
);
68 scoped_ptr
<views::Border
> MakeProgressBarBorder(int top
, int bottom
) {
69 return MakeEmptyBorder(top
,
70 message_center::kTextLeftPadding
,
72 message_center::kTextRightPadding
);
76 scoped_ptr
<views::Border
> MakeSeparatorBorder(int top
,
79 return views::Border::CreateSolidSidedBorder(top
, left
, 0, 0, color
);
83 // Return true if and only if the image is null or has alpha.
84 bool HasAlpha(gfx::ImageSkia
& image
, views::Widget
* widget
) {
85 // Determine which bitmap to use.
88 factor
= ui::GetScaleFactorForNativeView(widget
->GetNativeView());
90 // Extract that bitmap's alpha and look for a non-opaque pixel there.
91 SkBitmap bitmap
= image
.GetRepresentation(factor
).sk_bitmap();
92 if (!bitmap
.isNull()) {
94 bitmap
.extractAlpha(&alpha
);
95 for (int y
= 0; y
< bitmap
.height(); ++y
) {
96 for (int x
= 0; x
< bitmap
.width(); ++x
) {
97 if (alpha
.getColor(x
, y
) != SK_ColorBLACK
) {
104 // If no opaque pixel was found, return false unless the bitmap is empty.
105 return bitmap
.isNull();
108 // ItemView ////////////////////////////////////////////////////////////////////
110 // ItemViews are responsible for drawing each list notification item's title and
111 // message next to each other within a single column.
112 class ItemView
: public views::View
{
114 ItemView(const message_center::NotificationItem
& item
);
115 ~ItemView() override
;
117 // Overridden from views::View:
118 void SetVisible(bool visible
) override
;
121 DISALLOW_COPY_AND_ASSIGN(ItemView
);
124 ItemView::ItemView(const message_center::NotificationItem
& item
) {
125 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal
,
126 0, 0, message_center::kItemTitleToMessagePadding
));
128 views::Label
* title
= new views::Label(item
.title
);
129 title
->set_collapse_when_hidden(true);
130 title
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
131 title
->SetEnabledColor(message_center::kRegularTextColor
);
132 title
->SetBackgroundColor(message_center::kRegularTextBackgroundColor
);
135 views::Label
* message
= new views::Label(item
.message
);
136 message
->set_collapse_when_hidden(true);
137 message
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
138 message
->SetEnabledColor(message_center::kDimTextColor
);
139 message
->SetBackgroundColor(message_center::kDimTextBackgroundColor
);
140 AddChildView(message
);
142 PreferredSizeChanged();
146 ItemView::~ItemView() {
149 void ItemView::SetVisible(bool visible
) {
150 views::View::SetVisible(visible
);
151 for (int i
= 0; i
< child_count(); ++i
)
152 child_at(i
)->SetVisible(visible
);
155 // The NotificationImage is the view representing the area covered by the
156 // notification's image, including background and border. Its size can be
157 // specified in advance and images will be scaled to fit including a border if
161 views::View
* MakeNotificationImage(const gfx::Image
& image
, gfx::Size size
) {
162 views::View
* container
= new views::View();
163 container
->SetLayoutManager(new views::FillLayout());
164 container
->set_background(views::Background::CreateSolidBackground(
165 message_center::kImageBackgroundColor
));
167 gfx::Size
ideal_size(
168 message_center::kNotificationPreferredImageWidth
,
169 message_center::kNotificationPreferredImageHeight
);
170 gfx::Size scaled_size
=
171 message_center::GetImageSizeForContainerSize(ideal_size
, image
.Size());
173 views::View
* proportional_image_view
=
174 new message_center::ProportionalImageView(image
.AsImageSkia(),
177 // This calculation determines that the new image would have the correct
179 if (ideal_size
!= scaled_size
) {
180 proportional_image_view
->SetBorder(views::Border::CreateSolidBorder(
181 message_center::kNotificationImageBorderSize
, SK_ColorTRANSPARENT
));
184 container
->AddChildView(proportional_image_view
);
188 // NotificationProgressBar /////////////////////////////////////////////////////
190 class NotificationProgressBar
: public views::ProgressBar
{
192 NotificationProgressBar();
193 ~NotificationProgressBar() override
;
196 // Overriden from View
197 gfx::Size
GetPreferredSize() const override
;
198 void OnPaint(gfx::Canvas
* canvas
) override
;
200 DISALLOW_COPY_AND_ASSIGN(NotificationProgressBar
);
203 NotificationProgressBar::NotificationProgressBar() {
206 NotificationProgressBar::~NotificationProgressBar() {
209 gfx::Size
NotificationProgressBar::GetPreferredSize() const {
210 gfx::Size
pref_size(kProgressBarWidth
, message_center::kProgressBarThickness
);
211 gfx::Insets insets
= GetInsets();
212 pref_size
.Enlarge(insets
.width(), insets
.height());
216 void NotificationProgressBar::OnPaint(gfx::Canvas
* canvas
) {
217 gfx::Rect content_bounds
= GetContentsBounds();
220 SkPath background_path
;
221 background_path
.addRoundRect(gfx::RectToSkRect(content_bounds
),
222 message_center::kProgressBarCornerRadius
,
223 message_center::kProgressBarCornerRadius
);
224 SkPaint background_paint
;
225 background_paint
.setStyle(SkPaint::kFill_Style
);
226 background_paint
.setFlags(SkPaint::kAntiAlias_Flag
);
227 background_paint
.setColor(message_center::kProgressBarBackgroundColor
);
228 canvas
->DrawPath(background_path
, background_paint
);
231 const int slice_width
=
232 static_cast<int>(content_bounds
.width() * GetNormalizedValue() + 0.5);
236 gfx::Rect slice_bounds
= content_bounds
;
237 slice_bounds
.set_width(slice_width
);
239 slice_path
.addRoundRect(gfx::RectToSkRect(slice_bounds
),
240 message_center::kProgressBarCornerRadius
,
241 message_center::kProgressBarCornerRadius
);
243 slice_paint
.setStyle(SkPaint::kFill_Style
);
244 slice_paint
.setFlags(SkPaint::kAntiAlias_Flag
);
245 slice_paint
.setColor(message_center::kProgressBarSliceColor
);
246 canvas
->DrawPath(slice_path
, slice_paint
);
251 namespace message_center
{
253 // NotificationView ////////////////////////////////////////////////////////////
256 NotificationView
* NotificationView::Create(MessageCenterController
* controller
,
257 const Notification
& notification
,
259 switch (notification
.type()) {
260 case NOTIFICATION_TYPE_BASE_FORMAT
:
261 case NOTIFICATION_TYPE_IMAGE
:
262 case NOTIFICATION_TYPE_MULTIPLE
:
263 case NOTIFICATION_TYPE_SIMPLE
:
264 case NOTIFICATION_TYPE_PROGRESS
:
267 // If the caller asks for an unrecognized kind of view (entirely possible
268 // if an application is running on an older version of this code that
269 // doesn't have the requested kind of notification template), we'll fall
270 // back to a notification instance that will provide at least basic
272 LOG(WARNING
) << "Unable to fulfill request for unrecognized "
273 << "notification type " << notification
.type() << ". "
274 << "Falling back to simple notification type.";
277 // Currently all roads lead to the generic NotificationView.
278 NotificationView
* notification_view
=
279 new NotificationView(controller
, notification
);
281 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
282 // Don't create shadows for notification toasts on linux wih aura.
284 return notification_view
;
287 notification_view
->CreateShadowBorder();
288 return notification_view
;
291 views::View
* NotificationView::TargetForRect(views::View
* root
,
292 const gfx::Rect
& rect
) {
293 CHECK_EQ(root
, this);
295 // TODO(tdanderson): Modify this function to support rect-based event
296 // targeting. Using the center point of |rect| preserves this function's
297 // expected behavior for the time being.
298 gfx::Point point
= rect
.CenterPoint();
300 // Want to return this for underlying views, otherwise GetCursor is not
301 // called. But buttons are exceptions, they'll have their own event handlings.
302 std::vector
<views::View
*> buttons(action_buttons_
.begin(),
303 action_buttons_
.end());
304 buttons
.push_back(close_button());
306 for (size_t i
= 0; i
< buttons
.size(); ++i
) {
307 gfx::Point point_in_child
= point
;
308 ConvertPointToTarget(this, buttons
[i
], &point_in_child
);
309 if (buttons
[i
]->HitTestPoint(point_in_child
))
310 return buttons
[i
]->GetEventHandlerForPoint(point_in_child
);
316 void NotificationView::CreateOrUpdateViews(const Notification
& notification
) {
317 CreateOrUpdateTitleView(notification
);
318 CreateOrUpdateMessageView(notification
);
319 CreateOrUpdateContextMessageView(notification
);
320 CreateOrUpdateProgressBarView(notification
);
321 CreateOrUpdateListItemViews(notification
);
322 CreateOrUpdateIconView(notification
);
323 CreateOrUpdateImageView(notification
);
324 CreateOrUpdateActionButtonViews(notification
);
327 void NotificationView::SetAccessibleName(const Notification
& notification
) {
328 std::vector
<base::string16
> accessible_lines
;
329 accessible_lines
.push_back(notification
.title());
330 accessible_lines
.push_back(notification
.message());
331 accessible_lines
.push_back(notification
.context_message());
332 std::vector
<NotificationItem
> items
= notification
.items();
333 for (size_t i
= 0; i
< items
.size() && i
< kNotificationMaximumItems
; ++i
) {
334 accessible_lines
.push_back(items
[i
].title
+ base::ASCIIToUTF16(" ") +
337 set_accessible_name(JoinString(accessible_lines
, '\n'));
340 NotificationView::NotificationView(MessageCenterController
* controller
,
341 const Notification
& notification
)
344 notification
.notifier_id(),
345 notification
.small_image().AsImageSkia(),
346 notification
.display_source()),
347 controller_(controller
),
348 clickable_(notification
.clickable()),
352 context_message_view_(NULL
),
356 progress_bar_view_(NULL
) {
357 // Create the top_view_, which collects into a vertical box all content
358 // at the top of the notification (to the right of the icon) except for the
360 top_view_
= new views::View();
361 top_view_
->SetLayoutManager(
362 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
363 top_view_
->SetBorder(
364 MakeEmptyBorder(kTextTopPadding
- 8, 0, kTextBottomPadding
- 5, 0));
365 AddChildView(top_view_
);
366 // Create the bottom_view_, which collects into a vertical box all content
367 // below the notification icon.
368 bottom_view_
= new views::View();
369 bottom_view_
->SetLayoutManager(
370 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
371 AddChildView(bottom_view_
);
373 CreateOrUpdateViews(notification
);
375 // Put together the different content and control views. Layering those allows
376 // for proper layout logic and it also allows the close button and small
377 // image to overlap the content as needed to provide large enough click and
378 // touch areas (<http://crbug.com/168822> and <http://crbug.com/168856>).
379 AddChildView(small_image());
380 AddChildView(close_button());
381 SetAccessibleName(notification
);
384 scoped_ptr
<views::ViewTargeter
>(new views::ViewTargeter(this)));
387 NotificationView::~NotificationView() {
390 gfx::Size
NotificationView::GetPreferredSize() const {
391 int top_width
= top_view_
->GetPreferredSize().width() +
392 icon_view_
->GetPreferredSize().width();
393 int bottom_width
= bottom_view_
->GetPreferredSize().width();
394 int preferred_width
= std::max(top_width
, bottom_width
) + GetInsets().width();
395 return gfx::Size(preferred_width
, GetHeightForWidth(preferred_width
));
398 int NotificationView::GetHeightForWidth(int width
) const {
399 // Get the height assuming no line limit changes.
400 int content_width
= width
- GetInsets().width();
401 int top_height
= top_view_
->GetHeightForWidth(content_width
);
402 int bottom_height
= bottom_view_
->GetHeightForWidth(content_width
);
404 // <http://crbug.com/230448> Fix: Adjust the height when the message_view's
405 // line limit would be different for the specified width than it currently is.
406 // TODO(dharcourt): Avoid BoxLayout and directly compute the correct height.
410 title_lines
= title_view_
->GetLinesForWidthAndLimit(width
,
413 int used_limit
= message_view_
->GetLineLimit();
414 int correct_limit
= GetMessageLineLimit(title_lines
, width
);
415 if (used_limit
!= correct_limit
) {
416 top_height
-= GetMessageHeight(content_width
, used_limit
);
417 top_height
+= GetMessageHeight(content_width
, correct_limit
);
421 int content_height
= std::max(top_height
, kIconSize
) + bottom_height
;
423 // Adjust the height to make sure there is at least 16px of space below the
424 // icon if there is any space there (<http://crbug.com/232966>).
425 if (content_height
> kIconSize
)
426 content_height
= std::max(content_height
,
427 kIconSize
+ message_center::kIconBottomPadding
);
429 return content_height
+ GetInsets().height();
432 void NotificationView::Layout() {
433 MessageView::Layout();
434 gfx::Insets insets
= GetInsets();
435 int content_width
= width() - insets
.width();
437 // Before any resizing, set or adjust the number of message lines.
441 title_view_
->GetLinesForWidthAndLimit(width(), kMaxTitleLines
);
444 message_view_
->SetLineLimit(GetMessageLineLimit(title_lines
, width()));
447 int top_height
= top_view_
->GetHeightForWidth(content_width
);
448 top_view_
->SetBounds(insets
.left(), insets
.top(), content_width
, top_height
);
451 icon_view_
->SetBounds(insets
.left(), insets
.top(), kIconSize
, kIconSize
);
454 int bottom_y
= insets
.top() + std::max(top_height
, kIconSize
);
455 int bottom_height
= bottom_view_
->GetHeightForWidth(content_width
);
456 bottom_view_
->SetBounds(insets
.left(), bottom_y
,
457 content_width
, bottom_height
);
460 void NotificationView::OnFocus() {
461 MessageView::OnFocus();
462 ScrollRectToVisible(GetLocalBounds());
465 void NotificationView::ScrollRectToVisible(const gfx::Rect
& rect
) {
466 // Notification want to show the whole notification when a part of it (like
467 // a button) gets focused.
468 views::View::ScrollRectToVisible(GetLocalBounds());
471 gfx::NativeCursor
NotificationView::GetCursor(const ui::MouseEvent
& event
) {
472 if (!clickable_
|| !controller_
->HasClickedListener(notification_id()))
473 return views::View::GetCursor(event
);
475 return views::GetNativeHandCursor();
478 void NotificationView::UpdateWithNotification(
479 const Notification
& notification
) {
480 MessageView::UpdateWithNotification(notification
);
482 CreateOrUpdateViews(notification
);
483 SetAccessibleName(notification
);
488 void NotificationView::ButtonPressed(views::Button
* sender
,
489 const ui::Event
& event
) {
490 // Certain operations can cause |this| to be destructed, so copy the members
491 // we send to other parts of the code.
492 // TODO(dewittj): Remove this hack.
493 std::string
id(notification_id());
494 // See if the button pressed was an action button.
495 for (size_t i
= 0; i
< action_buttons_
.size(); ++i
) {
496 if (sender
== action_buttons_
[i
]) {
497 controller_
->ClickOnNotificationButton(id
, i
);
502 // Let the superclass handled anything other than action buttons.
503 // Warning: This may cause the NotificationView itself to be deleted,
504 // so don't do anything afterwards.
505 MessageView::ButtonPressed(sender
, event
);
508 void NotificationView::ClickOnNotification(const std::string
& notification_id
) {
509 controller_
->ClickOnNotification(notification_id
);
512 void NotificationView::RemoveNotification(const std::string
& notification_id
,
514 controller_
->RemoveNotification(notification_id
, by_user
);
517 void NotificationView::CreateOrUpdateTitleView(
518 const Notification
& notification
) {
519 if (notification
.title().empty()) {
521 // Deletion will also remove |title_view_| from its parent.
528 DCHECK(top_view_
!= NULL
);
530 const gfx::FontList
& font_list
=
531 views::Label().font_list().DeriveWithSizeDelta(2);
533 int title_character_limit
=
534 kNotificationWidth
* kMaxTitleLines
/ kMinPixelsPerTitleCharacter
;
536 base::string16 title
= gfx::TruncateString(notification
.title(),
537 title_character_limit
,
540 int padding
= kTitleLineHeight
- font_list
.GetHeight();
542 title_view_
= new BoundedLabel(title
, font_list
);
543 title_view_
->SetLineHeight(kTitleLineHeight
);
544 title_view_
->SetLineLimit(kMaxTitleLines
);
545 title_view_
->SetColors(message_center::kRegularTextColor
,
546 kRegularTextBackgroundColor
);
547 title_view_
->SetBorder(MakeTextBorder(padding
, 3, 0));
548 top_view_
->AddChildView(title_view_
);
550 title_view_
->SetText(title
);
554 void NotificationView::CreateOrUpdateMessageView(
555 const Notification
& notification
) {
556 if (notification
.message().empty()) {
558 // Deletion will also remove |message_view_| from its parent.
559 delete message_view_
;
560 message_view_
= NULL
;
565 DCHECK(top_view_
!= NULL
);
567 base::string16 text
= gfx::TruncateString(notification
.message(),
568 kMessageCharacterLimit
,
570 if (!message_view_
) {
571 int padding
= kMessageLineHeight
- views::Label().font_list().GetHeight();
572 message_view_
= new BoundedLabel(text
);
573 message_view_
->SetLineHeight(kMessageLineHeight
);
574 message_view_
->SetColors(message_center::kRegularTextColor
,
575 kDimTextBackgroundColor
);
576 message_view_
->SetBorder(MakeTextBorder(padding
, 4, 0));
577 top_view_
->AddChildView(message_view_
);
579 message_view_
->SetText(text
);
582 message_view_
->SetVisible(!notification
.items().size());
585 void NotificationView::CreateOrUpdateContextMessageView(
586 const Notification
& notification
) {
587 if (notification
.context_message().empty()) {
588 if (context_message_view_
) {
589 // Deletion will also remove |context_message_view_| from its parent.
590 delete context_message_view_
;
591 context_message_view_
= NULL
;
596 DCHECK(top_view_
!= NULL
);
598 base::string16 text
= gfx::TruncateString(notification
.context_message(),
599 kContextMessageCharacterLimit
,
601 if (!context_message_view_
) {
602 int padding
= kMessageLineHeight
- views::Label().font_list().GetHeight();
603 context_message_view_
= new BoundedLabel(text
);
604 context_message_view_
->SetLineLimit(
605 message_center::kContextMessageLineLimit
);
606 context_message_view_
->SetLineHeight(kMessageLineHeight
);
607 context_message_view_
->SetColors(message_center::kDimTextColor
,
608 kContextTextBackgroundColor
);
609 context_message_view_
->SetBorder(MakeTextBorder(padding
, 4, 0));
610 top_view_
->AddChildView(context_message_view_
);
612 context_message_view_
->SetText(text
);
616 void NotificationView::CreateOrUpdateProgressBarView(
617 const Notification
& notification
) {
618 if (notification
.type() != NOTIFICATION_TYPE_PROGRESS
) {
619 if (progress_bar_view_
) {
620 // Deletion will also remove |progress_bar_view_| from its parent.
621 delete progress_bar_view_
;
622 progress_bar_view_
= NULL
;
627 DCHECK(top_view_
!= NULL
);
629 if (!progress_bar_view_
) {
630 progress_bar_view_
= new NotificationProgressBar();
631 progress_bar_view_
->SetBorder(MakeProgressBarBorder(
632 message_center::kProgressBarTopPadding
, kProgressBarBottomPadding
));
633 top_view_
->AddChildView(progress_bar_view_
);
636 progress_bar_view_
->SetValue(notification
.progress() / 100.0);
637 progress_bar_view_
->SetVisible(!notification
.items().size());
640 void NotificationView::CreateOrUpdateListItemViews(
641 const Notification
& notification
) {
642 for (size_t i
= 0; i
< item_views_
.size(); ++i
)
643 delete item_views_
[i
];
646 int padding
= kMessageLineHeight
- views::Label().font_list().GetHeight();
647 std::vector
<NotificationItem
> items
= notification
.items();
649 if (items
.size() == 0)
653 for (size_t i
= 0; i
< items
.size() && i
< kNotificationMaximumItems
; ++i
) {
654 ItemView
* item_view
= new ItemView(items
[i
]);
655 item_view
->SetBorder(MakeTextBorder(padding
, i
? 0 : 4, 0));
656 item_views_
.push_back(item_view
);
657 top_view_
->AddChildView(item_view
);
661 void NotificationView::CreateOrUpdateIconView(
662 const Notification
& notification
) {
668 // TODO(dewittj): Detect a compatible update and use the existing icon view.
669 gfx::ImageSkia icon
= notification
.icon().AsImageSkia();
670 if (notification
.type() == NOTIFICATION_TYPE_SIMPLE
&&
671 (icon
.width() != kIconSize
|| icon
.height() != kIconSize
||
672 HasAlpha(icon
, GetWidget()))) {
673 views::ImageView
* icon_view
= new views::ImageView();
674 icon_view
->SetImage(icon
);
675 icon_view
->SetImageSize(gfx::Size(kLegacyIconSize
, kLegacyIconSize
));
676 icon_view
->SetHorizontalAlignment(views::ImageView::CENTER
);
677 icon_view
->SetVerticalAlignment(views::ImageView::CENTER
);
678 icon_view_
= icon_view
;
681 new ProportionalImageView(icon
, gfx::Size(kIconSize
, kIconSize
));
684 icon_view_
->set_background(
685 views::Background::CreateSolidBackground(kIconBackgroundColor
));
687 AddChildView(icon_view_
);
690 void NotificationView::CreateOrUpdateImageView(
691 const Notification
& notification
) {
697 DCHECK(bottom_view_
);
698 DCHECK_EQ(this, bottom_view_
->parent());
700 // TODO(dewittj): Detect a compatible update and use the existing image view.
701 if (!notification
.image().IsEmpty()) {
702 gfx::Size
image_size(kNotificationPreferredImageWidth
,
703 kNotificationPreferredImageHeight
);
704 image_view_
= MakeNotificationImage(notification
.image(), image_size
);
705 bottom_view_
->AddChildViewAt(image_view_
, 0);
709 void NotificationView::CreateOrUpdateActionButtonViews(
710 const Notification
& notification
) {
711 std::vector
<ButtonInfo
> buttons
= notification
.buttons();
712 bool new_buttons
= action_buttons_
.size() != buttons
.size();
714 if (new_buttons
|| buttons
.size() == 0) {
715 // STLDeleteElements also clears the container.
716 STLDeleteElements(&separators_
);
717 STLDeleteElements(&action_buttons_
);
720 DCHECK(bottom_view_
);
721 DCHECK_EQ(this, bottom_view_
->parent());
723 for (size_t i
= 0; i
< buttons
.size(); ++i
) {
724 ButtonInfo button_info
= buttons
[i
];
726 views::View
* separator
= new views::ImageView();
727 separator
->SetBorder(MakeSeparatorBorder(1, 0, kButtonSeparatorColor
));
728 separators_
.push_back(separator
);
729 bottom_view_
->AddChildView(separator
);
730 NotificationButton
* button
= new NotificationButton(this);
731 button
->SetTitle(button_info
.title
);
732 button
->SetIcon(button_info
.icon
.AsImageSkia());
733 action_buttons_
.push_back(button
);
734 bottom_view_
->AddChildView(button
);
736 action_buttons_
[i
]->SetTitle(button_info
.title
);
737 action_buttons_
[i
]->SetIcon(button_info
.icon
.AsImageSkia());
738 action_buttons_
[i
]->SchedulePaint();
739 action_buttons_
[i
]->Layout();
745 views::Widget
* widget
= GetWidget();
746 if (widget
!= NULL
) {
747 widget
->SetSize(widget
->GetContentsView()->GetPreferredSize());
748 GetWidget()->SynthesizeMouseMoveEvent();
753 int NotificationView::GetMessageLineLimit(int title_lines
, int width
) const {
754 // Image notifications require that the image must be kept flush against
755 // their icons, but we can allow more text if no image.
756 int effective_title_lines
= std::max(0, title_lines
- 1);
757 int line_reduction_from_title
= (image_view_
? 1 : 2) * effective_title_lines
;
759 // Title lines are counted as twice as big as message lines for the purpose
760 // of this calculation.
761 // The effect from the title reduction here should be:
762 // * 0 title lines: 5 max lines message.
763 // * 1 title line: 5 max lines message.
764 // * 2 title lines: 3 max lines message.
767 message_center::kMessageExpandedLineLimit
- line_reduction_from_title
);
770 int message_line_limit
= message_center::kMessageCollapsedLineLimit
;
772 // Subtract any lines taken by the context message.
773 if (context_message_view_
) {
774 message_line_limit
-= context_message_view_
->GetLinesForWidthAndLimit(
776 message_center::kContextMessageLineLimit
);
779 // The effect from the title reduction here should be:
780 // * 0 title lines: 2 max lines message + context message.
781 // * 1 title line: 2 max lines message + context message.
782 // * 2 title lines: 1 max lines message + context message.
784 std::max(0, message_line_limit
- line_reduction_from_title
);
786 return message_line_limit
;
789 int NotificationView::GetMessageHeight(int width
, int limit
) const {
790 return message_view_
?
791 message_view_
->GetSizeForWidthAndLines(width
, limit
).height() : 0;
794 } // namespace message_center