0947af81986e780bff8b4d74c0c30e955bad1c42
[platform/framework/web/crosswalk.git] / src / ui / message_center / views / notification_view.cc
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/layout.h"
15 #include "ui/gfx/canvas.h"
16 #include "ui/gfx/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/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
43 namespace {
44
45 // Dimensions.
46 const int kProgressBarWidth = message_center::kNotificationWidth -
47     message_center::kTextLeftPadding - message_center::kTextRightPadding;
48 const int kProgressBarBottomPadding = 0;
49
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);
56 }
57
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);
65 }
66
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);
73 }
74
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);
80 }
81
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());
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.
112 class 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
124 ItemView::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
146 ItemView::~ItemView() {
147 }
148
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);
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
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));
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
190 class 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
203 NotificationProgressBar::NotificationProgressBar() {
204 }
205
206 NotificationProgressBar::~NotificationProgressBar() {
207 }
208
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;
214 }
215
216 void 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
251 namespace message_center {
252
253 // NotificationView ////////////////////////////////////////////////////////////
254
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.";
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
291 views::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
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);
325 }
326
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);
336   }
337   set_accessible_name(JoinString(accessible_lines, '\n'));
338 }
339
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_);
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
387 NotificationView::~NotificationView() {
388 }
389
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));
396 }
397
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);
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
432 void 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
460 void NotificationView::OnFocus() {
461   MessageView::OnFocus();
462   ScrollRectToVisible(GetLocalBounds());
463 }
464
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());
469 }
470
471 gfx::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
478 void NotificationView::UpdateWithNotification(
479     const Notification& notification) {
480   MessageView::UpdateWithNotification(notification);
481
482   CreateOrUpdateViews(notification);
483   SetAccessibleName(notification);
484   Layout();
485   SchedulePaint();
486 }
487
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;
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
508 void NotificationView::ClickOnNotification(const std::string& notification_id) {
509   controller_->ClickOnNotification(notification_id);
510 }
511
512 void NotificationView::RemoveNotification(const std::string& notification_id,
513                                           bool by_user) {
514   controller_->RemoveNotification(notification_id, by_user);
515 }
516
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;
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
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;
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
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;
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
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;
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
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();
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
661 void 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
690 void 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
709 void 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
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(
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
789 int 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