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