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