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