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