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
44namespace message_center {
45
46namespace {
47
48const SkColor kNoNotificationsTextColor = SkColorSetRGB(0xb4, 0xb4, 0xb4);
49#if defined(OS_LINUX) && defined(OS_CHROMEOS)
50const SkColor kTransparentColor = SkColorSetARGB(0, 0, 0, 0);
51#endif
52const int kAnimateClearingNextNotificationDelayMS = 40;
53
54const int kDefaultAnimationDurationMs = 120;
55const int kDefaultFrameRateHz = 60;
56}  // namespace
57
58class 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
74NoNotificationMessageView::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
87NoNotificationMessageView::~NoNotificationMessageView() {
88}
89
90gfx::Size NoNotificationMessageView::GetPreferredSize() const {
91  return gfx::Size(kMinScrollViewHeight, label_->GetPreferredSize().width());
92}
93
94int NoNotificationMessageView::GetHeightForWidth(int width) const {
95  return kMinScrollViewHeight;
96}
97
98void 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.
107class 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();
152  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
173MessageListView::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
201MessageListView::~MessageListView() {
202  if (animator_.get())
203    animator_->RemoveObserver(this);
204}
205
206void 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
225void 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
248void 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
264void 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
279gfx::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
291int 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
309void 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
319void 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
328void MessageListView::SetRepositionTarget(const gfx::Rect& target) {
329  reposition_top_ = target.y();
330  fixed_height_ = GetHeightForWidth(width());
331}
332
333void 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
351void 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
364void 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
375void 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
394bool 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
402void 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
431void 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
458void 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
486bool 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
508void 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
534MessageCenterView::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
606MessageCenterView::~MessageCenterView() {
607  if (!is_closing_)
608    message_center_->RemoveObserver(this);
609}
610
611void 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
633void 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
682void 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
691void 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
698size_t MessageCenterView::NumMessageViewsForTest() const {
699  return message_list_view_->child_count();
700}
701
702void MessageCenterView::OnSettingsChanged() {
703  scroller_->InvalidateLayout();
704  PreferredSizeChanged();
705  Layout();
706}
707
708void 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
716void 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
768gfx::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
787int 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
807bool 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
817void MessageCenterView::OnMouseExited(const ui::MouseEvent& event) {
818  if (is_closing_)
819    return;
820
821  message_list_view_->ResetRepositionSession();
822  NotificationsChanged();
823}
824
825void 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
842void 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
878void 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
899void MessageCenterView::ClickOnNotification(
900    const std::string& notification_id) {
901  message_center_->ClickOnNotification(notification_id);
902}
903
904void MessageCenterView::RemoveNotification(const std::string& notification_id,
905                                           bool by_user) {
906  message_center_->RemoveNotification(notification_id, by_user);
907}
908
909scoped_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
915bool MessageCenterView::HasClickedListener(const std::string& notification_id) {
916  return message_center_->HasClickedListener(notification_id);
917}
918
919void MessageCenterView::ClickOnNotificationButton(
920    const std::string& notification_id,
921    int button_index) {
922  message_center_->ClickOnNotificationButton(notification_id, button_index);
923}
924
925void 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
944void 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
960void MessageCenterView::AnimationCanceled(const gfx::Animation* animation) {
961  DCHECK_EQ(animation, settings_transition_animation_.get());
962  AnimationEnded(animation);
963}
964
965void 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
975void 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
1003void MessageCenterView::SetNotificationViewForTest(MessageView* view) {
1004  message_list_view_->AddNotificationAt(view, 0);
1005}
1006
1007}  // namespace message_center
1008