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