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