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 "third_party/skia/include/core/SkPaint.h"
12 #include "third_party/skia/include/core/SkPath.h"
13 #include "ui/base/cursor/cursor.h"
14 #include "ui/base/layout.h"
15 #include "ui/gfx/canvas.h"
16 #include "ui/gfx/geometry/size.h"
17 #include "ui/gfx/skia_util.h"
18 #include "ui/gfx/text_elider.h"
19 #include "ui/message_center/message_center.h"
20 #include "ui/message_center/message_center_style.h"
21 #include "ui/message_center/notification.h"
22 #include "ui/message_center/notification_types.h"
23 #include "ui/message_center/views/bounded_label.h"
24 #include "ui/message_center/views/constants.h"
25 #include "ui/message_center/views/message_center_controller.h"
26 #include "ui/message_center/views/notification_button.h"
27 #include "ui/message_center/views/padded_button.h"
28 #include "ui/message_center/views/proportional_image_view.h"
29 #include "ui/native_theme/native_theme.h"
30 #include "ui/resources/grit/ui_resources.h"
31 #include "ui/strings/grit/ui_strings.h"
32 #include "ui/views/background.h"
33 #include "ui/views/border.h"
34 #include "ui/views/controls/button/image_button.h"
35 #include "ui/views/controls/image_view.h"
36 #include "ui/views/controls/label.h"
37 #include "ui/views/controls/progress_bar.h"
38 #include "ui/views/layout/box_layout.h"
39 #include "ui/views/layout/fill_layout.h"
40 #include "ui/views/native_cursor.h"
41 #include "ui/views/painter.h"
42 #include "ui/views/view_targeter.h"
43 #include "ui/views/widget/widget.h"
48 const int kProgressBarWidth
= message_center::kNotificationWidth
-
49 message_center::kTextLeftPadding
- message_center::kTextRightPadding
;
50 const int kProgressBarBottomPadding
= 0;
53 scoped_ptr
<views::Border
> MakeEmptyBorder(int top
,
57 return views::Border::CreateEmptyBorder(top
, left
, bottom
, right
);
61 scoped_ptr
<views::Border
> MakeTextBorder(int padding
, int top
, int bottom
) {
62 // Split the padding between the top and the bottom, then add the extra space.
63 return MakeEmptyBorder(padding
/ 2 + top
,
64 message_center::kTextLeftPadding
,
65 (padding
+ 1) / 2 + bottom
,
66 message_center::kTextRightPadding
);
70 scoped_ptr
<views::Border
> MakeProgressBarBorder(int top
, int bottom
) {
71 return MakeEmptyBorder(top
,
72 message_center::kTextLeftPadding
,
74 message_center::kTextRightPadding
);
78 scoped_ptr
<views::Border
> MakeSeparatorBorder(int top
,
81 return views::Border::CreateSolidSidedBorder(top
, left
, 0, 0, color
);
85 // Return true if and only if the image is null or has alpha.
86 bool HasAlpha(gfx::ImageSkia
& image
, views::Widget
* widget
) {
87 // Determine which bitmap to use.
90 factor
= ui::GetScaleFactorForNativeView(widget
->GetNativeView());
92 // Extract that bitmap's alpha and look for a non-opaque pixel there.
93 SkBitmap bitmap
= image
.GetRepresentation(factor
).sk_bitmap();
94 if (!bitmap
.isNull()) {
96 bitmap
.extractAlpha(&alpha
);
97 for (int y
= 0; y
< bitmap
.height(); ++y
) {
98 for (int x
= 0; x
< bitmap
.width(); ++x
) {
99 if (alpha
.getColor(x
, y
) != SK_ColorBLACK
) {
106 // If no opaque pixel was found, return false unless the bitmap is empty.
107 return bitmap
.isNull();
110 // ItemView ////////////////////////////////////////////////////////////////////
112 // ItemViews are responsible for drawing each list notification item's title and
113 // message next to each other within a single column.
114 class ItemView
: public views::View
{
116 ItemView(const message_center::NotificationItem
& item
);
117 ~ItemView() override
;
119 // Overridden from views::View:
120 void SetVisible(bool visible
) override
;
123 DISALLOW_COPY_AND_ASSIGN(ItemView
);
126 ItemView::ItemView(const message_center::NotificationItem
& item
) {
127 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal
,
128 0, 0, message_center::kItemTitleToMessagePadding
));
130 views::Label
* title
= new views::Label(item
.title
);
131 title
->set_collapse_when_hidden(true);
132 title
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
133 title
->SetEnabledColor(message_center::kRegularTextColor
);
134 title
->SetBackgroundColor(message_center::kRegularTextBackgroundColor
);
137 views::Label
* message
= new views::Label(item
.message
);
138 message
->set_collapse_when_hidden(true);
139 message
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
140 message
->SetEnabledColor(message_center::kDimTextColor
);
141 message
->SetBackgroundColor(message_center::kDimTextBackgroundColor
);
142 AddChildView(message
);
144 PreferredSizeChanged();
148 ItemView::~ItemView() {
151 void ItemView::SetVisible(bool visible
) {
152 views::View::SetVisible(visible
);
153 for (int i
= 0; i
< child_count(); ++i
)
154 child_at(i
)->SetVisible(visible
);
157 // NotificationProgressBar /////////////////////////////////////////////////////
159 class NotificationProgressBar
: public views::ProgressBar
{
161 NotificationProgressBar();
162 ~NotificationProgressBar() override
;
165 // Overriden from View
166 gfx::Size
GetPreferredSize() const override
;
167 void OnPaint(gfx::Canvas
* canvas
) override
;
169 DISALLOW_COPY_AND_ASSIGN(NotificationProgressBar
);
172 NotificationProgressBar::NotificationProgressBar() {
175 NotificationProgressBar::~NotificationProgressBar() {
178 gfx::Size
NotificationProgressBar::GetPreferredSize() const {
179 gfx::Size
pref_size(kProgressBarWidth
, message_center::kProgressBarThickness
);
180 gfx::Insets insets
= GetInsets();
181 pref_size
.Enlarge(insets
.width(), insets
.height());
185 void NotificationProgressBar::OnPaint(gfx::Canvas
* canvas
) {
186 gfx::Rect content_bounds
= GetContentsBounds();
189 SkPath background_path
;
190 background_path
.addRoundRect(gfx::RectToSkRect(content_bounds
),
191 message_center::kProgressBarCornerRadius
,
192 message_center::kProgressBarCornerRadius
);
193 SkPaint background_paint
;
194 background_paint
.setStyle(SkPaint::kFill_Style
);
195 background_paint
.setFlags(SkPaint::kAntiAlias_Flag
);
196 background_paint
.setColor(message_center::kProgressBarBackgroundColor
);
197 canvas
->DrawPath(background_path
, background_paint
);
200 const int slice_width
=
201 static_cast<int>(content_bounds
.width() * GetNormalizedValue() + 0.5);
205 gfx::Rect slice_bounds
= content_bounds
;
206 slice_bounds
.set_width(slice_width
);
208 slice_path
.addRoundRect(gfx::RectToSkRect(slice_bounds
),
209 message_center::kProgressBarCornerRadius
,
210 message_center::kProgressBarCornerRadius
);
212 slice_paint
.setStyle(SkPaint::kFill_Style
);
213 slice_paint
.setFlags(SkPaint::kAntiAlias_Flag
);
214 slice_paint
.setColor(message_center::kProgressBarSliceColor
);
215 canvas
->DrawPath(slice_path
, slice_paint
);
220 namespace message_center
{
222 // NotificationView ////////////////////////////////////////////////////////////
225 NotificationView
* NotificationView::Create(MessageCenterController
* controller
,
226 const Notification
& notification
,
228 switch (notification
.type()) {
229 case NOTIFICATION_TYPE_BASE_FORMAT
:
230 case NOTIFICATION_TYPE_IMAGE
:
231 case NOTIFICATION_TYPE_MULTIPLE
:
232 case NOTIFICATION_TYPE_SIMPLE
:
233 case NOTIFICATION_TYPE_PROGRESS
:
236 // If the caller asks for an unrecognized kind of view (entirely possible
237 // if an application is running on an older version of this code that
238 // doesn't have the requested kind of notification template), we'll fall
239 // back to a notification instance that will provide at least basic
241 LOG(WARNING
) << "Unable to fulfill request for unrecognized "
242 << "notification type " << notification
.type() << ". "
243 << "Falling back to simple notification type.";
246 // Currently all roads lead to the generic NotificationView.
247 NotificationView
* notification_view
=
248 new NotificationView(controller
, notification
);
250 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
251 // Don't create shadows for notification toasts on linux wih aura.
253 return notification_view
;
256 notification_view
->CreateShadowBorder();
257 return notification_view
;
260 views::View
* NotificationView::TargetForRect(views::View
* root
,
261 const gfx::Rect
& rect
) {
262 CHECK_EQ(root
, this);
264 // TODO(tdanderson): Modify this function to support rect-based event
265 // targeting. Using the center point of |rect| preserves this function's
266 // expected behavior for the time being.
267 gfx::Point point
= rect
.CenterPoint();
269 // Want to return this for underlying views, otherwise GetCursor is not
270 // called. But buttons are exceptions, they'll have their own event handlings.
271 std::vector
<views::View
*> buttons(action_buttons_
.begin(),
272 action_buttons_
.end());
273 buttons
.push_back(close_button());
275 for (size_t i
= 0; i
< buttons
.size(); ++i
) {
276 gfx::Point point_in_child
= point
;
277 ConvertPointToTarget(this, buttons
[i
], &point_in_child
);
278 if (buttons
[i
]->HitTestPoint(point_in_child
))
279 return buttons
[i
]->GetEventHandlerForPoint(point_in_child
);
285 void NotificationView::CreateOrUpdateViews(const Notification
& notification
) {
286 CreateOrUpdateTitleView(notification
);
287 CreateOrUpdateMessageView(notification
);
288 CreateOrUpdateContextMessageView(notification
);
289 CreateOrUpdateProgressBarView(notification
);
290 CreateOrUpdateListItemViews(notification
);
291 CreateOrUpdateIconView(notification
);
292 CreateOrUpdateImageView(notification
);
293 CreateOrUpdateActionButtonViews(notification
);
296 void NotificationView::SetAccessibleName(const Notification
& notification
) {
297 std::vector
<base::string16
> accessible_lines
;
298 accessible_lines
.push_back(notification
.title());
299 accessible_lines
.push_back(notification
.message());
300 accessible_lines
.push_back(notification
.context_message());
301 std::vector
<NotificationItem
> items
= notification
.items();
302 for (size_t i
= 0; i
< items
.size() && i
< kNotificationMaximumItems
; ++i
) {
303 accessible_lines
.push_back(items
[i
].title
+ base::ASCIIToUTF16(" ") +
307 base::JoinString(accessible_lines
, base::ASCIIToUTF16("\n")));
310 NotificationView::NotificationView(MessageCenterController
* controller
,
311 const Notification
& notification
)
314 notification
.notifier_id(),
315 notification
.small_image().AsImageSkia(),
316 notification
.display_source()),
317 controller_(controller
),
318 clickable_(notification
.clickable()),
322 context_message_view_(NULL
),
325 image_container_(NULL
),
327 progress_bar_view_(NULL
) {
328 // Create the top_view_, which collects into a vertical box all content
329 // at the top of the notification (to the right of the icon) except for the
331 top_view_
= new views::View();
332 top_view_
->SetLayoutManager(
333 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
334 top_view_
->SetBorder(
335 MakeEmptyBorder(kTextTopPadding
- 8, 0, kTextBottomPadding
- 5, 0));
336 AddChildView(top_view_
);
337 // Create the bottom_view_, which collects into a vertical box all content
338 // below the notification icon.
339 bottom_view_
= new views::View();
340 bottom_view_
->SetLayoutManager(
341 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
342 AddChildView(bottom_view_
);
344 CreateOrUpdateViews(notification
);
346 // Put together the different content and control views. Layering those allows
347 // for proper layout logic and it also allows the close button and small
348 // image to overlap the content as needed to provide large enough click and
349 // touch areas (<http://crbug.com/168822> and <http://crbug.com/168856>).
350 AddChildView(small_image());
351 AddChildView(close_button());
352 SetAccessibleName(notification
);
355 scoped_ptr
<views::ViewTargeter
>(new views::ViewTargeter(this)));
358 NotificationView::~NotificationView() {
361 gfx::Size
NotificationView::GetPreferredSize() const {
362 int top_width
= top_view_
->GetPreferredSize().width() +
363 icon_view_
->GetPreferredSize().width();
364 int bottom_width
= bottom_view_
->GetPreferredSize().width();
365 int preferred_width
= std::max(top_width
, bottom_width
) + GetInsets().width();
366 return gfx::Size(preferred_width
, GetHeightForWidth(preferred_width
));
369 int NotificationView::GetHeightForWidth(int width
) const {
370 // Get the height assuming no line limit changes.
371 int content_width
= width
- GetInsets().width();
372 int top_height
= top_view_
->GetHeightForWidth(content_width
);
373 int bottom_height
= bottom_view_
->GetHeightForWidth(content_width
);
375 // <http://crbug.com/230448> Fix: Adjust the height when the message_view's
376 // line limit would be different for the specified width than it currently is.
377 // TODO(dharcourt): Avoid BoxLayout and directly compute the correct height.
381 title_lines
= title_view_
->GetLinesForWidthAndLimit(width
,
384 int used_limit
= message_view_
->GetLineLimit();
385 int correct_limit
= GetMessageLineLimit(title_lines
, width
);
386 if (used_limit
!= correct_limit
) {
387 top_height
-= GetMessageHeight(content_width
, used_limit
);
388 top_height
+= GetMessageHeight(content_width
, correct_limit
);
392 int content_height
= std::max(top_height
, kIconSize
) + bottom_height
;
394 // Adjust the height to make sure there is at least 16px of space below the
395 // icon if there is any space there (<http://crbug.com/232966>).
396 if (content_height
> kIconSize
)
397 content_height
= std::max(content_height
,
398 kIconSize
+ message_center::kIconBottomPadding
);
400 return content_height
+ GetInsets().height();
403 void NotificationView::Layout() {
404 MessageView::Layout();
405 gfx::Insets insets
= GetInsets();
406 int content_width
= width() - insets
.width();
408 // Before any resizing, set or adjust the number of message lines.
412 title_view_
->GetLinesForWidthAndLimit(width(), kMaxTitleLines
);
415 message_view_
->SetLineLimit(GetMessageLineLimit(title_lines
, width()));
418 int top_height
= top_view_
->GetHeightForWidth(content_width
);
419 top_view_
->SetBounds(insets
.left(), insets
.top(), content_width
, top_height
);
422 icon_view_
->SetBounds(insets
.left(), insets
.top(), kIconSize
, kIconSize
);
425 int bottom_y
= insets
.top() + std::max(top_height
, kIconSize
);
426 int bottom_height
= bottom_view_
->GetHeightForWidth(content_width
);
427 bottom_view_
->SetBounds(insets
.left(), bottom_y
,
428 content_width
, bottom_height
);
431 void NotificationView::OnFocus() {
432 MessageView::OnFocus();
433 ScrollRectToVisible(GetLocalBounds());
436 void NotificationView::ScrollRectToVisible(const gfx::Rect
& rect
) {
437 // Notification want to show the whole notification when a part of it (like
438 // a button) gets focused.
439 views::View::ScrollRectToVisible(GetLocalBounds());
442 gfx::NativeCursor
NotificationView::GetCursor(const ui::MouseEvent
& event
) {
443 if (!clickable_
|| !controller_
->HasClickedListener(notification_id()))
444 return views::View::GetCursor(event
);
446 return views::GetNativeHandCursor();
449 void NotificationView::UpdateWithNotification(
450 const Notification
& notification
) {
451 MessageView::UpdateWithNotification(notification
);
453 CreateOrUpdateViews(notification
);
454 SetAccessibleName(notification
);
459 void NotificationView::ButtonPressed(views::Button
* sender
,
460 const ui::Event
& event
) {
461 // Certain operations can cause |this| to be destructed, so copy the members
462 // we send to other parts of the code.
463 // TODO(dewittj): Remove this hack.
464 std::string
id(notification_id());
465 // See if the button pressed was an action button.
466 for (size_t i
= 0; i
< action_buttons_
.size(); ++i
) {
467 if (sender
== action_buttons_
[i
]) {
468 controller_
->ClickOnNotificationButton(id
, i
);
473 // Let the superclass handled anything other than action buttons.
474 // Warning: This may cause the NotificationView itself to be deleted,
475 // so don't do anything afterwards.
476 MessageView::ButtonPressed(sender
, event
);
479 void NotificationView::ClickOnNotification(const std::string
& notification_id
) {
480 controller_
->ClickOnNotification(notification_id
);
483 void NotificationView::RemoveNotification(const std::string
& notification_id
,
485 controller_
->RemoveNotification(notification_id
, by_user
);
488 void NotificationView::CreateOrUpdateTitleView(
489 const Notification
& notification
) {
490 if (notification
.title().empty()) {
492 // Deletion will also remove |title_view_| from its parent.
499 DCHECK(top_view_
!= NULL
);
501 const gfx::FontList
& font_list
=
502 views::Label().font_list().DeriveWithSizeDelta(2);
504 int title_character_limit
=
505 kNotificationWidth
* kMaxTitleLines
/ kMinPixelsPerTitleCharacter
;
507 base::string16 title
= gfx::TruncateString(notification
.title(),
508 title_character_limit
,
511 int padding
= kTitleLineHeight
- font_list
.GetHeight();
513 title_view_
= new BoundedLabel(title
, font_list
);
514 title_view_
->SetLineHeight(kTitleLineHeight
);
515 title_view_
->SetLineLimit(kMaxTitleLines
);
516 title_view_
->SetColors(message_center::kRegularTextColor
,
517 kRegularTextBackgroundColor
);
518 title_view_
->SetBorder(MakeTextBorder(padding
, 3, 0));
519 top_view_
->AddChildView(title_view_
);
521 title_view_
->SetText(title
);
525 void NotificationView::CreateOrUpdateMessageView(
526 const Notification
& notification
) {
527 if (notification
.message().empty()) {
529 // Deletion will also remove |message_view_| from its parent.
530 delete message_view_
;
531 message_view_
= NULL
;
536 DCHECK(top_view_
!= NULL
);
538 base::string16 text
= gfx::TruncateString(notification
.message(),
539 kMessageCharacterLimit
,
541 if (!message_view_
) {
542 int padding
= kMessageLineHeight
- views::Label().font_list().GetHeight();
543 message_view_
= new BoundedLabel(text
);
544 message_view_
->SetLineHeight(kMessageLineHeight
);
545 message_view_
->SetColors(message_center::kRegularTextColor
,
546 kDimTextBackgroundColor
);
547 message_view_
->SetBorder(MakeTextBorder(padding
, 4, 0));
548 top_view_
->AddChildView(message_view_
);
550 message_view_
->SetText(text
);
553 message_view_
->SetVisible(!notification
.items().size());
556 void NotificationView::CreateOrUpdateContextMessageView(
557 const Notification
& notification
) {
558 if (notification
.context_message().empty()) {
559 if (context_message_view_
) {
560 // Deletion will also remove |context_message_view_| from its parent.
561 delete context_message_view_
;
562 context_message_view_
= NULL
;
567 DCHECK(top_view_
!= NULL
);
569 base::string16 text
= gfx::TruncateString(notification
.context_message(),
570 kContextMessageCharacterLimit
,
572 if (!context_message_view_
) {
573 int padding
= kMessageLineHeight
- views::Label().font_list().GetHeight();
574 context_message_view_
= new BoundedLabel(text
);
575 context_message_view_
->SetLineLimit(
576 message_center::kContextMessageLineLimit
);
577 context_message_view_
->SetLineHeight(kMessageLineHeight
);
578 context_message_view_
->SetColors(message_center::kDimTextColor
,
579 kContextTextBackgroundColor
);
580 context_message_view_
->SetBorder(MakeTextBorder(padding
, 4, 0));
581 top_view_
->AddChildView(context_message_view_
);
583 context_message_view_
->SetText(text
);
587 void NotificationView::CreateOrUpdateProgressBarView(
588 const Notification
& notification
) {
589 if (notification
.type() != NOTIFICATION_TYPE_PROGRESS
) {
590 if (progress_bar_view_
) {
591 // Deletion will also remove |progress_bar_view_| from its parent.
592 delete progress_bar_view_
;
593 progress_bar_view_
= NULL
;
598 DCHECK(top_view_
!= NULL
);
600 if (!progress_bar_view_
) {
601 progress_bar_view_
= new NotificationProgressBar();
602 progress_bar_view_
->SetBorder(MakeProgressBarBorder(
603 message_center::kProgressBarTopPadding
, kProgressBarBottomPadding
));
604 top_view_
->AddChildView(progress_bar_view_
);
607 progress_bar_view_
->SetValue(notification
.progress() / 100.0);
608 progress_bar_view_
->SetVisible(!notification
.items().size());
611 void NotificationView::CreateOrUpdateListItemViews(
612 const Notification
& notification
) {
613 for (size_t i
= 0; i
< item_views_
.size(); ++i
)
614 delete item_views_
[i
];
617 int padding
= kMessageLineHeight
- views::Label().font_list().GetHeight();
618 std::vector
<NotificationItem
> items
= notification
.items();
620 if (items
.size() == 0)
624 for (size_t i
= 0; i
< items
.size() && i
< kNotificationMaximumItems
; ++i
) {
625 ItemView
* item_view
= new ItemView(items
[i
]);
626 item_view
->SetBorder(MakeTextBorder(padding
, i
? 0 : 4, 0));
627 item_views_
.push_back(item_view
);
628 top_view_
->AddChildView(item_view
);
632 void NotificationView::CreateOrUpdateIconView(
633 const Notification
& notification
) {
635 icon_view_
= new ProportionalImageView(gfx::Size(kIconSize
, kIconSize
));
636 icon_view_
->set_background(
637 views::Background::CreateSolidBackground(kIconBackgroundColor
));
638 AddChildView(icon_view_
);
641 gfx::ImageSkia icon
= notification
.icon().AsImageSkia();
642 gfx::Size max_image_size
=
643 notification
.type() == NOTIFICATION_TYPE_SIMPLE
&&
644 (icon
.width() < kIconSize
|| icon
.height() < kIconSize
||
645 HasAlpha(icon
, GetWidget()))
646 ? gfx::Size(kLegacyIconSize
, kLegacyIconSize
)
647 : gfx::Size(kIconSize
, kIconSize
);
648 icon_view_
->SetImage(icon
, max_image_size
);
651 void NotificationView::CreateOrUpdateImageView(
652 const Notification
& notification
) {
653 // |image_view_| is the view representing the area covered by the
654 // notification's image, including background and border. Its size can be
655 // specified in advance and images will be scaled to fit including a border if
657 if (notification
.image().IsEmpty()) {
658 delete image_container_
;
659 image_container_
= NULL
;
664 gfx::Size
ideal_size(kNotificationPreferredImageWidth
,
665 kNotificationPreferredImageHeight
);
667 if (!image_container_
) {
668 DCHECK(!image_view_
);
669 DCHECK(bottom_view_
);
670 DCHECK_EQ(this, bottom_view_
->parent());
672 image_container_
= new views::View();
673 image_container_
->SetLayoutManager(new views::FillLayout());
674 image_container_
->set_background(views::Background::CreateSolidBackground(
675 message_center::kImageBackgroundColor
));
677 image_view_
= new message_center::ProportionalImageView(ideal_size
);
678 image_container_
->AddChildView(image_view_
);
679 bottom_view_
->AddChildViewAt(image_container_
, 0);
683 image_view_
->SetImage(notification
.image().AsImageSkia(), ideal_size
);
685 gfx::Size scaled_size
= message_center::GetImageSizeForContainerSize(
686 ideal_size
, notification
.image().Size());
687 image_view_
->SetBorder(ideal_size
!= scaled_size
688 ? views::Border::CreateSolidBorder(
689 message_center::kNotificationImageBorderSize
,
694 void NotificationView::CreateOrUpdateActionButtonViews(
695 const Notification
& notification
) {
696 std::vector
<ButtonInfo
> buttons
= notification
.buttons();
697 bool new_buttons
= action_buttons_
.size() != buttons
.size();
699 if (new_buttons
|| buttons
.size() == 0) {
700 // STLDeleteElements also clears the container.
701 STLDeleteElements(&separators_
);
702 STLDeleteElements(&action_buttons_
);
705 DCHECK(bottom_view_
);
706 DCHECK_EQ(this, bottom_view_
->parent());
708 for (size_t i
= 0; i
< buttons
.size(); ++i
) {
709 ButtonInfo button_info
= buttons
[i
];
711 views::View
* separator
= new views::ImageView();
712 separator
->SetBorder(MakeSeparatorBorder(1, 0, kButtonSeparatorColor
));
713 separators_
.push_back(separator
);
714 bottom_view_
->AddChildView(separator
);
715 NotificationButton
* button
= new NotificationButton(this);
716 button
->SetTitle(button_info
.title
);
717 button
->SetIcon(button_info
.icon
.AsImageSkia());
718 action_buttons_
.push_back(button
);
719 bottom_view_
->AddChildView(button
);
721 action_buttons_
[i
]->SetTitle(button_info
.title
);
722 action_buttons_
[i
]->SetIcon(button_info
.icon
.AsImageSkia());
723 action_buttons_
[i
]->SchedulePaint();
724 action_buttons_
[i
]->Layout();
730 views::Widget
* widget
= GetWidget();
731 if (widget
!= NULL
) {
732 widget
->SetSize(widget
->GetContentsView()->GetPreferredSize());
733 GetWidget()->SynthesizeMouseMoveEvent();
738 int NotificationView::GetMessageLineLimit(int title_lines
, int width
) const {
739 // Image notifications require that the image must be kept flush against
740 // their icons, but we can allow more text if no image.
741 int effective_title_lines
= std::max(0, title_lines
- 1);
742 int line_reduction_from_title
= (image_view_
? 1 : 2) * effective_title_lines
;
744 // Title lines are counted as twice as big as message lines for the purpose
745 // of this calculation.
746 // The effect from the title reduction here should be:
747 // * 0 title lines: 5 max lines message.
748 // * 1 title line: 5 max lines message.
749 // * 2 title lines: 3 max lines message.
752 message_center::kMessageExpandedLineLimit
- line_reduction_from_title
);
755 int message_line_limit
= message_center::kMessageCollapsedLineLimit
;
757 // Subtract any lines taken by the context message.
758 if (context_message_view_
) {
759 message_line_limit
-= context_message_view_
->GetLinesForWidthAndLimit(
761 message_center::kContextMessageLineLimit
);
764 // The effect from the title reduction here should be:
765 // * 0 title lines: 2 max lines message + context message.
766 // * 1 title line: 2 max lines message + context message.
767 // * 2 title lines: 1 max lines message + context message.
769 std::max(0, message_line_limit
- line_reduction_from_title
);
771 return message_line_limit
;
774 int NotificationView::GetMessageHeight(int width
, int limit
) const {
775 return message_view_
?
776 message_view_
->GetSizeForWidthAndLines(width
, limit
).height() : 0;
779 } // namespace message_center