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