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