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