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