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