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.
4
5#include "ui/message_center/views/notification_view.h"
6
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 "grit/ui_resources.h"
12#include "grit/ui_strings.h"
13#include "ui/base/cursor/cursor.h"
14#include "ui/base/l10n/l10n_util.h"
15#include "ui/base/layout.h"
16#include "ui/base/resource/resource_bundle.h"
17#include "ui/gfx/canvas.h"
18#include "ui/gfx/size.h"
19#include "ui/gfx/skia_util.h"
20#include "ui/gfx/text_elider.h"
21#include "ui/message_center/message_center.h"
22#include "ui/message_center/message_center_style.h"
23#include "ui/message_center/notification.h"
24#include "ui/message_center/notification_types.h"
25#include "ui/message_center/views/bounded_label.h"
26#include "ui/message_center/views/constants.h"
27#include "ui/message_center/views/message_center_controller.h"
28#include "ui/message_center/views/notification_button.h"
29#include "ui/message_center/views/padded_button.h"
30#include "ui/message_center/views/proportional_image_view.h"
31#include "ui/native_theme/native_theme.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/widget/widget.h"
43
44namespace {
45
46// Dimensions.
47const int kProgressBarWidth = message_center::kNotificationWidth -
48    message_center::kTextLeftPadding - message_center::kTextRightPadding;
49const int kProgressBarBottomPadding = 0;
50
51// static
52scoped_ptr<views::Border> MakeEmptyBorder(int top,
53                                          int left,
54                                          int bottom,
55                                          int right) {
56  return views::Border::CreateEmptyBorder(top, left, bottom, right);
57}
58
59// static
60scoped_ptr<views::Border> MakeTextBorder(int padding, int top, int bottom) {
61  // Split the padding between the top and the bottom, then add the extra space.
62  return MakeEmptyBorder(padding / 2 + top,
63                         message_center::kTextLeftPadding,
64                         (padding + 1) / 2 + bottom,
65                         message_center::kTextRightPadding);
66}
67
68// static
69scoped_ptr<views::Border> MakeProgressBarBorder(int top, int bottom) {
70  return MakeEmptyBorder(top,
71                         message_center::kTextLeftPadding,
72                         bottom,
73                         message_center::kTextRightPadding);
74}
75
76// static
77scoped_ptr<views::Border> MakeSeparatorBorder(int top,
78                                              int left,
79                                              SkColor color) {
80  return views::Border::CreateSolidSidedBorder(top, left, 0, 0, color);
81}
82
83// static
84// Return true if and only if the image is null or has alpha.
85bool HasAlpha(gfx::ImageSkia& image, views::Widget* widget) {
86  // Determine which bitmap to use.
87  float factor = 1.0f;
88  if (widget)
89    factor = ui::GetScaleFactorForNativeView(widget->GetNativeView());
90
91  // Extract that bitmap's alpha and look for a non-opaque pixel there.
92  SkBitmap bitmap = image.GetRepresentation(factor).sk_bitmap();
93  if (!bitmap.isNull()) {
94    SkBitmap alpha;
95    bitmap.extractAlpha(&alpha);
96    for (int y = 0; y < bitmap.height(); ++y) {
97      for (int x = 0; x < bitmap.width(); ++x) {
98        if (alpha.getColor(x, y) != SK_ColorBLACK) {
99          return true;
100        }
101      }
102    }
103  }
104
105  // If no opaque pixel was found, return false unless the bitmap is empty.
106  return bitmap.isNull();
107}
108
109// ItemView ////////////////////////////////////////////////////////////////////
110
111// ItemViews are responsible for drawing each list notification item's title and
112// message next to each other within a single column.
113class ItemView : public views::View {
114 public:
115  ItemView(const message_center::NotificationItem& item);
116  virtual ~ItemView();
117
118  // Overridden from views::View:
119  virtual void SetVisible(bool visible) OVERRIDE;
120
121 private:
122  DISALLOW_COPY_AND_ASSIGN(ItemView);
123};
124
125ItemView::ItemView(const message_center::NotificationItem& item) {
126  SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
127      0, 0, message_center::kItemTitleToMessagePadding));
128
129  views::Label* title = new views::Label(item.title);
130  title->set_collapse_when_hidden(true);
131  title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
132  title->SetEnabledColor(message_center::kRegularTextColor);
133  title->SetBackgroundColor(message_center::kRegularTextBackgroundColor);
134  AddChildView(title);
135
136  views::Label* message = new views::Label(item.message);
137  message->set_collapse_when_hidden(true);
138  message->SetHorizontalAlignment(gfx::ALIGN_LEFT);
139  message->SetEnabledColor(message_center::kDimTextColor);
140  message->SetBackgroundColor(message_center::kDimTextBackgroundColor);
141  AddChildView(message);
142
143  PreferredSizeChanged();
144  SchedulePaint();
145}
146
147ItemView::~ItemView() {
148}
149
150void ItemView::SetVisible(bool visible) {
151  views::View::SetVisible(visible);
152  for (int i = 0; i < child_count(); ++i)
153    child_at(i)->SetVisible(visible);
154}
155
156// The NotificationImage is the view representing the area covered by the
157// notification's image, including background and border.  Its size can be
158// specified in advance and images will be scaled to fit including a border if
159// necessary.
160
161// static
162views::View* MakeNotificationImage(const gfx::Image& image, gfx::Size size) {
163  views::View* container = new views::View();
164  container->SetLayoutManager(new views::FillLayout());
165  container->set_background(views::Background::CreateSolidBackground(
166      message_center::kImageBackgroundColor));
167
168  gfx::Size ideal_size(
169      message_center::kNotificationPreferredImageWidth,
170      message_center::kNotificationPreferredImageHeight);
171  gfx::Size scaled_size =
172      message_center::GetImageSizeForContainerSize(ideal_size, image.Size());
173
174  views::View* proportional_image_view =
175      new message_center::ProportionalImageView(image.AsImageSkia(),
176                                                ideal_size);
177
178  // This calculation determines that the new image would have the correct
179  // height for width.
180  if (ideal_size != scaled_size) {
181    proportional_image_view->SetBorder(views::Border::CreateSolidBorder(
182        message_center::kNotificationImageBorderSize, SK_ColorTRANSPARENT));
183  }
184
185  container->AddChildView(proportional_image_view);
186  return container;
187}
188
189// NotificationProgressBar /////////////////////////////////////////////////////
190
191class NotificationProgressBar : public views::ProgressBar {
192 public:
193  NotificationProgressBar();
194  virtual ~NotificationProgressBar();
195
196 private:
197  // Overriden from View
198  virtual gfx::Size GetPreferredSize() const OVERRIDE;
199  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
200
201  DISALLOW_COPY_AND_ASSIGN(NotificationProgressBar);
202};
203
204NotificationProgressBar::NotificationProgressBar() {
205}
206
207NotificationProgressBar::~NotificationProgressBar() {
208}
209
210gfx::Size NotificationProgressBar::GetPreferredSize() const {
211  gfx::Size pref_size(kProgressBarWidth, message_center::kProgressBarThickness);
212  gfx::Insets insets = GetInsets();
213  pref_size.Enlarge(insets.width(), insets.height());
214  return pref_size;
215}
216
217void NotificationProgressBar::OnPaint(gfx::Canvas* canvas) {
218  gfx::Rect content_bounds = GetContentsBounds();
219
220  // Draw background.
221  SkPath background_path;
222  background_path.addRoundRect(gfx::RectToSkRect(content_bounds),
223                               message_center::kProgressBarCornerRadius,
224                               message_center::kProgressBarCornerRadius);
225  SkPaint background_paint;
226  background_paint.setStyle(SkPaint::kFill_Style);
227  background_paint.setFlags(SkPaint::kAntiAlias_Flag);
228  background_paint.setColor(message_center::kProgressBarBackgroundColor);
229  canvas->DrawPath(background_path, background_paint);
230
231  // Draw slice.
232  const int slice_width =
233      static_cast<int>(content_bounds.width() * GetNormalizedValue() + 0.5);
234  if (slice_width < 1)
235    return;
236
237  gfx::Rect slice_bounds = content_bounds;
238  slice_bounds.set_width(slice_width);
239  SkPath slice_path;
240  slice_path.addRoundRect(gfx::RectToSkRect(slice_bounds),
241                          message_center::kProgressBarCornerRadius,
242                          message_center::kProgressBarCornerRadius);
243  SkPaint slice_paint;
244  slice_paint.setStyle(SkPaint::kFill_Style);
245  slice_paint.setFlags(SkPaint::kAntiAlias_Flag);
246  slice_paint.setColor(message_center::kProgressBarSliceColor);
247  canvas->DrawPath(slice_path, slice_paint);
248}
249
250}  // namespace
251
252namespace message_center {
253
254// NotificationView ////////////////////////////////////////////////////////////
255
256// static
257NotificationView* NotificationView::Create(MessageCenterController* controller,
258                                           const Notification& notification,
259                                           bool top_level) {
260  switch (notification.type()) {
261    case NOTIFICATION_TYPE_BASE_FORMAT:
262    case NOTIFICATION_TYPE_IMAGE:
263    case NOTIFICATION_TYPE_MULTIPLE:
264    case NOTIFICATION_TYPE_SIMPLE:
265    case NOTIFICATION_TYPE_PROGRESS:
266      break;
267    default:
268      // If the caller asks for an unrecognized kind of view (entirely possible
269      // if an application is running on an older version of this code that
270      // doesn't have the requested kind of notification template), we'll fall
271      // back to a notification instance that will provide at least basic
272      // functionality.
273      LOG(WARNING) << "Unable to fulfill request for unrecognized "
274                   << "notification type " << notification.type() << ". "
275                   << "Falling back to simple notification type.";
276  }
277
278  // Currently all roads lead to the generic NotificationView.
279  NotificationView* notification_view =
280      new NotificationView(controller, notification);
281
282#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
283  // Don't create shadows for notification toasts on linux wih aura.
284  if (top_level)
285    return notification_view;
286#endif
287
288  notification_view->CreateShadowBorder();
289  return notification_view;
290}
291
292void NotificationView::CreateOrUpdateViews(const Notification& notification) {
293  CreateOrUpdateTitleView(notification);
294  CreateOrUpdateMessageView(notification);
295  CreateOrUpdateContextMessageView(notification);
296  CreateOrUpdateProgressBarView(notification);
297  CreateOrUpdateListItemViews(notification);
298  CreateOrUpdateIconView(notification);
299  CreateOrUpdateImageView(notification);
300  CreateOrUpdateActionButtonViews(notification);
301}
302
303void NotificationView::SetAccessibleName(const Notification& notification) {
304  std::vector<base::string16> accessible_lines;
305  accessible_lines.push_back(notification.title());
306  accessible_lines.push_back(notification.message());
307  accessible_lines.push_back(notification.context_message());
308  std::vector<NotificationItem> items = notification.items();
309  for (size_t i = 0; i < items.size() && i < kNotificationMaximumItems; ++i) {
310    accessible_lines.push_back(items[i].title + base::ASCIIToUTF16(" ") +
311                               items[i].message);
312  }
313  set_accessible_name(JoinString(accessible_lines, '\n'));
314}
315
316NotificationView::NotificationView(MessageCenterController* controller,
317                                   const Notification& notification)
318    : MessageView(this,
319                  notification.id(),
320                  notification.notifier_id(),
321                  notification.small_image().AsImageSkia(),
322                  notification.display_source()),
323      controller_(controller),
324      clickable_(notification.clickable()),
325      top_view_(NULL),
326      title_view_(NULL),
327      message_view_(NULL),
328      context_message_view_(NULL),
329      icon_view_(NULL),
330      bottom_view_(NULL),
331      image_view_(NULL),
332      progress_bar_view_(NULL) {
333  // Create the top_view_, which collects into a vertical box all content
334  // at the top of the notification (to the right of the icon) except for the
335  // close button.
336  top_view_ = new views::View();
337  top_view_->SetLayoutManager(
338      new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
339  top_view_->SetBorder(
340      MakeEmptyBorder(kTextTopPadding - 8, 0, kTextBottomPadding - 5, 0));
341  AddChildView(top_view_);
342  // Create the bottom_view_, which collects into a vertical box all content
343  // below the notification icon.
344  bottom_view_ = new views::View();
345  bottom_view_->SetLayoutManager(
346      new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
347  AddChildView(bottom_view_);
348
349  CreateOrUpdateViews(notification);
350
351  // Put together the different content and control views. Layering those allows
352  // for proper layout logic and it also allows the close button and small
353  // image to overlap the content as needed to provide large enough click and
354  // touch areas (<http://crbug.com/168822> and <http://crbug.com/168856>).
355  AddChildView(small_image());
356  AddChildView(close_button());
357  SetAccessibleName(notification);
358}
359
360NotificationView::~NotificationView() {
361}
362
363gfx::Size NotificationView::GetPreferredSize() const {
364  int top_width = top_view_->GetPreferredSize().width() +
365                  icon_view_->GetPreferredSize().width();
366  int bottom_width = bottom_view_->GetPreferredSize().width();
367  int preferred_width = std::max(top_width, bottom_width) + GetInsets().width();
368  return gfx::Size(preferred_width, GetHeightForWidth(preferred_width));
369}
370
371int NotificationView::GetHeightForWidth(int width) const {
372  // Get the height assuming no line limit changes.
373  int content_width = width - GetInsets().width();
374  int top_height = top_view_->GetHeightForWidth(content_width);
375  int bottom_height = bottom_view_->GetHeightForWidth(content_width);
376
377  // <http://crbug.com/230448> Fix: Adjust the height when the message_view's
378  // line limit would be different for the specified width than it currently is.
379  // TODO(dharcourt): Avoid BoxLayout and directly compute the correct height.
380  if (message_view_) {
381    int title_lines = 0;
382    if (title_view_) {
383      title_lines = title_view_->GetLinesForWidthAndLimit(width,
384                                                          kMaxTitleLines);
385    }
386    int used_limit = message_view_->GetLineLimit();
387    int correct_limit = GetMessageLineLimit(title_lines, width);
388    if (used_limit != correct_limit) {
389      top_height -= GetMessageHeight(content_width, used_limit);
390      top_height += GetMessageHeight(content_width, correct_limit);
391    }
392  }
393
394  int content_height = std::max(top_height, kIconSize) + bottom_height;
395
396  // Adjust the height to make sure there is at least 16px of space below the
397  // icon if there is any space there (<http://crbug.com/232966>).
398  if (content_height > kIconSize)
399    content_height = std::max(content_height,
400                              kIconSize + message_center::kIconBottomPadding);
401
402  return content_height + GetInsets().height();
403}
404
405void NotificationView::Layout() {
406  MessageView::Layout();
407  gfx::Insets insets = GetInsets();
408  int content_width = width() - insets.width();
409
410  // Before any resizing, set or adjust the number of message lines.
411  int title_lines = 0;
412  if (title_view_) {
413    title_lines =
414        title_view_->GetLinesForWidthAndLimit(width(), kMaxTitleLines);
415  }
416  if (message_view_)
417    message_view_->SetLineLimit(GetMessageLineLimit(title_lines, width()));
418
419  // Top views.
420  int top_height = top_view_->GetHeightForWidth(content_width);
421  top_view_->SetBounds(insets.left(), insets.top(), content_width, top_height);
422
423  // Icon.
424  icon_view_->SetBounds(insets.left(), insets.top(), kIconSize, kIconSize);
425
426  // Bottom views.
427  int bottom_y = insets.top() + std::max(top_height, kIconSize);
428  int bottom_height = bottom_view_->GetHeightForWidth(content_width);
429  bottom_view_->SetBounds(insets.left(), bottom_y,
430                          content_width, bottom_height);
431}
432
433void NotificationView::OnFocus() {
434  MessageView::OnFocus();
435  ScrollRectToVisible(GetLocalBounds());
436}
437
438void NotificationView::ScrollRectToVisible(const gfx::Rect& rect) {
439  // Notification want to show the whole notification when a part of it (like
440  // a button) gets focused.
441  views::View::ScrollRectToVisible(GetLocalBounds());
442}
443
444views::View* NotificationView::GetEventHandlerForRect(const gfx::Rect& rect) {
445  // TODO(tdanderson): Modify this function to support rect-based event
446  // targeting. Using the center point of |rect| preserves this function's
447  // expected behavior for the time being.
448  gfx::Point point = rect.CenterPoint();
449
450  // Want to return this for underlying views, otherwise GetCursor is not
451  // called. But buttons are exceptions, they'll have their own event handlings.
452  std::vector<views::View*> buttons(action_buttons_.begin(),
453                                    action_buttons_.end());
454  buttons.push_back(close_button());
455
456  for (size_t i = 0; i < buttons.size(); ++i) {
457    gfx::Point point_in_child = point;
458    ConvertPointToTarget(this, buttons[i], &point_in_child);
459    if (buttons[i]->HitTestPoint(point_in_child))
460      return buttons[i]->GetEventHandlerForPoint(point_in_child);
461  }
462
463  return this;
464}
465
466gfx::NativeCursor NotificationView::GetCursor(const ui::MouseEvent& event) {
467  if (!clickable_ || !controller_->HasClickedListener(notification_id()))
468    return views::View::GetCursor(event);
469
470  return views::GetNativeHandCursor();
471}
472
473void NotificationView::UpdateWithNotification(
474    const Notification& notification) {
475  MessageView::UpdateWithNotification(notification);
476
477  CreateOrUpdateViews(notification);
478  SetAccessibleName(notification);
479  Layout();
480  SchedulePaint();
481}
482
483void NotificationView::ButtonPressed(views::Button* sender,
484                                     const ui::Event& event) {
485  // Certain operations can cause |this| to be destructed, so copy the members
486  // we send to other parts of the code.
487  // TODO(dewittj): Remove this hack.
488  std::string id(notification_id());
489  // See if the button pressed was an action button.
490  for (size_t i = 0; i < action_buttons_.size(); ++i) {
491    if (sender == action_buttons_[i]) {
492      controller_->ClickOnNotificationButton(id, i);
493      return;
494    }
495  }
496
497  // Let the superclass handled anything other than action buttons.
498  // Warning: This may cause the NotificationView itself to be deleted,
499  // so don't do anything afterwards.
500  MessageView::ButtonPressed(sender, event);
501}
502
503void NotificationView::ClickOnNotification(const std::string& notification_id) {
504  controller_->ClickOnNotification(notification_id);
505}
506
507void NotificationView::RemoveNotification(const std::string& notification_id,
508                                          bool by_user) {
509  controller_->RemoveNotification(notification_id, by_user);
510}
511
512void NotificationView::CreateOrUpdateTitleView(
513    const Notification& notification) {
514  if (notification.title().empty()) {
515    if (title_view_) {
516      // Deletion will also remove |title_view_| from its parent.
517      delete title_view_;
518      title_view_ = NULL;
519    }
520    return;
521  }
522
523  DCHECK(top_view_ != NULL);
524
525  const gfx::FontList& font_list =
526      views::Label().font_list().DeriveWithSizeDelta(2);
527
528  int title_character_limit =
529      kNotificationWidth * kMaxTitleLines / kMinPixelsPerTitleCharacter;
530
531  if (!title_view_) {
532    int padding = kTitleLineHeight - font_list.GetHeight();
533
534    title_view_ = new BoundedLabel(
535        gfx::TruncateString(notification.title(), title_character_limit),
536        font_list);
537    title_view_->SetLineHeight(kTitleLineHeight);
538    title_view_->SetLineLimit(kMaxTitleLines);
539    title_view_->SetColors(message_center::kRegularTextColor,
540                           kRegularTextBackgroundColor);
541    title_view_->SetBorder(MakeTextBorder(padding, 3, 0));
542    top_view_->AddChildView(title_view_);
543  } else {
544    title_view_->SetText(
545        gfx::TruncateString(notification.title(), title_character_limit));
546  }
547}
548
549void NotificationView::CreateOrUpdateMessageView(
550    const Notification& notification) {
551  if (notification.message().empty()) {
552    if (message_view_) {
553      // Deletion will also remove |message_view_| from its parent.
554      delete message_view_;
555      message_view_ = NULL;
556    }
557    return;
558  }
559
560  DCHECK(top_view_ != NULL);
561
562  if (!message_view_) {
563    int padding = kMessageLineHeight - views::Label().font_list().GetHeight();
564    message_view_ = new BoundedLabel(
565        gfx::TruncateString(notification.message(), kMessageCharacterLimit));
566    message_view_->SetLineHeight(kMessageLineHeight);
567    message_view_->SetColors(message_center::kRegularTextColor,
568                             kDimTextBackgroundColor);
569    message_view_->SetBorder(MakeTextBorder(padding, 4, 0));
570    top_view_->AddChildView(message_view_);
571  } else {
572    message_view_->SetText(
573        gfx::TruncateString(notification.message(), kMessageCharacterLimit));
574  }
575
576  message_view_->SetVisible(!notification.items().size());
577}
578
579void NotificationView::CreateOrUpdateContextMessageView(
580    const Notification& notification) {
581  if (notification.context_message().empty()) {
582    if (context_message_view_) {
583      // Deletion will also remove |context_message_view_| from its parent.
584      delete context_message_view_;
585      context_message_view_ = NULL;
586    }
587    return;
588  }
589
590  DCHECK(top_view_ != NULL);
591
592  if (!context_message_view_) {
593    int padding = kMessageLineHeight - views::Label().font_list().GetHeight();
594    context_message_view_ = new BoundedLabel(gfx::TruncateString(
595        notification.context_message(), kContextMessageCharacterLimit));
596    context_message_view_->SetLineLimit(
597        message_center::kContextMessageLineLimit);
598    context_message_view_->SetLineHeight(kMessageLineHeight);
599    context_message_view_->SetColors(message_center::kDimTextColor,
600                                     kContextTextBackgroundColor);
601    context_message_view_->SetBorder(MakeTextBorder(padding, 4, 0));
602    top_view_->AddChildView(context_message_view_);
603  } else {
604    context_message_view_->SetText(gfx::TruncateString(
605        notification.context_message(), kContextMessageCharacterLimit));
606  }
607}
608
609void NotificationView::CreateOrUpdateProgressBarView(
610    const Notification& notification) {
611  if (notification.type() != NOTIFICATION_TYPE_PROGRESS) {
612    if (progress_bar_view_) {
613      // Deletion will also remove |progress_bar_view_| from its parent.
614      delete progress_bar_view_;
615      progress_bar_view_ = NULL;
616    }
617    return;
618  }
619
620  DCHECK(top_view_ != NULL);
621
622  if (!progress_bar_view_) {
623    progress_bar_view_ = new NotificationProgressBar();
624    progress_bar_view_->SetBorder(MakeProgressBarBorder(
625        message_center::kProgressBarTopPadding, kProgressBarBottomPadding));
626    top_view_->AddChildView(progress_bar_view_);
627  }
628
629  progress_bar_view_->SetValue(notification.progress() / 100.0);
630  progress_bar_view_->SetVisible(!notification.items().size());
631}
632
633void NotificationView::CreateOrUpdateListItemViews(
634    const Notification& notification) {
635  for (size_t i = 0; i < item_views_.size(); ++i)
636    delete item_views_[i];
637  item_views_.clear();
638
639  int padding = kMessageLineHeight - views::Label().font_list().GetHeight();
640  std::vector<NotificationItem> items = notification.items();
641
642  if (items.size() == 0)
643    return;
644
645  DCHECK(top_view_);
646  for (size_t i = 0; i < items.size() && i < kNotificationMaximumItems; ++i) {
647    ItemView* item_view = new ItemView(items[i]);
648    item_view->SetBorder(MakeTextBorder(padding, i ? 0 : 4, 0));
649    item_views_.push_back(item_view);
650    top_view_->AddChildView(item_view);
651  }
652}
653
654void NotificationView::CreateOrUpdateIconView(
655    const Notification& notification) {
656  if (icon_view_) {
657    delete icon_view_;
658    icon_view_ = NULL;
659  }
660
661  // TODO(dewittj): Detect a compatible update and use the existing icon view.
662  gfx::ImageSkia icon = notification.icon().AsImageSkia();
663  if (notification.type() == NOTIFICATION_TYPE_SIMPLE &&
664      (icon.width() != kIconSize || icon.height() != kIconSize ||
665       HasAlpha(icon, GetWidget()))) {
666    views::ImageView* icon_view = new views::ImageView();
667    icon_view->SetImage(icon);
668    icon_view->SetImageSize(gfx::Size(kLegacyIconSize, kLegacyIconSize));
669    icon_view->SetHorizontalAlignment(views::ImageView::CENTER);
670    icon_view->SetVerticalAlignment(views::ImageView::CENTER);
671    icon_view_ = icon_view;
672  } else {
673    icon_view_ =
674        new ProportionalImageView(icon, gfx::Size(kIconSize, kIconSize));
675  }
676
677  icon_view_->set_background(
678      views::Background::CreateSolidBackground(kIconBackgroundColor));
679
680  AddChildView(icon_view_);
681}
682
683void NotificationView::CreateOrUpdateImageView(
684    const Notification& notification) {
685  if (image_view_) {
686    delete image_view_;
687    image_view_ = NULL;
688  }
689
690  DCHECK(bottom_view_);
691  DCHECK_EQ(this, bottom_view_->parent());
692
693  // TODO(dewittj): Detect a compatible update and use the existing image view.
694  if (!notification.image().IsEmpty()) {
695    gfx::Size image_size(kNotificationPreferredImageWidth,
696                         kNotificationPreferredImageHeight);
697    image_view_ = MakeNotificationImage(notification.image(), image_size);
698    bottom_view_->AddChildViewAt(image_view_, 0);
699  }
700}
701
702void NotificationView::CreateOrUpdateActionButtonViews(
703    const Notification& notification) {
704  std::vector<ButtonInfo> buttons = notification.buttons();
705  bool new_buttons = action_buttons_.size() != buttons.size();
706
707  if (new_buttons || buttons.size() == 0) {
708    // STLDeleteElements also clears the container.
709    STLDeleteElements(&separators_);
710    STLDeleteElements(&action_buttons_);
711  }
712
713  DCHECK(bottom_view_);
714  DCHECK_EQ(this, bottom_view_->parent());
715
716  for (size_t i = 0; i < buttons.size(); ++i) {
717    ButtonInfo button_info = buttons[i];
718    if (new_buttons) {
719      views::View* separator = new views::ImageView();
720      separator->SetBorder(MakeSeparatorBorder(1, 0, kButtonSeparatorColor));
721      separators_.push_back(separator);
722      bottom_view_->AddChildView(separator);
723      NotificationButton* button = new NotificationButton(this);
724      button->SetTitle(button_info.title);
725      button->SetIcon(button_info.icon.AsImageSkia());
726      action_buttons_.push_back(button);
727      bottom_view_->AddChildView(button);
728    } else {
729      action_buttons_[i]->SetTitle(button_info.title);
730      action_buttons_[i]->SetIcon(button_info.icon.AsImageSkia());
731      action_buttons_[i]->SchedulePaint();
732      action_buttons_[i]->Layout();
733    }
734  }
735
736  if (new_buttons) {
737    Layout();
738    views::Widget* widget = GetWidget();
739    if (widget != NULL) {
740      widget->SetSize(widget->GetContentsView()->GetPreferredSize());
741      GetWidget()->SynthesizeMouseMoveEvent();
742    }
743  }
744}
745
746int NotificationView::GetMessageLineLimit(int title_lines, int width) const {
747  // Image notifications require that the image must be kept flush against
748  // their icons, but we can allow more text if no image.
749  int effective_title_lines = std::max(0, title_lines - 1);
750  int line_reduction_from_title = (image_view_ ? 1 : 2) * effective_title_lines;
751  if (!image_view_) {
752    // Title lines are counted as twice as big as message lines for the purpose
753    // of this calculation.
754    // The effect from the title reduction here should be:
755    //   * 0 title lines: 5 max lines message.
756    //   * 1 title line:  5 max lines message.
757    //   * 2 title lines: 3 max lines message.
758    return std::max(
759        0,
760        message_center::kMessageExpandedLineLimit - line_reduction_from_title);
761  }
762
763  int message_line_limit = message_center::kMessageCollapsedLineLimit;
764
765  // Subtract any lines taken by the context message.
766  if (context_message_view_) {
767    message_line_limit -= context_message_view_->GetLinesForWidthAndLimit(
768        width,
769        message_center::kContextMessageLineLimit);
770  }
771
772  // The effect from the title reduction here should be:
773  //   * 0 title lines: 2 max lines message + context message.
774  //   * 1 title line:  2 max lines message + context message.
775  //   * 2 title lines: 1 max lines message + context message.
776  message_line_limit =
777      std::max(0, message_line_limit - line_reduction_from_title);
778
779  return message_line_limit;
780}
781
782int NotificationView::GetMessageHeight(int width, int limit) const {
783  return message_view_ ?
784         message_view_->GetSizeForWidthAndLines(width, limit).height() : 0;
785}
786
787}  // namespace message_center
788