Home | History | Annotate | Download | only in views
      1 // Copyright (c) 2013 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/message_center_view.h"
      6 
      7 #include <list>
      8 #include <map>
      9 
     10 #include "base/memory/weak_ptr.h"
     11 #include "base/message_loop/message_loop.h"
     12 #include "base/stl_util.h"
     13 #include "grit/ui_strings.h"
     14 #include "ui/base/animation/multi_animation.h"
     15 #include "ui/base/animation/slide_animation.h"
     16 #include "ui/base/l10n/l10n_util.h"
     17 #include "ui/gfx/canvas.h"
     18 #include "ui/gfx/insets.h"
     19 #include "ui/gfx/rect.h"
     20 #include "ui/gfx/size.h"
     21 #include "ui/message_center/message_center.h"
     22 #include "ui/message_center/message_center_style.h"
     23 #include "ui/message_center/views/message_center_button_bar.h"
     24 #include "ui/message_center/views/message_view.h"
     25 #include "ui/message_center/views/notification_view.h"
     26 #include "ui/message_center/views/notifier_settings_view.h"
     27 #include "ui/views/animation/bounds_animator.h"
     28 #include "ui/views/animation/bounds_animator_observer.h"
     29 #include "ui/views/background.h"
     30 #include "ui/views/border.h"
     31 #include "ui/views/controls/button/button.h"
     32 #include "ui/views/controls/label.h"
     33 #include "ui/views/controls/scroll_view.h"
     34 #include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
     35 #include "ui/views/layout/box_layout.h"
     36 #include "ui/views/widget/widget.h"
     37 
     38 namespace message_center {
     39 
     40 namespace {
     41 
     42 const SkColor kBorderDarkColor = SkColorSetRGB(0xaa, 0xaa, 0xaa);
     43 const SkColor kButtonTextHighlightColor = SkColorSetRGB(0x2a, 0x2a, 0x2a);
     44 const SkColor kButtonTextHoverColor = SkColorSetRGB(0x2a, 0x2a, 0x2a);
     45 const SkColor kNoNotificationsTextColor = SkColorSetRGB(0xb4, 0xb4, 0xb4);
     46 const SkColor kTransparentColor = SkColorSetARGB(0, 0, 0, 0);
     47 const int kAnimateClearingNextNotificationDelayMS = 40;
     48 const int kButtonBarBorderThickness = 1;
     49 const int kMinScrollViewHeight = 100;
     50 
     51 static const int kDefaultAnimationDurationMs = 120;
     52 static const int kDefaultFrameRateHz = 60;
     53 
     54 }  // namespace
     55 
     56 // BoundedScrollView ///////////////////////////////////////////////////////////
     57 
     58 // A custom scroll view whose height has a minimum and maximum value and whose
     59 // scroll bar disappears when not needed.
     60 class BoundedScrollView : public views::ScrollView {
     61  public:
     62   BoundedScrollView(int min_height, int max_height);
     63 
     64   // Overridden from views::View:
     65   virtual gfx::Size GetPreferredSize() OVERRIDE;
     66   virtual int GetHeightForWidth(int width) OVERRIDE;
     67   virtual void Layout() OVERRIDE;
     68 
     69  private:
     70   int min_height_;
     71   int max_height_;
     72 
     73   DISALLOW_COPY_AND_ASSIGN(BoundedScrollView);
     74 };
     75 
     76 BoundedScrollView::BoundedScrollView(int min_height, int max_height)
     77     : min_height_(min_height),
     78       max_height_(max_height) {
     79   set_notify_enter_exit_on_child(true);
     80   // Cancels the default dashed focus border.
     81   set_focus_border(NULL);
     82   set_background(
     83       views::Background::CreateSolidBackground(kMessageCenterBackgroundColor));
     84   SetVerticalScrollBar(new views::OverlayScrollBar(false));
     85 }
     86 
     87 gfx::Size BoundedScrollView::GetPreferredSize() {
     88   gfx::Size size = contents()->GetPreferredSize();
     89   size.SetToMax(gfx::Size(size.width(), min_height_));
     90   size.SetToMin(gfx::Size(size.width(), max_height_));
     91   gfx::Insets insets = GetInsets();
     92   size.Enlarge(insets.width(), insets.height());
     93   return size;
     94 }
     95 
     96 int BoundedScrollView::GetHeightForWidth(int width) {
     97   gfx::Insets insets = GetInsets();
     98   width = std::max(0, width - insets.width());
     99   int height = contents()->GetHeightForWidth(width) + insets.height();
    100   return std::min(std::max(height, min_height_), max_height_);
    101 }
    102 
    103 void BoundedScrollView::Layout() {
    104   int content_width = width();
    105   int content_height = contents()->GetHeightForWidth(content_width);
    106   if (content_height > height()) {
    107     content_width = std::max(content_width - GetScrollBarWidth(), 0);
    108     content_height = contents()->GetHeightForWidth(content_width);
    109   }
    110   if (contents()->bounds().size() != gfx::Size(content_width, content_height))
    111     contents()->SetBounds(0, 0, content_width, content_height);
    112   views::ScrollView::Layout();
    113 }
    114 
    115 class NoNotificationMessageView : public views::View {
    116  public:
    117   NoNotificationMessageView();
    118   virtual ~NoNotificationMessageView();
    119 
    120   // Overridden from views::View.
    121   virtual gfx::Size GetPreferredSize() OVERRIDE;
    122   virtual int GetHeightForWidth(int width) OVERRIDE;
    123   virtual void Layout() OVERRIDE;
    124 
    125  private:
    126   views::Label* label_;
    127 
    128   DISALLOW_COPY_AND_ASSIGN(NoNotificationMessageView);
    129 };
    130 
    131 NoNotificationMessageView::NoNotificationMessageView() {
    132   label_ = new views::Label(l10n_util::GetStringUTF16(
    133       IDS_MESSAGE_CENTER_NO_MESSAGES));
    134   label_->SetAutoColorReadabilityEnabled(false);
    135   label_->SetEnabledColor(kNoNotificationsTextColor);
    136   // Set transparent background to ensure that subpixel rendering
    137   // is disabled. See crbug.com/169056
    138 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
    139   label_->SetBackgroundColor(kTransparentColor);
    140 #endif
    141   AddChildView(label_);
    142 }
    143 
    144 NoNotificationMessageView::~NoNotificationMessageView() {
    145 }
    146 
    147 gfx::Size NoNotificationMessageView::GetPreferredSize() {
    148   return gfx::Size(kMinScrollViewHeight, label_->GetPreferredSize().width());
    149 }
    150 
    151 int NoNotificationMessageView::GetHeightForWidth(int width) {
    152   return kMinScrollViewHeight;
    153 }
    154 
    155 void NoNotificationMessageView::Layout() {
    156   int text_height = label_->GetHeightForWidth(width());
    157   int margin = (height() - text_height) / 2;
    158   label_->SetBounds(0, margin, width(), text_height);
    159 }
    160 
    161 // Displays a list of messages for rich notifications. It also supports
    162 // repositioning.
    163 class MessageListView : public views::View,
    164                         public views::BoundsAnimatorObserver {
    165  public:
    166   explicit MessageListView(MessageCenterView* message_center_view,
    167                            bool top_down);
    168   virtual ~MessageListView();
    169 
    170   void AddNotificationAt(views::View* view, int i);
    171   void RemoveNotificationAt(int i);
    172   void UpdateNotificationAt(views::View* view, int i);
    173   void SetRepositionTarget(const gfx::Rect& target_rect);
    174   void ResetRepositionSession();
    175   void ClearAllNotifications(const gfx::Rect& visible_scroll_rect);
    176 
    177  protected:
    178   // Overridden from views::View.
    179   virtual void Layout() OVERRIDE;
    180   virtual gfx::Size GetPreferredSize() OVERRIDE;
    181   virtual int GetHeightForWidth(int width) OVERRIDE;
    182   virtual void PaintChildren(gfx::Canvas* canvas) OVERRIDE;
    183   virtual void ReorderChildLayers(ui::Layer* parent_layer) OVERRIDE;
    184 
    185   // Overridden from views::BoundsAnimatorObserver.
    186   virtual void OnBoundsAnimatorProgressed(
    187       views::BoundsAnimator* animator) OVERRIDE;
    188   virtual void OnBoundsAnimatorDone(views::BoundsAnimator* animator) OVERRIDE;
    189 
    190  private:
    191   // Returns the actual index for child of |index|.
    192   // MessageListView allows to slide down upper notifications, which means
    193   // that the upper ones should come above the lower ones if top_down is not
    194   // enabled. To achieve this, inversed order is adopted. The top most
    195   // notification is the last child, and the bottom most notification is the
    196   // first child.
    197   int GetActualIndex(int index);
    198   bool IsValidChild(views::View* child);
    199   void DoUpdateIfPossible();
    200 
    201   // Animates all notifications below target upwards to align with the top of
    202   // the last closed notification.
    203   void AnimateNotificationsBelowTarget();
    204   // Animates all notifications above target downwards to align with the top of
    205   // the last closed notification.
    206   void AnimateNotificationsAboveTarget();
    207 
    208   // Schedules animation for a child to the specified position. Returns false
    209   // if |child| will disappear after the animation.
    210   bool AnimateChild(views::View* child, int top, int height);
    211 
    212   // Animate clearing one notification.
    213   void AnimateClearingOneNotification();
    214   MessageCenterView* message_center_view() const {
    215     return message_center_view_;
    216   }
    217 
    218   MessageCenterView* message_center_view_;  // Weak reference.
    219   // The top position of the reposition target rectangle.
    220   int reposition_top_;
    221   int fixed_height_;
    222   bool has_deferred_task_;
    223   bool clear_all_started_;
    224   bool top_down_;
    225   std::set<views::View*> adding_views_;
    226   std::set<views::View*> deleting_views_;
    227   std::set<views::View*> deleted_when_done_;
    228   std::list<views::View*> clearing_all_views_;
    229   scoped_ptr<views::BoundsAnimator> animator_;
    230   base::WeakPtrFactory<MessageListView> weak_ptr_factory_;
    231 
    232   DISALLOW_COPY_AND_ASSIGN(MessageListView);
    233 };
    234 
    235 MessageListView::MessageListView(MessageCenterView* message_center_view,
    236                                  bool top_down)
    237     : message_center_view_(message_center_view),
    238       reposition_top_(-1),
    239       fixed_height_(0),
    240       has_deferred_task_(false),
    241       clear_all_started_(false),
    242       top_down_(top_down),
    243       weak_ptr_factory_(this) {
    244   views::BoxLayout* layout =
    245       new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1);
    246   layout->set_spread_blank_space(true);
    247   SetLayoutManager(layout);
    248 
    249   // Set the margin to 0 for the layout. BoxLayout assumes the same margin
    250   // for top and bottom, but the bottom margin here should be smaller
    251   // because of the shadow of message view. Use an empty border instead
    252   // to provide this margin.
    253   gfx::Insets shadow_insets = MessageView::GetShadowInsets();
    254   set_background(views::Background::CreateSolidBackground(
    255       kMessageCenterBackgroundColor));
    256   set_border(views::Border::CreateEmptyBorder(
    257       kMarginBetweenItems - shadow_insets.top(), /* top */
    258       kMarginBetweenItems - shadow_insets.left(), /* left */
    259       kMarginBetweenItems - shadow_insets.bottom(),  /* bottom */
    260       kMarginBetweenItems - shadow_insets.right() /* right */ ));
    261 }
    262 
    263 MessageListView::~MessageListView() {
    264   if (animator_.get())
    265     animator_->RemoveObserver(this);
    266 }
    267 
    268 void MessageListView::Layout() {
    269   if (animator_.get())
    270     return;
    271 
    272   gfx::Rect child_area = GetContentsBounds();
    273   int top = child_area.y();
    274   int between_items =
    275       kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
    276 
    277   for (int i = 0; i < child_count(); ++i) {
    278     views::View* child = child_at(i);
    279     if (!child->visible())
    280       continue;
    281     int height = child->GetHeightForWidth(child_area.width());
    282     child->SetBounds(child_area.x(), top, child_area.width(), height);
    283     top += height + between_items;
    284   }
    285 }
    286 
    287 void MessageListView::AddNotificationAt(views::View* view, int i) {
    288   AddChildViewAt(view, GetActualIndex(i));
    289   if (GetContentsBounds().IsEmpty())
    290     return;
    291 
    292   adding_views_.insert(view);
    293   DoUpdateIfPossible();
    294 }
    295 
    296 void MessageListView::RemoveNotificationAt(int i) {
    297   views::View* child = child_at(GetActualIndex(i));
    298   if (GetContentsBounds().IsEmpty()) {
    299     delete child;
    300   } else {
    301     if (child->layer()) {
    302       deleting_views_.insert(child);
    303     } else {
    304       if (animator_.get())
    305         animator_->StopAnimatingView(child);
    306       delete child;
    307     }
    308     DoUpdateIfPossible();
    309   }
    310 }
    311 
    312 void MessageListView::UpdateNotificationAt(views::View* view, int i) {
    313   int actual_index = GetActualIndex(i);
    314   views::View* child = child_at(actual_index);
    315   if (animator_.get())
    316     animator_->StopAnimatingView(child);
    317   gfx::Rect old_bounds = child->bounds();
    318   if (deleting_views_.find(child) != deleting_views_.end())
    319     deleting_views_.erase(child);
    320   if (deleted_when_done_.find(child) != deleted_when_done_.end())
    321     deleted_when_done_.erase(child);
    322   delete child;
    323   AddChildViewAt(view, actual_index);
    324   view->SetBounds(old_bounds.x(), old_bounds.y(), old_bounds.width(),
    325                   view->GetHeightForWidth(old_bounds.width()));
    326   DoUpdateIfPossible();
    327 }
    328 
    329 gfx::Size MessageListView::GetPreferredSize() {
    330   int width = 0;
    331   for (int i = 0; i < child_count(); i++) {
    332     views::View* child = child_at(i);
    333     if (IsValidChild(child))
    334       width = std::max(width, child->GetPreferredSize().width());
    335   }
    336 
    337   return gfx::Size(width + GetInsets().width(),
    338                    GetHeightForWidth(width + GetInsets().width()));
    339 }
    340 
    341 int MessageListView::GetHeightForWidth(int width) {
    342   if (fixed_height_ > 0)
    343     return fixed_height_;
    344 
    345   width -= GetInsets().width();
    346   int height = 0;
    347   int padding = 0;
    348   for (int i = 0; i < child_count(); ++i) {
    349     views::View* child = child_at(i);
    350     if (!IsValidChild(child))
    351       continue;
    352     height += child->GetHeightForWidth(width) + padding;
    353     padding = kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
    354   }
    355 
    356   return height + GetInsets().height();
    357 }
    358 
    359 void MessageListView::PaintChildren(gfx::Canvas* canvas) {
    360   // Paint in the inversed order. Otherwise upper notification may be
    361   // hidden by the lower one.
    362   for (int i = child_count() - 1; i >= 0; --i) {
    363     if (!child_at(i)->layer())
    364       child_at(i)->Paint(canvas);
    365   }
    366 }
    367 
    368 void MessageListView::ReorderChildLayers(ui::Layer* parent_layer) {
    369   // Reorder children to stack the last child layer at the top. Otherwise
    370   // upper notification may be hidden by the lower one.
    371   for (int i = 0; i < child_count(); ++i) {
    372     if (child_at(i)->layer())
    373       parent_layer->StackAtBottom(child_at(i)->layer());
    374   }
    375 }
    376 
    377 void MessageListView::SetRepositionTarget(const gfx::Rect& target) {
    378   reposition_top_ = target.y();
    379   fixed_height_ = GetHeightForWidth(width());
    380 }
    381 
    382 void MessageListView::ResetRepositionSession() {
    383   // Don't call DoUpdateIfPossible(), but let Layout() do the task without
    384   // animation. Reset will cause the change of the bubble size itself, and
    385   // animation from the old location will look weird.
    386   if (reposition_top_ >= 0 && animator_.get()) {
    387     has_deferred_task_ = false;
    388     // cancel cause OnBoundsAnimatorDone which deletes |deleted_when_done_|.
    389     animator_->Cancel();
    390     STLDeleteContainerPointers(deleting_views_.begin(), deleting_views_.end());
    391     deleting_views_.clear();
    392     adding_views_.clear();
    393     animator_.reset();
    394   }
    395 
    396   reposition_top_ = -1;
    397   fixed_height_ = 0;
    398 }
    399 
    400 void MessageListView::ClearAllNotifications(
    401     const gfx::Rect& visible_scroll_rect) {
    402   for (int i = 0; i < child_count(); ++i) {
    403     views::View* child = child_at(i);
    404     if (!child->visible())
    405       continue;
    406     if (gfx::IntersectRects(child->bounds(), visible_scroll_rect).IsEmpty())
    407       continue;
    408     clearing_all_views_.push_back(child);
    409   }
    410   DoUpdateIfPossible();
    411 }
    412 
    413 void MessageListView::OnBoundsAnimatorProgressed(
    414     views::BoundsAnimator* animator) {
    415   DCHECK_EQ(animator_.get(), animator);
    416   for (std::set<views::View*>::iterator iter = deleted_when_done_.begin();
    417        iter != deleted_when_done_.end(); ++iter) {
    418     const ui::SlideAnimation* animation = animator->GetAnimationForView(*iter);
    419     if (animation)
    420       (*iter)->layer()->SetOpacity(animation->CurrentValueBetween(1.0, 0.0));
    421   }
    422 }
    423 
    424 void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
    425   STLDeleteContainerPointers(
    426       deleted_when_done_.begin(), deleted_when_done_.end());
    427   deleted_when_done_.clear();
    428 
    429   if (clear_all_started_) {
    430     clear_all_started_ = false;
    431     message_center_view()->OnAllNotificationsCleared();
    432   }
    433 
    434   if (has_deferred_task_) {
    435     has_deferred_task_ = false;
    436     DoUpdateIfPossible();
    437   }
    438 
    439   if (GetWidget())
    440     GetWidget()->SynthesizeMouseMoveEvent();
    441 }
    442 
    443 int MessageListView::GetActualIndex(int index) {
    444   for (int i = 0; i < child_count() && i <= index; ++i)
    445     index += IsValidChild(child_at(i)) ? 0 : 1;
    446   return std::min(index, child_count());
    447 }
    448 
    449 bool MessageListView::IsValidChild(views::View* child) {
    450   return child->visible() &&
    451          deleting_views_.find(child) == deleting_views_.end() &&
    452          deleted_when_done_.find(child) == deleted_when_done_.end();
    453 }
    454 
    455 void MessageListView::DoUpdateIfPossible() {
    456   gfx::Rect child_area = GetContentsBounds();
    457   if (child_area.IsEmpty())
    458     return;
    459 
    460   if (animator_.get() && animator_->IsAnimating()) {
    461     has_deferred_task_ = true;
    462     return;
    463   }
    464 
    465   if (!animator_.get()) {
    466     animator_.reset(new views::BoundsAnimator(this));
    467     animator_->AddObserver(this);
    468   }
    469 
    470   if (!clearing_all_views_.empty()) {
    471     AnimateClearingOneNotification();
    472     return;
    473   }
    474 
    475   if (top_down_)
    476     AnimateNotificationsBelowTarget();
    477   else
    478     AnimateNotificationsAboveTarget();
    479 
    480   adding_views_.clear();
    481   deleting_views_.clear();
    482 }
    483 
    484 void MessageListView::AnimateNotificationsBelowTarget() {
    485   int last_index = -1;
    486   for (int i = 0; i < child_count(); ++i) {
    487     views::View* child = child_at(i);
    488     if (!IsValidChild(child)) {
    489       AnimateChild(child, child->y(), child->height());
    490     } else if (reposition_top_ < 0 || child->y() > reposition_top_) {
    491       // Find first notification below target (or all notifications if no
    492       // target).
    493       last_index = i;
    494       break;
    495     }
    496   }
    497   if (last_index > 0) {
    498     int between_items =
    499         kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
    500     int top = (reposition_top_ > 0) ? reposition_top_ : GetInsets().top();
    501 
    502     for (int i = last_index; i < child_count(); ++i) {
    503       // Animate notifications below target upwards.
    504       views::View* child = child_at(i);
    505       if (AnimateChild(child, top, child->height()))
    506         top += child->height() + between_items;
    507     }
    508   }
    509 }
    510 
    511 void MessageListView::AnimateNotificationsAboveTarget() {
    512   int last_index = -1;
    513   for (int i = child_count() - 1; i >= 0; --i) {
    514     views::View* child = child_at(i);
    515     if (!IsValidChild(child)) {
    516       AnimateChild(child, child->y(), child->height());
    517     } else if (reposition_top_ < 0 || child->y() < reposition_top_) {
    518       // Find first notification above target (or all notifications if no
    519       // target).
    520       last_index = i;
    521       break;
    522     }
    523   }
    524   if (last_index > 0) {
    525     int between_items =
    526         kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
    527     int bottom = (reposition_top_ > 0)
    528                      ? reposition_top_ + child_at(last_index)->height()
    529                      : GetHeightForWidth(width()) - GetInsets().bottom();
    530     for (int i = last_index; i >= 0; --i) {
    531       // Animate notifications above target downwards.
    532       views::View* child = child_at(i);
    533       if (AnimateChild(child, bottom - child->height(), child->height()))
    534         bottom -= child->height() + between_items;
    535     }
    536   }
    537 }
    538 
    539 bool MessageListView::AnimateChild(views::View* child, int top, int height) {
    540   gfx::Rect child_area = GetContentsBounds();
    541   if (adding_views_.find(child) != adding_views_.end()) {
    542     child->SetBounds(child_area.right(), top, child_area.width(), height);
    543     animator_->AnimateViewTo(
    544         child, gfx::Rect(child_area.x(), top, child_area.width(), height));
    545   } else if (deleting_views_.find(child) != deleting_views_.end()) {
    546     DCHECK(child->layer());
    547     // No moves, but animate to fade-out.
    548     animator_->AnimateViewTo(child, child->bounds());
    549     deleted_when_done_.insert(child);
    550     return false;
    551   } else {
    552     gfx::Rect target(child_area.x(), top, child_area.width(), height);
    553     if (child->bounds().origin() != target.origin())
    554       animator_->AnimateViewTo(child, target);
    555     else
    556       child->SetBoundsRect(target);
    557   }
    558   return true;
    559 }
    560 
    561 void MessageListView::AnimateClearingOneNotification() {
    562   DCHECK(!clearing_all_views_.empty());
    563 
    564   clear_all_started_ = true;
    565 
    566   views::View* child = clearing_all_views_.front();
    567   clearing_all_views_.pop_front();
    568 
    569   // Slide from left to right.
    570   gfx::Rect new_bounds = child->bounds();
    571   new_bounds.set_x(new_bounds.right() + kMarginBetweenItems);
    572   animator_->AnimateViewTo(child, new_bounds);
    573 
    574   // Schedule to start sliding out next notification after a short delay.
    575   if (!clearing_all_views_.empty()) {
    576     base::MessageLoop::current()->PostDelayedTask(
    577         FROM_HERE,
    578         base::Bind(&MessageListView::AnimateClearingOneNotification,
    579                    weak_ptr_factory_.GetWeakPtr()),
    580         base::TimeDelta::FromMilliseconds(
    581             kAnimateClearingNextNotificationDelayMS));
    582   }
    583 }
    584 
    585 // MessageCenterView ///////////////////////////////////////////////////////////
    586 
    587 MessageCenterView::MessageCenterView(MessageCenter* message_center,
    588                                      MessageCenterTray* tray,
    589                                      int max_height,
    590                                      bool initially_settings_visible,
    591                                      bool top_down)
    592     : message_center_(message_center),
    593       tray_(tray),
    594       top_down_(top_down),
    595       settings_visible_(initially_settings_visible),
    596       source_view_(NULL),
    597       source_height_(0),
    598       target_view_(NULL),
    599       target_height_(0),
    600       is_closing_(false) {
    601   message_center_->AddObserver(this);
    602   set_notify_enter_exit_on_child(true);
    603   set_background(views::Background::CreateSolidBackground(
    604       kMessageCenterBackgroundColor));
    605 
    606   NotifierSettingsProvider* notifier_settings_provider =
    607       message_center_->GetNotifierSettingsProvider();
    608   button_bar_ = new MessageCenterButtonBar(this,
    609                                            message_center,
    610                                            notifier_settings_provider,
    611                                            initially_settings_visible);
    612 
    613   const int button_height = button_bar_->GetPreferredSize().height();
    614 
    615   scroller_ =
    616       new BoundedScrollView(kMinScrollViewHeight, max_height - button_height);
    617 
    618   if (get_use_acceleration_when_possible()) {
    619     scroller_->SetPaintToLayer(true);
    620     scroller_->SetFillsBoundsOpaquely(false);
    621     scroller_->layer()->SetMasksToBounds(true);
    622   }
    623 
    624   message_list_view_ = new MessageListView(this, top_down);
    625   no_notifications_message_view_ = new NoNotificationMessageView();
    626   // Set the default visibility to false, otherwise the notification has slide
    627   // in animation when the center is shown.
    628   no_notifications_message_view_->SetVisible(false);
    629   message_list_view_->AddChildView(no_notifications_message_view_);
    630   scroller_->SetContents(message_list_view_);
    631 
    632   settings_view_ = new NotifierSettingsView(notifier_settings_provider);
    633 
    634   if (initially_settings_visible)
    635     scroller_->SetVisible(false);
    636   else
    637     settings_view_->SetVisible(false);
    638 
    639   AddChildView(scroller_);
    640   AddChildView(settings_view_);
    641   AddChildView(button_bar_);
    642 }
    643 
    644 MessageCenterView::~MessageCenterView() {
    645   if (!is_closing_)
    646     message_center_->RemoveObserver(this);
    647 }
    648 
    649 void MessageCenterView::SetNotifications(
    650     const NotificationList::Notifications& notifications)  {
    651   if (is_closing_)
    652     return;
    653 
    654   message_views_.clear();
    655   int index = 0;
    656   for (NotificationList::Notifications::const_iterator iter =
    657            notifications.begin(); iter != notifications.end();
    658        ++iter, ++index) {
    659     AddNotificationAt(*(*iter), index);
    660     if (message_views_.size() >= kMaxVisibleMessageCenterNotifications)
    661       break;
    662   }
    663   NotificationsChanged();
    664   scroller_->RequestFocus();
    665 }
    666 
    667 void MessageCenterView::SetSettingsVisible(bool visible) {
    668   if (is_closing_)
    669     return;
    670 
    671   if (visible == settings_visible_)
    672     return;
    673 
    674   settings_visible_ = visible;
    675 
    676   if (visible) {
    677     source_view_ = scroller_;
    678     target_view_ = settings_view_;
    679   } else {
    680     source_view_ = settings_view_;
    681     target_view_ = scroller_;
    682   }
    683   source_height_ = source_view_->GetHeightForWidth(width());
    684   target_height_ = target_view_->GetHeightForWidth(width());
    685 
    686   ui::MultiAnimation::Parts parts;
    687   // First part: slide resize animation.
    688   parts.push_back(ui::MultiAnimation::Part(
    689       (source_height_ == target_height_) ? 0 : kDefaultAnimationDurationMs,
    690       ui::Tween::EASE_OUT));
    691   // Second part: fade-out the source_view.
    692   if (source_view_->layer()) {
    693     parts.push_back(ui::MultiAnimation::Part(
    694         kDefaultAnimationDurationMs, ui::Tween::LINEAR));
    695   } else {
    696     parts.push_back(ui::MultiAnimation::Part());
    697   }
    698   // Third part: fade-in the target_view.
    699   if (target_view_->layer()) {
    700     parts.push_back(ui::MultiAnimation::Part(
    701         kDefaultAnimationDurationMs, ui::Tween::LINEAR));
    702     target_view_->layer()->SetOpacity(0);
    703     target_view_->SetVisible(true);
    704   } else {
    705     parts.push_back(ui::MultiAnimation::Part());
    706   }
    707   settings_transition_animation_.reset(new ui::MultiAnimation(
    708       parts, base::TimeDelta::FromMicroseconds(1000000 / kDefaultFrameRateHz)));
    709   settings_transition_animation_->set_delegate(this);
    710   settings_transition_animation_->set_continuous(false);
    711   settings_transition_animation_->Start();
    712 
    713   button_bar_->SetBackArrowVisible(visible);
    714 }
    715 
    716 void MessageCenterView::ClearAllNotifications() {
    717   if (is_closing_)
    718     return;
    719 
    720   scroller_->SetEnabled(false);
    721   button_bar_->SetAllButtonsEnabled(false);
    722   message_list_view_->ClearAllNotifications(scroller_->GetVisibleRect());
    723 }
    724 
    725 void MessageCenterView::OnAllNotificationsCleared() {
    726   scroller_->SetEnabled(true);
    727   button_bar_->SetAllButtonsEnabled(true);
    728   button_bar_->SetCloseAllButtonEnabled(false);
    729   message_center_->RemoveAllNotifications(true);  // Action by user.
    730 }
    731 
    732 size_t MessageCenterView::NumMessageViewsForTest() const {
    733   return message_list_view_->child_count();
    734 }
    735 
    736 void MessageCenterView::OnSettingsChanged() {
    737   scroller_->InvalidateLayout();
    738   PreferredSizeChanged();
    739   Layout();
    740 }
    741 
    742 void MessageCenterView::SetIsClosing(bool is_closing) {
    743   is_closing_ = is_closing;
    744   if (is_closing)
    745     message_center_->RemoveObserver(this);
    746   else
    747     message_center_->AddObserver(this);
    748 }
    749 
    750 void MessageCenterView::Layout() {
    751   if (is_closing_)
    752     return;
    753 
    754   int button_height = button_bar_->GetHeightForWidth(width()) +
    755                       button_bar_->GetInsets().height();
    756   // Skip unnecessary re-layout of contents during the resize animation.
    757   if (settings_transition_animation_ &&
    758       settings_transition_animation_->is_animating() &&
    759       settings_transition_animation_->current_part_index() == 0) {
    760     if (!top_down_)
    761       button_bar_->SetBounds(
    762           0, height() - button_height, width(), button_height);
    763     return;
    764   }
    765 
    766   scroller_->SetBounds(0,
    767                        top_down_ ? button_height : 0,
    768                        width(),
    769                        height() - button_height);
    770   settings_view_->SetBounds(0,
    771                             top_down_ ? button_height : 0,
    772                             width(),
    773                             height() - button_height);
    774 
    775   bool is_scrollable = false;
    776   if (scroller_->visible())
    777     is_scrollable = scroller_->height() < message_list_view_->height();
    778   else
    779     is_scrollable = settings_view_->IsScrollable();
    780 
    781   if (is_scrollable && !button_bar_->border()) {
    782     // Draw separator line on the top of the button bar if it is on the bottom
    783     // or draw it at the bottom if the bar is on the top.
    784     button_bar_->set_border(views::Border::CreateSolidSidedBorder(
    785         top_down_ ? 0 : 1,
    786         0,
    787         top_down_ ? 1 : 0,
    788         0,
    789         kFooterDelimiterColor));
    790     button_bar_->SchedulePaint();
    791   } else if (!is_scrollable && button_bar_->border()) {
    792     button_bar_->set_border(NULL);
    793     button_bar_->SchedulePaint();
    794   }
    795   button_bar_->SetBounds(0,
    796                          top_down_ ? 0 : height() - button_height,
    797                          width(),
    798                          button_height);
    799   if (GetWidget())
    800     GetWidget()->GetRootView()->SchedulePaint();
    801 }
    802 
    803 gfx::Size MessageCenterView::GetPreferredSize() {
    804   if (settings_transition_animation_ &&
    805       settings_transition_animation_->is_animating()) {
    806     int content_width = std::max(source_view_->GetPreferredSize().width(),
    807                                  target_view_->GetPreferredSize().width());
    808     int width = std::max(content_width,
    809                          button_bar_->GetPreferredSize().width());
    810     return gfx::Size(width, GetHeightForWidth(width));
    811   }
    812 
    813   int width = 0;
    814   for (int i = 0; i < child_count(); ++i) {
    815     views::View* child = child_at(0);
    816     if (child->visible())
    817       width = std::max(width, child->GetPreferredSize().width());
    818   }
    819   return gfx::Size(width, GetHeightForWidth(width));
    820 }
    821 
    822 int MessageCenterView::GetHeightForWidth(int width) {
    823   if (settings_transition_animation_ &&
    824       settings_transition_animation_->is_animating()) {
    825     int content_height = target_height_;
    826     if (settings_transition_animation_->current_part_index() == 0) {
    827       content_height = settings_transition_animation_->CurrentValueBetween(
    828           source_height_, target_height_);
    829     }
    830     return button_bar_->GetHeightForWidth(width) + content_height;
    831   }
    832 
    833   int content_height = 0;
    834   if (scroller_->visible())
    835     content_height += scroller_->GetHeightForWidth(width);
    836   else
    837     content_height += settings_view_->GetHeightForWidth(width);
    838   return button_bar_->GetHeightForWidth(width) +
    839          button_bar_->GetInsets().height() + content_height;
    840 }
    841 
    842 bool MessageCenterView::OnMouseWheel(const ui::MouseWheelEvent& event) {
    843   // Do not rely on the default scroll event handler of ScrollView because
    844   // the scroll happens only when the focus is on the ScrollView. The
    845   // notification center will allow the scrolling even when the focus is on
    846   // the buttons.
    847   if (scroller_->bounds().Contains(event.location()))
    848     return scroller_->OnMouseWheel(event);
    849   return views::View::OnMouseWheel(event);
    850 }
    851 
    852 void MessageCenterView::OnMouseExited(const ui::MouseEvent& event) {
    853   if (is_closing_)
    854     return;
    855 
    856   message_list_view_->ResetRepositionSession();
    857   NotificationsChanged();
    858 }
    859 
    860 void MessageCenterView::OnNotificationAdded(const std::string& id) {
    861   int index = 0;
    862   const NotificationList::Notifications& notifications =
    863       message_center_->GetNotifications();
    864   for (NotificationList::Notifications::const_iterator iter =
    865            notifications.begin(); iter != notifications.end();
    866        ++iter, ++index) {
    867     if ((*iter)->id() == id) {
    868       AddNotificationAt(*(*iter), index);
    869       break;
    870     }
    871     if (message_views_.size() >= kMaxVisibleMessageCenterNotifications)
    872       break;
    873   }
    874   NotificationsChanged();
    875 }
    876 
    877 void MessageCenterView::OnNotificationRemoved(const std::string& id,
    878                                               bool by_user) {
    879   for (size_t i = 0; i < message_views_.size(); ++i) {
    880     if (message_views_[i]->notification_id() == id) {
    881       if (by_user) {
    882         message_list_view_->SetRepositionTarget(message_views_[i]->bounds());
    883         // Moves the keyboard focus to the next notification if the removed
    884         // notification is focused so that the user can dismiss notifications
    885         // without re-focusing by tab key.
    886         if (message_views_.size() > 1) {
    887           views::View* focused_view = GetFocusManager()->GetFocusedView();
    888           if (message_views_[i]->IsCloseButtonFocused() ||
    889               focused_view == message_views_[i]) {
    890             size_t next_index = i + 1;
    891             if (next_index >= message_views_.size())
    892               next_index = message_views_.size() - 2;
    893             if (focused_view == message_views_[i])
    894               message_views_[next_index]->RequestFocus();
    895             else
    896               message_views_[next_index]->RequestFocusOnCloseButton();
    897           }
    898         }
    899       }
    900       message_list_view_->RemoveNotificationAt(i);
    901       message_views_.erase(message_views_.begin() + i);
    902       NotificationsChanged();
    903       break;
    904     }
    905   }
    906 }
    907 
    908 void MessageCenterView::OnNotificationUpdated(const std::string& id) {
    909   const NotificationList::Notifications& notifications =
    910       message_center_->GetNotifications();
    911   size_t index = 0;
    912   for (NotificationList::Notifications::const_iterator iter =
    913            notifications.begin();
    914        iter != notifications.end() && index < message_views_.size();
    915        ++iter, ++index) {
    916     DCHECK((*iter)->id() == message_views_[index]->notification_id());
    917     if ((*iter)->id() == id) {
    918       MessageView* view =
    919           NotificationView::Create(*(*iter),
    920                                    message_center_,
    921                                    tray_,
    922                                    true,   // Create expanded.
    923                                    false); // Not creating a top-level
    924                                            // notification.
    925       view->set_scroller(scroller_);
    926       message_list_view_->UpdateNotificationAt(view, index);
    927       message_views_[index] = view;
    928       NotificationsChanged();
    929       break;
    930     }
    931   }
    932 }
    933 
    934 void MessageCenterView::AnimationEnded(const ui::Animation* animation) {
    935   DCHECK_EQ(animation, settings_transition_animation_.get());
    936 
    937   source_view_->SetVisible(false);
    938   target_view_->SetVisible(true);
    939   if (source_view_->layer())
    940     source_view_->layer()->SetOpacity(1.0);
    941   if (target_view_->layer())
    942     target_view_->layer()->SetOpacity(1.0);
    943   settings_transition_animation_.reset();
    944   PreferredSizeChanged();
    945   Layout();
    946 }
    947 
    948 void MessageCenterView::AnimationProgressed(const ui::Animation* animation) {
    949   DCHECK_EQ(animation, settings_transition_animation_.get());
    950   PreferredSizeChanged();
    951   if (settings_transition_animation_->current_part_index() == 1 &&
    952       source_view_->layer()) {
    953     source_view_->layer()->SetOpacity(
    954         1.0 - settings_transition_animation_->GetCurrentValue());
    955     SchedulePaint();
    956   } else if (settings_transition_animation_->current_part_index() == 2 &&
    957              target_view_->layer()) {
    958     target_view_->layer()->SetOpacity(
    959         settings_transition_animation_->GetCurrentValue());
    960     SchedulePaint();
    961   }
    962 }
    963 
    964 void MessageCenterView::AnimationCanceled(const ui::Animation* animation) {
    965   DCHECK_EQ(animation, settings_transition_animation_.get());
    966   AnimationEnded(animation);
    967 }
    968 
    969 void MessageCenterView::AddNotificationAt(const Notification& notification,
    970                                           int index) {
    971   // NotificationViews are expanded by default here until
    972   // http://crbug.com/217902 is fixed. TODO(dharcourt): Fix.
    973   MessageView* view =
    974       NotificationView::Create(notification,
    975                                message_center_,
    976                                tray_,
    977                                true,    // Create expanded.
    978                                false);  // Not creating a top-level
    979                                         // notification.
    980   view->set_scroller(scroller_);
    981   message_views_.insert(message_views_.begin() + index, view);
    982   message_list_view_->AddNotificationAt(view, index);
    983   message_center_->DisplayedNotification(notification.id());
    984 }
    985 
    986 void MessageCenterView::NotificationsChanged() {
    987   bool no_message_views = message_views_.empty();
    988 
    989   no_notifications_message_view_->SetVisible(no_message_views);
    990   button_bar_->SetCloseAllButtonEnabled(!no_message_views);
    991   scroller_->set_focusable(!no_message_views);
    992 
    993   scroller_->InvalidateLayout();
    994   PreferredSizeChanged();
    995   Layout();
    996 }
    997 
    998 void MessageCenterView::SetNotificationViewForTest(views::View* view) {
    999   message_list_view_->AddNotificationAt(view, 0);
   1000 }
   1001 
   1002 }  // namespace message_center
   1003