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 "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"
42
43namespace {
44
45// Dimensions.
46const int kProgressBarWidth = message_center::kNotificationWidth -
47    message_center::kTextLeftPadding - message_center::kTextRightPadding;
48const int kProgressBarBottomPadding = 0;
49
50// static
51scoped_ptr<views::Border> MakeEmptyBorder(int top,
52                                          int left,
53                                          int bottom,
54                                          int right) {
55  return views::Border::CreateEmptyBorder(top, left, bottom, right);
56}
57
58// static
59scoped_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);
65}
66
67// static
68scoped_ptr<views::Border> MakeProgressBarBorder(int top, int bottom) {
69  return MakeEmptyBorder(top,
70                         message_center::kTextLeftPadding,
71                         bottom,
72                         message_center::kTextRightPadding);
73}
74
75// static
76scoped_ptr<views::Border> MakeSeparatorBorder(int top,
77                                              int left,
78                                              SkColor color) {
79  return views::Border::CreateSolidSidedBorder(top, left, 0, 0, color);
80}
81
82// static
83// Return true if and only if the image is null or has alpha.
84bool 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());
89
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;
99        }
100      }
101    }
102  }
103
104  // If no opaque pixel was found, return false unless the bitmap is empty.
105  return bitmap.isNull();
106}
107
108// ItemView ////////////////////////////////////////////////////////////////////
109
110// ItemViews are responsible for drawing each list notification item's title and
111// message next to each other within a single column.
112class ItemView : public views::View {
113 public:
114  ItemView(const message_center::NotificationItem& item);
115  virtual ~ItemView();
116
117  // Overridden from views::View:
118  virtual void SetVisible(bool visible) OVERRIDE;
119
120 private:
121  DISALLOW_COPY_AND_ASSIGN(ItemView);
122};
123
124ItemView::ItemView(const message_center::NotificationItem& item) {
125  SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
126      0, 0, message_center::kItemTitleToMessagePadding));
127
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);
134
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);
141
142  PreferredSizeChanged();
143  SchedulePaint();
144}
145
146ItemView::~ItemView() {
147}
148
149void ItemView::SetVisible(bool visible) {
150  views::View::SetVisible(visible);
151  for (int i = 0; i < child_count(); ++i)
152    child_at(i)->SetVisible(visible);
153}
154
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.
159
160// static
161views::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));
166
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());
172
173  views::View* proportional_image_view =
174      new message_center::ProportionalImageView(image.AsImageSkia(),
175                                                ideal_size);
176
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));
182  }
183
184  container->AddChildView(proportional_image_view);
185  return container;
186}
187
188// NotificationProgressBar /////////////////////////////////////////////////////
189
190class NotificationProgressBar : public views::ProgressBar {
191 public:
192  NotificationProgressBar();
193  virtual ~NotificationProgressBar();
194
195 private:
196  // Overriden from View
197  virtual gfx::Size GetPreferredSize() const OVERRIDE;
198  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
199
200  DISALLOW_COPY_AND_ASSIGN(NotificationProgressBar);
201};
202
203NotificationProgressBar::NotificationProgressBar() {
204}
205
206NotificationProgressBar::~NotificationProgressBar() {
207}
208
209gfx::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;
214}
215
216void NotificationProgressBar::OnPaint(gfx::Canvas* canvas) {
217  gfx::Rect content_bounds = GetContentsBounds();
218
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);
229
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;
235
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);
247}
248
249}  // namespace
250
251namespace message_center {
252
253// NotificationView ////////////////////////////////////////////////////////////
254
255// static
256NotificationView* 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.";
275  }
276
277  // Currently all roads lead to the generic NotificationView.
278  NotificationView* notification_view =
279      new NotificationView(controller, notification);
280
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
286
287  notification_view->CreateShadowBorder();
288  return notification_view;
289}
290
291views::View* NotificationView::TargetForRect(views::View* root,
292                                             const gfx::Rect& rect) {
293  CHECK_EQ(root, this);
294
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();
299
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());
305
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);
311  }
312
313  return root;
314}
315
316void 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);
325}
326
327void 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);
336  }
337  set_accessible_name(JoinString(accessible_lines, '\n'));
338}
339
340NotificationView::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_);
372
373  CreateOrUpdateViews(notification);
374
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);
382
383  SetEventTargeter(
384      scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
385}
386
387NotificationView::~NotificationView() {
388}
389
390gfx::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));
396}
397
398int 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);
403
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);
412    }
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);
418    }
419  }
420
421  int content_height = std::max(top_height, kIconSize) + bottom_height;
422
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);
428
429  return content_height + GetInsets().height();
430}
431
432void NotificationView::Layout() {
433  MessageView::Layout();
434  gfx::Insets insets = GetInsets();
435  int content_width = width() - insets.width();
436
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);
442  }
443  if (message_view_)
444    message_view_->SetLineLimit(GetMessageLineLimit(title_lines, width()));
445
446  // Top views.
447  int top_height = top_view_->GetHeightForWidth(content_width);
448  top_view_->SetBounds(insets.left(), insets.top(), content_width, top_height);
449
450  // Icon.
451  icon_view_->SetBounds(insets.left(), insets.top(), kIconSize, kIconSize);
452
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);
458}
459
460void NotificationView::OnFocus() {
461  MessageView::OnFocus();
462  ScrollRectToVisible(GetLocalBounds());
463}
464
465void 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());
469}
470
471gfx::NativeCursor NotificationView::GetCursor(const ui::MouseEvent& event) {
472  if (!clickable_ || !controller_->HasClickedListener(notification_id()))
473    return views::View::GetCursor(event);
474
475  return views::GetNativeHandCursor();
476}
477
478void NotificationView::UpdateWithNotification(
479    const Notification& notification) {
480  MessageView::UpdateWithNotification(notification);
481
482  CreateOrUpdateViews(notification);
483  SetAccessibleName(notification);
484  Layout();
485  SchedulePaint();
486}
487
488void 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;
499    }
500  }
501
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);
506}
507
508void NotificationView::ClickOnNotification(const std::string& notification_id) {
509  controller_->ClickOnNotification(notification_id);
510}
511
512void NotificationView::RemoveNotification(const std::string& notification_id,
513                                          bool by_user) {
514  controller_->RemoveNotification(notification_id, by_user);
515}
516
517void 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;
524    }
525    return;
526  }
527
528  DCHECK(top_view_ != NULL);
529
530  const gfx::FontList& font_list =
531      views::Label().font_list().DeriveWithSizeDelta(2);
532
533  int title_character_limit =
534      kNotificationWidth * kMaxTitleLines / kMinPixelsPerTitleCharacter;
535
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();
541
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);
551  }
552}
553
554void 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;
561    }
562    return;
563  }
564
565  DCHECK(top_view_ != NULL);
566
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);
580  }
581
582  message_view_->SetVisible(!notification.items().size());
583}
584
585void 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;
592    }
593    return;
594  }
595
596  DCHECK(top_view_ != NULL);
597
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);
613  }
614}
615
616void 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;
623    }
624    return;
625  }
626
627  DCHECK(top_view_ != NULL);
628
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_);
634  }
635
636  progress_bar_view_->SetValue(notification.progress() / 100.0);
637  progress_bar_view_->SetVisible(!notification.items().size());
638}
639
640void 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();
645
646  int padding = kMessageLineHeight - views::Label().font_list().GetHeight();
647  std::vector<NotificationItem> items = notification.items();
648
649  if (items.size() == 0)
650    return;
651
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);
658  }
659}
660
661void NotificationView::CreateOrUpdateIconView(
662    const Notification& notification) {
663  if (icon_view_) {
664    delete icon_view_;
665    icon_view_ = NULL;
666  }
667
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));
682  }
683
684  icon_view_->set_background(
685      views::Background::CreateSolidBackground(kIconBackgroundColor));
686
687  AddChildView(icon_view_);
688}
689
690void NotificationView::CreateOrUpdateImageView(
691    const Notification& notification) {
692  if (image_view_) {
693    delete image_view_;
694    image_view_ = NULL;
695  }
696
697  DCHECK(bottom_view_);
698  DCHECK_EQ(this, bottom_view_->parent());
699
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);
706  }
707}
708
709void NotificationView::CreateOrUpdateActionButtonViews(
710    const Notification& notification) {
711  std::vector<ButtonInfo> buttons = notification.buttons();
712  bool new_buttons = action_buttons_.size() != buttons.size();
713
714  if (new_buttons || buttons.size() == 0) {
715    // STLDeleteElements also clears the container.
716    STLDeleteElements(&separators_);
717    STLDeleteElements(&action_buttons_);
718  }
719
720  DCHECK(bottom_view_);
721  DCHECK_EQ(this, bottom_view_->parent());
722
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();
740    }
741  }
742
743  if (new_buttons) {
744    Layout();
745    views::Widget* widget = GetWidget();
746    if (widget != NULL) {
747      widget->SetSize(widget->GetContentsView()->GetPreferredSize());
748      GetWidget()->SynthesizeMouseMoveEvent();
749    }
750  }
751}
752
753int 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(
766        0,
767        message_center::kMessageExpandedLineLimit - line_reduction_from_title);
768  }
769
770  int message_line_limit = message_center::kMessageCollapsedLineLimit;
771
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);
777  }
778
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);
785
786  return message_line_limit;
787}
788
789int NotificationView::GetMessageHeight(int width, int limit) const {
790  return message_view_ ?
791         message_view_->GetSizeForWidthAndLines(width, limit).height() : 0;
792}
793
794}  // namespace message_center
795