• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
44 namespace {
45 
46 // Dimensions.
47 const int kProgressBarWidth = message_center::kNotificationWidth -
48     message_center::kTextLeftPadding - message_center::kTextRightPadding;
49 const int kProgressBarBottomPadding = 0;
50 
51 // static
MakeEmptyBorder(int top,int left,int bottom,int right)52 scoped_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
MakeTextBorder(int padding,int top,int bottom)60 scoped_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
MakeProgressBarBorder(int top,int bottom)69 scoped_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
MakeSeparatorBorder(int top,int left,SkColor color)77 scoped_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.
HasAlpha(gfx::ImageSkia & image,views::Widget * widget)85 bool 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.
113 class 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 
ItemView(const message_center::NotificationItem & item)125 ItemView::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 
~ItemView()147 ItemView::~ItemView() {
148 }
149 
SetVisible(bool visible)150 void 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
MakeNotificationImage(const gfx::Image & image,gfx::Size size)162 views::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 
191 class 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 
NotificationProgressBar()204 NotificationProgressBar::NotificationProgressBar() {
205 }
206 
~NotificationProgressBar()207 NotificationProgressBar::~NotificationProgressBar() {
208 }
209 
GetPreferredSize() const210 gfx::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 
OnPaint(gfx::Canvas * canvas)217 void 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 
252 namespace message_center {
253 
254 // NotificationView ////////////////////////////////////////////////////////////
255 
256 // static
Create(MessageCenterController * controller,const Notification & notification,bool top_level)257 NotificationView* 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 
CreateOrUpdateViews(const Notification & notification)292 void 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 
SetAccessibleName(const Notification & notification)303 void 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 
NotificationView(MessageCenterController * controller,const Notification & notification)316 NotificationView::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 
~NotificationView()360 NotificationView::~NotificationView() {
361 }
362 
GetPreferredSize() const363 gfx::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 
GetHeightForWidth(int width) const371 int 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 
Layout()405 void 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 
OnFocus()433 void NotificationView::OnFocus() {
434   MessageView::OnFocus();
435   ScrollRectToVisible(GetLocalBounds());
436 }
437 
ScrollRectToVisible(const gfx::Rect & rect)438 void 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 
GetEventHandlerForRect(const gfx::Rect & rect)444 views::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 
GetCursor(const ui::MouseEvent & event)466 gfx::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 
UpdateWithNotification(const Notification & notification)473 void NotificationView::UpdateWithNotification(
474     const Notification& notification) {
475   MessageView::UpdateWithNotification(notification);
476 
477   CreateOrUpdateViews(notification);
478   SetAccessibleName(notification);
479   Layout();
480   SchedulePaint();
481 }
482 
ButtonPressed(views::Button * sender,const ui::Event & event)483 void 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 
ClickOnNotification(const std::string & notification_id)503 void NotificationView::ClickOnNotification(const std::string& notification_id) {
504   controller_->ClickOnNotification(notification_id);
505 }
506 
RemoveNotification(const std::string & notification_id,bool by_user)507 void NotificationView::RemoveNotification(const std::string& notification_id,
508                                           bool by_user) {
509   controller_->RemoveNotification(notification_id, by_user);
510 }
511 
CreateOrUpdateTitleView(const Notification & notification)512 void 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 
CreateOrUpdateMessageView(const Notification & notification)549 void 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 
CreateOrUpdateContextMessageView(const Notification & notification)579 void 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 
CreateOrUpdateProgressBarView(const Notification & notification)609 void 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 
CreateOrUpdateListItemViews(const Notification & notification)633 void 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 
CreateOrUpdateIconView(const Notification & notification)654 void 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 
CreateOrUpdateImageView(const Notification & notification)683 void 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 
CreateOrUpdateActionButtonViews(const Notification & notification)702 void 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 
GetMessageLineLimit(int title_lines,int width) const746 int 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 
GetMessageHeight(int width,int limit) const782 int 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