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