Add ICU message format support
[chromium-blink-merge.git] / ui / message_center / views / notification_view.cc
blob27c8accae20cdf39092662575eb52221be7b4e5d
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"
45 namespace {
47 // Dimensions.
48 const int kProgressBarWidth = message_center::kNotificationWidth -
49 message_center::kTextLeftPadding - message_center::kTextRightPadding;
50 const int kProgressBarBottomPadding = 0;
52 // static
53 scoped_ptr<views::Border> MakeEmptyBorder(int top,
54 int left,
55 int bottom,
56 int right) {
57 return views::Border::CreateEmptyBorder(top, left, bottom, right);
60 // static
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);
69 // static
70 scoped_ptr<views::Border> MakeProgressBarBorder(int top, int bottom) {
71 return MakeEmptyBorder(top,
72 message_center::kTextLeftPadding,
73 bottom,
74 message_center::kTextRightPadding);
77 // static
78 scoped_ptr<views::Border> MakeSeparatorBorder(int top,
79 int left,
80 SkColor color) {
81 return views::Border::CreateSolidSidedBorder(top, left, 0, 0, color);
84 // static
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.
88 float factor = 1.0f;
89 if (widget)
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()) {
95 SkBitmap alpha;
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) {
100 return true;
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 {
115 public:
116 ItemView(const message_center::NotificationItem& item);
117 ~ItemView() override;
119 // Overridden from views::View:
120 void SetVisible(bool visible) override;
122 private:
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);
135 AddChildView(title);
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();
145 SchedulePaint();
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 {
160 public:
161 NotificationProgressBar();
162 ~NotificationProgressBar() override;
164 private:
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());
182 return pref_size;
185 void NotificationProgressBar::OnPaint(gfx::Canvas* canvas) {
186 gfx::Rect content_bounds = GetContentsBounds();
188 // Draw background.
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);
199 // Draw slice.
200 const int slice_width =
201 static_cast<int>(content_bounds.width() * GetNormalizedValue() + 0.5);
202 if (slice_width < 1)
203 return;
205 gfx::Rect slice_bounds = content_bounds;
206 slice_bounds.set_width(slice_width);
207 SkPath slice_path;
208 slice_path.addRoundRect(gfx::RectToSkRect(slice_bounds),
209 message_center::kProgressBarCornerRadius,
210 message_center::kProgressBarCornerRadius);
211 SkPaint slice_paint;
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);
218 } // namespace
220 namespace message_center {
222 // NotificationView ////////////////////////////////////////////////////////////
224 // static
225 NotificationView* NotificationView::Create(MessageCenterController* controller,
226 const Notification& notification,
227 bool top_level) {
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:
234 break;
235 default:
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
240 // functionality.
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.
252 if (top_level)
253 return notification_view;
254 #endif
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);
282 return root;
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(" ") +
304 items[i].message);
306 set_accessible_name(
307 base::JoinString(accessible_lines, base::ASCIIToUTF16("\n")));
310 NotificationView::NotificationView(MessageCenterController* controller,
311 const Notification& notification)
312 : MessageView(this,
313 notification.id(),
314 notification.notifier_id(),
315 notification.small_image().AsImageSkia(),
316 notification.display_source()),
317 controller_(controller),
318 clickable_(notification.clickable()),
319 top_view_(NULL),
320 title_view_(NULL),
321 message_view_(NULL),
322 context_message_view_(NULL),
323 icon_view_(NULL),
324 bottom_view_(NULL),
325 image_container_(NULL),
326 image_view_(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
330 // close button.
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);
354 SetEventTargeter(
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.
378 if (message_view_) {
379 int title_lines = 0;
380 if (title_view_) {
381 title_lines = title_view_->GetLinesForWidthAndLimit(width,
382 kMaxTitleLines);
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.
409 int title_lines = 0;
410 if (title_view_) {
411 title_lines =
412 title_view_->GetLinesForWidthAndLimit(width(), kMaxTitleLines);
414 if (message_view_)
415 message_view_->SetLineLimit(GetMessageLineLimit(title_lines, width()));
417 // Top views.
418 int top_height = top_view_->GetHeightForWidth(content_width);
419 top_view_->SetBounds(insets.left(), insets.top(), content_width, top_height);
421 // Icon.
422 icon_view_->SetBounds(insets.left(), insets.top(), kIconSize, kIconSize);
424 // Bottom views.
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);
455 Layout();
456 SchedulePaint();
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);
469 return;
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,
484 bool by_user) {
485 controller_->RemoveNotification(notification_id, by_user);
488 void NotificationView::CreateOrUpdateTitleView(
489 const Notification& notification) {
490 if (notification.title().empty()) {
491 if (title_view_) {
492 // Deletion will also remove |title_view_| from its parent.
493 delete title_view_;
494 title_view_ = NULL;
496 return;
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,
509 gfx::WORD_BREAK);
510 if (!title_view_) {
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_);
520 } else {
521 title_view_->SetText(title);
525 void NotificationView::CreateOrUpdateMessageView(
526 const Notification& notification) {
527 if (notification.message().empty()) {
528 if (message_view_) {
529 // Deletion will also remove |message_view_| from its parent.
530 delete message_view_;
531 message_view_ = NULL;
533 return;
536 DCHECK(top_view_ != NULL);
538 base::string16 text = gfx::TruncateString(notification.message(),
539 kMessageCharacterLimit,
540 gfx::WORD_BREAK);
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_);
549 } else {
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;
564 return;
567 DCHECK(top_view_ != NULL);
569 base::string16 text = gfx::TruncateString(notification.context_message(),
570 kContextMessageCharacterLimit,
571 gfx::WORD_BREAK);
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_);
582 } else {
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;
595 return;
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];
615 item_views_.clear();
617 int padding = kMessageLineHeight - views::Label().font_list().GetHeight();
618 std::vector<NotificationItem> items = notification.items();
620 if (items.size() == 0)
621 return;
623 DCHECK(top_view_);
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) {
634 if (!icon_view_) {
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
656 // necessary.
657 if (notification.image().IsEmpty()) {
658 delete image_container_;
659 image_container_ = NULL;
660 image_view_ = NULL;
661 return;
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);
682 DCHECK(image_view_);
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,
690 SK_ColorTRANSPARENT)
691 : NULL);
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];
710 if (new_buttons) {
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);
720 } else {
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();
728 if (new_buttons) {
729 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;
743 if (!image_view_) {
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.
750 return std::max(
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(
760 width,
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.
768 message_line_limit =
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