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