Move mojo gypfiles to toplevel mojo
[chromium-blink-merge.git] / ui / message_center / views / notification_view.cc
blob1cc9c2b17ab7c9e747cbcab5dda6821ad74b1d0c
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"
43 namespace {
45 // Dimensions.
46 const int kProgressBarWidth = message_center::kNotificationWidth -
47 message_center::kTextLeftPadding - message_center::kTextRightPadding;
48 const int kProgressBarBottomPadding = 0;
50 // static
51 scoped_ptr<views::Border> MakeEmptyBorder(int top,
52 int left,
53 int bottom,
54 int right) {
55 return views::Border::CreateEmptyBorder(top, left, bottom, right);
58 // static
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);
67 // static
68 scoped_ptr<views::Border> MakeProgressBarBorder(int top, int bottom) {
69 return MakeEmptyBorder(top,
70 message_center::kTextLeftPadding,
71 bottom,
72 message_center::kTextRightPadding);
75 // static
76 scoped_ptr<views::Border> MakeSeparatorBorder(int top,
77 int left,
78 SkColor color) {
79 return views::Border::CreateSolidSidedBorder(top, left, 0, 0, color);
82 // static
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.
86 float factor = 1.0f;
87 if (widget)
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()) {
93 SkBitmap alpha;
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) {
98 return true;
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 {
113 public:
114 ItemView(const message_center::NotificationItem& item);
115 ~ItemView() override;
117 // Overridden from views::View:
118 void SetVisible(bool visible) override;
120 private:
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);
133 AddChildView(title);
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();
143 SchedulePaint();
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
158 // necessary.
160 // static
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(),
175 ideal_size);
177 // This calculation determines that the new image would have the correct
178 // height for width.
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);
185 return container;
188 // NotificationProgressBar /////////////////////////////////////////////////////
190 class NotificationProgressBar : public views::ProgressBar {
191 public:
192 NotificationProgressBar();
193 ~NotificationProgressBar() override;
195 private:
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());
213 return pref_size;
216 void NotificationProgressBar::OnPaint(gfx::Canvas* canvas) {
217 gfx::Rect content_bounds = GetContentsBounds();
219 // Draw background.
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);
230 // Draw slice.
231 const int slice_width =
232 static_cast<int>(content_bounds.width() * GetNormalizedValue() + 0.5);
233 if (slice_width < 1)
234 return;
236 gfx::Rect slice_bounds = content_bounds;
237 slice_bounds.set_width(slice_width);
238 SkPath slice_path;
239 slice_path.addRoundRect(gfx::RectToSkRect(slice_bounds),
240 message_center::kProgressBarCornerRadius,
241 message_center::kProgressBarCornerRadius);
242 SkPaint slice_paint;
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);
249 } // namespace
251 namespace message_center {
253 // NotificationView ////////////////////////////////////////////////////////////
255 // static
256 NotificationView* NotificationView::Create(MessageCenterController* controller,
257 const Notification& notification,
258 bool top_level) {
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:
265 break;
266 default:
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
271 // functionality.
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.
283 if (top_level)
284 return notification_view;
285 #endif
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);
313 return root;
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(" ") +
335 items[i].message);
337 set_accessible_name(JoinString(accessible_lines, '\n'));
340 NotificationView::NotificationView(MessageCenterController* controller,
341 const Notification& notification)
342 : MessageView(this,
343 notification.id(),
344 notification.notifier_id(),
345 notification.small_image().AsImageSkia(),
346 notification.display_source()),
347 controller_(controller),
348 clickable_(notification.clickable()),
349 top_view_(NULL),
350 title_view_(NULL),
351 message_view_(NULL),
352 context_message_view_(NULL),
353 icon_view_(NULL),
354 bottom_view_(NULL),
355 image_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
359 // close button.
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);
383 SetEventTargeter(
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.
407 if (message_view_) {
408 int title_lines = 0;
409 if (title_view_) {
410 title_lines = title_view_->GetLinesForWidthAndLimit(width,
411 kMaxTitleLines);
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.
438 int title_lines = 0;
439 if (title_view_) {
440 title_lines =
441 title_view_->GetLinesForWidthAndLimit(width(), kMaxTitleLines);
443 if (message_view_)
444 message_view_->SetLineLimit(GetMessageLineLimit(title_lines, width()));
446 // Top views.
447 int top_height = top_view_->GetHeightForWidth(content_width);
448 top_view_->SetBounds(insets.left(), insets.top(), content_width, top_height);
450 // Icon.
451 icon_view_->SetBounds(insets.left(), insets.top(), kIconSize, kIconSize);
453 // Bottom views.
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);
484 Layout();
485 SchedulePaint();
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);
498 return;
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,
513 bool by_user) {
514 controller_->RemoveNotification(notification_id, by_user);
517 void NotificationView::CreateOrUpdateTitleView(
518 const Notification& notification) {
519 if (notification.title().empty()) {
520 if (title_view_) {
521 // Deletion will also remove |title_view_| from its parent.
522 delete title_view_;
523 title_view_ = NULL;
525 return;
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,
538 gfx::WORD_BREAK);
539 if (!title_view_) {
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_);
549 } else {
550 title_view_->SetText(title);
554 void NotificationView::CreateOrUpdateMessageView(
555 const Notification& notification) {
556 if (notification.message().empty()) {
557 if (message_view_) {
558 // Deletion will also remove |message_view_| from its parent.
559 delete message_view_;
560 message_view_ = NULL;
562 return;
565 DCHECK(top_view_ != NULL);
567 base::string16 text = gfx::TruncateString(notification.message(),
568 kMessageCharacterLimit,
569 gfx::WORD_BREAK);
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_);
578 } else {
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;
593 return;
596 DCHECK(top_view_ != NULL);
598 base::string16 text = gfx::TruncateString(notification.context_message(),
599 kContextMessageCharacterLimit,
600 gfx::WORD_BREAK);
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_);
611 } else {
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;
624 return;
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];
644 item_views_.clear();
646 int padding = kMessageLineHeight - views::Label().font_list().GetHeight();
647 std::vector<NotificationItem> items = notification.items();
649 if (items.size() == 0)
650 return;
652 DCHECK(top_view_);
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) {
663 if (icon_view_) {
664 delete icon_view_;
665 icon_view_ = NULL;
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;
679 } else {
680 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) {
692 if (image_view_) {
693 delete image_view_;
694 image_view_ = NULL;
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];
725 if (new_buttons) {
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);
735 } else {
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();
743 if (new_buttons) {
744 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;
758 if (!image_view_) {
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.
765 return std::max(
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(
775 width,
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.
783 message_line_limit =
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