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.
5 #include "ui/message_center/views/message_center_view.h"
10 #include "base/memory/weak_ptr.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/stl_util.h"
13 #include "grit/ui_resources.h"
14 #include "grit/ui_strings.h"
15 #include "ui/base/l10n/l10n_util.h"
16 #include "ui/base/resource/resource_bundle.h"
17 #include "ui/gfx/animation/multi_animation.h"
18 #include "ui/gfx/animation/slide_animation.h"
19 #include "ui/gfx/canvas.h"
20 #include "ui/gfx/insets.h"
21 #include "ui/gfx/rect.h"
22 #include "ui/gfx/size.h"
23 #include "ui/message_center/message_center.h"
24 #include "ui/message_center/message_center_style.h"
25 #include "ui/message_center/message_center_tray.h"
26 #include "ui/message_center/message_center_types.h"
27 #include "ui/message_center/message_center_util.h"
28 #include "ui/message_center/views/message_center_button_bar.h"
29 #include "ui/message_center/views/message_view.h"
30 #include "ui/message_center/views/message_view_context_menu_controller.h"
31 #include "ui/message_center/views/notification_view.h"
32 #include "ui/message_center/views/notifier_settings_view.h"
33 #include "ui/views/animation/bounds_animator.h"
34 #include "ui/views/animation/bounds_animator_observer.h"
35 #include "ui/views/background.h"
36 #include "ui/views/border.h"
37 #include "ui/views/controls/button/button.h"
38 #include "ui/views/controls/label.h"
39 #include "ui/views/controls/scroll_view.h"
40 #include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
41 #include "ui/views/layout/box_layout.h"
42 #include "ui/views/layout/fill_layout.h"
43 #include "ui/views/widget/widget.h"
45 namespace message_center {
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);
53 const int kAnimateClearingNextNotificationDelayMS = 40;
54 const int kMinScrollViewHeight = 100;
56 const int kDefaultAnimationDurationMs = 120;
57 const int kDefaultFrameRateHz = 60;
60 // BoundedScrollView ///////////////////////////////////////////////////////////
62 // A custom scroll view whose height has a minimum and maximum value and whose
63 // scroll bar disappears when not needed.
64 class BoundedScrollView : public views::ScrollView {
66 BoundedScrollView(int min_height, int max_height);
68 // Overridden from views::View:
69 virtual gfx::Size GetPreferredSize() OVERRIDE;
70 virtual int GetHeightForWidth(int width) OVERRIDE;
71 virtual void Layout() OVERRIDE;
77 DISALLOW_COPY_AND_ASSIGN(BoundedScrollView);
80 BoundedScrollView::BoundedScrollView(int min_height, int max_height)
81 : min_height_(min_height),
82 max_height_(max_height) {
83 set_notify_enter_exit_on_child(true);
85 views::Background::CreateSolidBackground(kMessageCenterBackgroundColor));
86 SetVerticalScrollBar(new views::OverlayScrollBar(false));
89 gfx::Size BoundedScrollView::GetPreferredSize() {
90 gfx::Size size = contents()->GetPreferredSize();
91 size.SetToMax(gfx::Size(size.width(), min_height_));
92 size.SetToMin(gfx::Size(size.width(), max_height_));
93 gfx::Insets insets = GetInsets();
94 size.Enlarge(insets.width(), insets.height());
98 int BoundedScrollView::GetHeightForWidth(int width) {
99 gfx::Insets insets = GetInsets();
100 width = std::max(0, width - insets.width());
101 int height = contents()->GetHeightForWidth(width) + insets.height();
102 return std::min(std::max(height, min_height_), max_height_);
105 void BoundedScrollView::Layout() {
106 int content_width = width();
107 int content_height = contents()->GetHeightForWidth(content_width);
108 if (content_height > height()) {
109 content_width = std::max(content_width - GetScrollBarWidth(), 0);
110 content_height = contents()->GetHeightForWidth(content_width);
112 if (contents()->bounds().size() != gfx::Size(content_width, content_height))
113 contents()->SetBounds(0, 0, content_width, content_height);
114 views::ScrollView::Layout();
117 class NoNotificationMessageView : public views::View {
119 NoNotificationMessageView();
120 virtual ~NoNotificationMessageView();
122 // Overridden from views::View.
123 virtual gfx::Size GetPreferredSize() OVERRIDE;
124 virtual int GetHeightForWidth(int width) OVERRIDE;
125 virtual void Layout() OVERRIDE;
128 views::Label* label_;
130 DISALLOW_COPY_AND_ASSIGN(NoNotificationMessageView);
133 NoNotificationMessageView::NoNotificationMessageView() {
134 label_ = new views::Label(l10n_util::GetStringUTF16(
135 IDS_MESSAGE_CENTER_NO_MESSAGES));
136 label_->SetAutoColorReadabilityEnabled(false);
137 label_->SetEnabledColor(kNoNotificationsTextColor);
138 // Set transparent background to ensure that subpixel rendering
139 // is disabled. See crbug.com/169056
140 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
141 label_->SetBackgroundColor(kTransparentColor);
143 AddChildView(label_);
146 NoNotificationMessageView::~NoNotificationMessageView() {
149 gfx::Size NoNotificationMessageView::GetPreferredSize() {
150 return gfx::Size(kMinScrollViewHeight, label_->GetPreferredSize().width());
153 int NoNotificationMessageView::GetHeightForWidth(int width) {
154 return kMinScrollViewHeight;
157 void NoNotificationMessageView::Layout() {
158 int text_height = label_->GetHeightForWidth(width());
159 int margin = (height() - text_height) / 2;
160 label_->SetBounds(0, margin, width(), text_height);
163 // Displays a list of messages for rich notifications. Functions as an array of
164 // MessageViews and animates them on transitions. It also supports
166 class MessageListView : public views::View,
167 public views::BoundsAnimatorObserver {
169 explicit MessageListView(MessageCenterView* message_center_view,
171 virtual ~MessageListView();
173 void AddNotificationAt(MessageView* view, int i);
174 void RemoveNotificationAt(int i);
175 void UpdateNotificationAt(MessageView* view, int i);
176 void SetRepositionTarget(const gfx::Rect& target_rect);
177 void ResetRepositionSession();
178 void ClearAllNotifications(const gfx::Rect& visible_scroll_rect);
181 // Overridden from views::View.
182 virtual void Layout() OVERRIDE;
183 virtual gfx::Size GetPreferredSize() OVERRIDE;
184 virtual int GetHeightForWidth(int width) OVERRIDE;
185 virtual void PaintChildren(gfx::Canvas* canvas) OVERRIDE;
186 virtual void ReorderChildLayers(ui::Layer* parent_layer) OVERRIDE;
188 // Overridden from views::BoundsAnimatorObserver.
189 virtual void OnBoundsAnimatorProgressed(
190 views::BoundsAnimator* animator) OVERRIDE;
191 virtual void OnBoundsAnimatorDone(views::BoundsAnimator* animator) OVERRIDE;
194 // Returns the actual index for child of |index|.
195 // MessageListView allows to slide down upper notifications, which means
196 // that the upper ones should come above the lower ones if top_down is not
197 // enabled. To achieve this, inversed order is adopted. The top most
198 // notification is the last child, and the bottom most notification is the
200 int GetActualIndex(int index);
201 bool IsValidChild(views::View* child);
202 void DoUpdateIfPossible();
204 // Animates all notifications below target upwards to align with the top of
205 // the last closed notification.
206 void AnimateNotificationsBelowTarget();
207 // Animates all notifications above target downwards to align with the top of
208 // the last closed notification.
209 void AnimateNotificationsAboveTarget();
211 // Schedules animation for a child to the specified position. Returns false
212 // if |child| will disappear after the animation.
213 bool AnimateChild(views::View* child, int top, int height);
215 // Animate clearing one notification.
216 void AnimateClearingOneNotification();
217 MessageCenterView* message_center_view() const {
218 return message_center_view_;
221 MessageCenterView* message_center_view_; // Weak reference.
222 // The top position of the reposition target rectangle.
225 bool has_deferred_task_;
226 bool clear_all_started_;
228 std::set<views::View*> adding_views_;
229 std::set<views::View*> deleting_views_;
230 std::set<views::View*> deleted_when_done_;
231 std::list<views::View*> clearing_all_views_;
232 scoped_ptr<views::BoundsAnimator> animator_;
233 base::WeakPtrFactory<MessageListView> weak_ptr_factory_;
235 DISALLOW_COPY_AND_ASSIGN(MessageListView);
238 MessageListView::MessageListView(MessageCenterView* message_center_view,
240 : message_center_view_(message_center_view),
243 has_deferred_task_(false),
244 clear_all_started_(false),
246 weak_ptr_factory_(this) {
247 views::BoxLayout* layout =
248 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1);
249 layout->set_spread_blank_space(true);
250 SetLayoutManager(layout);
252 // Set the margin to 0 for the layout. BoxLayout assumes the same margin
253 // for top and bottom, but the bottom margin here should be smaller
254 // because of the shadow of message view. Use an empty border instead
255 // to provide this margin.
256 gfx::Insets shadow_insets = MessageView::GetShadowInsets();
257 set_background(views::Background::CreateSolidBackground(
258 kMessageCenterBackgroundColor));
259 SetBorder(views::Border::CreateEmptyBorder(
260 top_down ? 0 : kMarginBetweenItems - shadow_insets.top(), /* top */
261 kMarginBetweenItems - shadow_insets.left(), /* left */
262 top_down ? kMarginBetweenItems - shadow_insets.bottom() : 0, /* bottom */
263 kMarginBetweenItems - shadow_insets.right() /* right */));
266 MessageListView::~MessageListView() {
268 animator_->RemoveObserver(this);
271 void MessageListView::Layout() {
275 gfx::Rect child_area = GetContentsBounds();
276 int top = child_area.y();
278 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
280 for (int i = 0; i < child_count(); ++i) {
281 views::View* child = child_at(i);
282 if (!child->visible())
284 int height = child->GetHeightForWidth(child_area.width());
285 child->SetBounds(child_area.x(), top, child_area.width(), height);
286 top += height + between_items;
290 void MessageListView::AddNotificationAt(MessageView* view, int i) {
291 AddChildViewAt(view, GetActualIndex(i));
292 if (GetContentsBounds().IsEmpty())
295 adding_views_.insert(view);
296 DoUpdateIfPossible();
299 void MessageListView::RemoveNotificationAt(int i) {
300 views::View* child = child_at(GetActualIndex(i));
301 if (GetContentsBounds().IsEmpty()) {
304 if (child->layer()) {
305 deleting_views_.insert(child);
308 animator_->StopAnimatingView(child);
311 DoUpdateIfPossible();
315 void MessageListView::UpdateNotificationAt(MessageView* view, int i) {
316 int actual_index = GetActualIndex(i);
317 views::View* child = child_at(actual_index);
319 animator_->StopAnimatingView(child);
320 gfx::Rect old_bounds = child->bounds();
321 if (deleting_views_.find(child) != deleting_views_.end())
322 deleting_views_.erase(child);
323 if (deleted_when_done_.find(child) != deleted_when_done_.end())
324 deleted_when_done_.erase(child);
326 AddChildViewAt(view, actual_index);
327 view->SetBounds(old_bounds.x(), old_bounds.y(), old_bounds.width(),
328 view->GetHeightForWidth(old_bounds.width()));
329 DoUpdateIfPossible();
332 gfx::Size MessageListView::GetPreferredSize() {
334 for (int i = 0; i < child_count(); i++) {
335 views::View* child = child_at(i);
336 if (IsValidChild(child))
337 width = std::max(width, child->GetPreferredSize().width());
340 return gfx::Size(width + GetInsets().width(),
341 GetHeightForWidth(width + GetInsets().width()));
344 int MessageListView::GetHeightForWidth(int width) {
345 if (fixed_height_ > 0)
346 return fixed_height_;
348 width -= GetInsets().width();
351 for (int i = 0; i < child_count(); ++i) {
352 views::View* child = child_at(i);
353 if (!IsValidChild(child))
355 height += child->GetHeightForWidth(width) + padding;
356 padding = kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
359 return height + GetInsets().height();
362 void MessageListView::PaintChildren(gfx::Canvas* canvas) {
363 // Paint in the inversed order. Otherwise upper notification may be
364 // hidden by the lower one.
365 for (int i = child_count() - 1; i >= 0; --i) {
366 if (!child_at(i)->layer())
367 child_at(i)->Paint(canvas);
371 void MessageListView::ReorderChildLayers(ui::Layer* parent_layer) {
372 // Reorder children to stack the last child layer at the top. Otherwise
373 // upper notification may be hidden by the lower one.
374 for (int i = 0; i < child_count(); ++i) {
375 if (child_at(i)->layer())
376 parent_layer->StackAtBottom(child_at(i)->layer());
380 void MessageListView::SetRepositionTarget(const gfx::Rect& target) {
381 reposition_top_ = target.y();
382 fixed_height_ = GetHeightForWidth(width());
385 void MessageListView::ResetRepositionSession() {
386 // Don't call DoUpdateIfPossible(), but let Layout() do the task without
387 // animation. Reset will cause the change of the bubble size itself, and
388 // animation from the old location will look weird.
389 if (reposition_top_ >= 0 && animator_.get()) {
390 has_deferred_task_ = false;
391 // cancel cause OnBoundsAnimatorDone which deletes |deleted_when_done_|.
393 STLDeleteContainerPointers(deleting_views_.begin(), deleting_views_.end());
394 deleting_views_.clear();
395 adding_views_.clear();
399 reposition_top_ = -1;
403 void MessageListView::ClearAllNotifications(
404 const gfx::Rect& visible_scroll_rect) {
405 for (int i = 0; i < child_count(); ++i) {
406 views::View* child = child_at(i);
407 if (!child->visible())
409 if (gfx::IntersectRects(child->bounds(), visible_scroll_rect).IsEmpty())
411 clearing_all_views_.push_back(child);
413 DoUpdateIfPossible();
416 void MessageListView::OnBoundsAnimatorProgressed(
417 views::BoundsAnimator* animator) {
418 DCHECK_EQ(animator_.get(), animator);
419 for (std::set<views::View*>::iterator iter = deleted_when_done_.begin();
420 iter != deleted_when_done_.end(); ++iter) {
421 const gfx::SlideAnimation* animation = animator->GetAnimationForView(*iter);
423 (*iter)->layer()->SetOpacity(animation->CurrentValueBetween(1.0, 0.0));
427 void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
428 STLDeleteContainerPointers(
429 deleted_when_done_.begin(), deleted_when_done_.end());
430 deleted_when_done_.clear();
432 if (clear_all_started_) {
433 clear_all_started_ = false;
434 message_center_view()->OnAllNotificationsCleared();
437 if (has_deferred_task_) {
438 has_deferred_task_ = false;
439 DoUpdateIfPossible();
443 GetWidget()->SynthesizeMouseMoveEvent();
446 int MessageListView::GetActualIndex(int index) {
447 for (int i = 0; i < child_count() && i <= index; ++i)
448 index += IsValidChild(child_at(i)) ? 0 : 1;
449 return std::min(index, child_count());
452 bool MessageListView::IsValidChild(views::View* child) {
453 return child->visible() &&
454 deleting_views_.find(child) == deleting_views_.end() &&
455 deleted_when_done_.find(child) == deleted_when_done_.end();
458 void MessageListView::DoUpdateIfPossible() {
459 gfx::Rect child_area = GetContentsBounds();
460 if (child_area.IsEmpty())
463 if (animator_.get() && animator_->IsAnimating()) {
464 has_deferred_task_ = true;
468 if (!animator_.get()) {
469 animator_.reset(new views::BoundsAnimator(this));
470 animator_->AddObserver(this);
473 if (!clearing_all_views_.empty()) {
474 AnimateClearingOneNotification();
479 AnimateNotificationsBelowTarget();
481 AnimateNotificationsAboveTarget();
483 adding_views_.clear();
484 deleting_views_.clear();
487 void MessageListView::AnimateNotificationsBelowTarget() {
489 for (int i = 0; i < child_count(); ++i) {
490 views::View* child = child_at(i);
491 if (!IsValidChild(child)) {
492 AnimateChild(child, child->y(), child->height());
493 } else if (reposition_top_ < 0 || child->y() > reposition_top_) {
494 // Find first notification below target (or all notifications if no
500 if (last_index > 0) {
502 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
503 int top = (reposition_top_ > 0) ? reposition_top_ : GetInsets().top();
505 for (int i = last_index; i < child_count(); ++i) {
506 // Animate notifications below target upwards.
507 views::View* child = child_at(i);
508 if (AnimateChild(child, top, child->height()))
509 top += child->height() + between_items;
514 void MessageListView::AnimateNotificationsAboveTarget() {
516 for (int i = child_count() - 1; i >= 0; --i) {
517 views::View* child = child_at(i);
518 if (!IsValidChild(child)) {
519 AnimateChild(child, child->y(), child->height());
520 } else if (reposition_top_ < 0 || child->y() < reposition_top_) {
521 // Find first notification above target (or all notifications if no
527 if (last_index >= 0) {
529 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
530 int bottom = (reposition_top_ > 0)
531 ? reposition_top_ + child_at(last_index)->height()
532 : GetHeightForWidth(width()) - GetInsets().bottom();
533 for (int i = last_index; i >= 0; --i) {
534 // Animate notifications above target downwards.
535 views::View* child = child_at(i);
536 if (AnimateChild(child, bottom - child->height(), child->height()))
537 bottom -= child->height() + between_items;
542 bool MessageListView::AnimateChild(views::View* child, int top, int height) {
543 gfx::Rect child_area = GetContentsBounds();
544 if (adding_views_.find(child) != adding_views_.end()) {
545 child->SetBounds(child_area.right(), top, child_area.width(), height);
546 animator_->AnimateViewTo(
547 child, gfx::Rect(child_area.x(), top, child_area.width(), height));
548 } else if (deleting_views_.find(child) != deleting_views_.end()) {
549 DCHECK(child->layer());
550 // No moves, but animate to fade-out.
551 animator_->AnimateViewTo(child, child->bounds());
552 deleted_when_done_.insert(child);
555 gfx::Rect target(child_area.x(), top, child_area.width(), height);
556 if (child->bounds().origin() != target.origin())
557 animator_->AnimateViewTo(child, target);
559 child->SetBoundsRect(target);
564 void MessageListView::AnimateClearingOneNotification() {
565 DCHECK(!clearing_all_views_.empty());
567 clear_all_started_ = true;
569 views::View* child = clearing_all_views_.front();
570 clearing_all_views_.pop_front();
572 // Slide from left to right.
573 gfx::Rect new_bounds = child->bounds();
574 new_bounds.set_x(new_bounds.right() + kMarginBetweenItems);
575 animator_->AnimateViewTo(child, new_bounds);
577 // Schedule to start sliding out next notification after a short delay.
578 if (!clearing_all_views_.empty()) {
579 base::MessageLoop::current()->PostDelayedTask(
581 base::Bind(&MessageListView::AnimateClearingOneNotification,
582 weak_ptr_factory_.GetWeakPtr()),
583 base::TimeDelta::FromMilliseconds(
584 kAnimateClearingNextNotificationDelayMS));
588 // MessageCenterView ///////////////////////////////////////////////////////////
590 MessageCenterView::MessageCenterView(MessageCenter* message_center,
591 MessageCenterTray* tray,
593 bool initially_settings_visible,
595 : message_center_(message_center),
598 settings_view_(NULL),
601 settings_visible_(initially_settings_visible),
607 context_menu_controller_(new MessageViewContextMenuController(this)) {
608 message_center_->AddObserver(this);
609 set_notify_enter_exit_on_child(true);
610 set_background(views::Background::CreateSolidBackground(
611 kMessageCenterBackgroundColor));
613 NotifierSettingsProvider* notifier_settings_provider =
614 message_center_->GetNotifierSettingsProvider();
615 button_bar_ = new MessageCenterButtonBar(this,
617 notifier_settings_provider,
618 initially_settings_visible);
620 const int button_height = button_bar_->GetPreferredSize().height();
623 new BoundedScrollView(kMinScrollViewHeight, max_height - button_height);
625 if (get_use_acceleration_when_possible()) {
626 scroller_->SetPaintToLayer(true);
627 scroller_->SetFillsBoundsOpaquely(false);
628 scroller_->layer()->SetMasksToBounds(true);
631 empty_list_view_.reset(new NoNotificationMessageView);
632 empty_list_view_->set_owned_by_client();
633 message_list_view_.reset(new MessageListView(this, top_down));
634 message_list_view_->set_owned_by_client();
636 // We want to swap the contents of the scroll view between the empty list
637 // view and the message list view, without constructing them afresh each
638 // time. So, since the scroll view deletes old contents each time you
639 // set the contents (regardless of the |owned_by_client_| setting) we need
640 // an intermediate view for the contents whose children we can swap in and
642 views::View* scroller_contents = new views::View();
643 scroller_contents->SetLayoutManager(new views::FillLayout());
644 scroller_contents->AddChildView(empty_list_view_.get());
645 scroller_->SetContents(scroller_contents);
647 settings_view_ = new NotifierSettingsView(notifier_settings_provider);
649 if (initially_settings_visible)
650 scroller_->SetVisible(false);
652 settings_view_->SetVisible(false);
654 AddChildView(scroller_);
655 AddChildView(settings_view_);
656 AddChildView(button_bar_);
659 MessageCenterView::~MessageCenterView() {
661 message_center_->RemoveObserver(this);
664 void MessageCenterView::SetNotifications(
665 const NotificationList::Notifications& notifications) {
669 notification_views_.clear();
672 for (NotificationList::Notifications::const_iterator iter =
673 notifications.begin(); iter != notifications.end(); ++iter) {
674 AddNotificationAt(*(*iter), index++);
676 message_center_->DisplayedNotification((*iter)->id());
677 if (notification_views_.size() >= kMaxVisibleMessageCenterNotifications)
681 NotificationsChanged();
682 scroller_->RequestFocus();
685 void MessageCenterView::SetSettingsVisible(bool visible) {
689 if (visible == settings_visible_)
692 settings_visible_ = visible;
695 source_view_ = scroller_;
696 target_view_ = settings_view_;
698 source_view_ = settings_view_;
699 target_view_ = scroller_;
701 source_height_ = source_view_->GetHeightForWidth(width());
702 target_height_ = target_view_->GetHeightForWidth(width());
704 gfx::MultiAnimation::Parts parts;
705 // First part: slide resize animation.
706 parts.push_back(gfx::MultiAnimation::Part(
707 (source_height_ == target_height_) ? 0 : kDefaultAnimationDurationMs,
708 gfx::Tween::EASE_OUT));
709 // Second part: fade-out the source_view.
710 if (source_view_->layer()) {
711 parts.push_back(gfx::MultiAnimation::Part(
712 kDefaultAnimationDurationMs, gfx::Tween::LINEAR));
714 parts.push_back(gfx::MultiAnimation::Part());
716 // Third part: fade-in the target_view.
717 if (target_view_->layer()) {
718 parts.push_back(gfx::MultiAnimation::Part(
719 kDefaultAnimationDurationMs, gfx::Tween::LINEAR));
720 target_view_->layer()->SetOpacity(0);
721 target_view_->SetVisible(true);
723 parts.push_back(gfx::MultiAnimation::Part());
725 settings_transition_animation_.reset(new gfx::MultiAnimation(
726 parts, base::TimeDelta::FromMicroseconds(1000000 / kDefaultFrameRateHz)));
727 settings_transition_animation_->set_delegate(this);
728 settings_transition_animation_->set_continuous(false);
729 settings_transition_animation_->Start();
731 button_bar_->SetBackArrowVisible(visible);
734 void MessageCenterView::ClearAllNotifications() {
738 scroller_->SetEnabled(false);
739 button_bar_->SetAllButtonsEnabled(false);
740 message_list_view_->ClearAllNotifications(scroller_->GetVisibleRect());
743 void MessageCenterView::OnAllNotificationsCleared() {
744 scroller_->SetEnabled(true);
745 button_bar_->SetAllButtonsEnabled(true);
746 button_bar_->SetCloseAllButtonEnabled(false);
747 message_center_->RemoveAllVisibleNotifications(true); // Action by user.
750 size_t MessageCenterView::NumMessageViewsForTest() const {
751 return message_list_view_->child_count();
754 void MessageCenterView::OnSettingsChanged() {
755 scroller_->InvalidateLayout();
756 PreferredSizeChanged();
760 void MessageCenterView::SetIsClosing(bool is_closing) {
761 is_closing_ = is_closing;
763 message_center_->RemoveObserver(this);
765 message_center_->AddObserver(this);
768 void MessageCenterView::Layout() {
772 int button_height = button_bar_->GetHeightForWidth(width()) +
773 button_bar_->GetInsets().height();
774 // Skip unnecessary re-layout of contents during the resize animation.
775 bool animating = settings_transition_animation_ &&
776 settings_transition_animation_->is_animating();
777 if (animating && settings_transition_animation_->current_part_index() == 0) {
779 button_bar_->SetBounds(
780 0, height() - button_height, width(), button_height);
785 scroller_->SetBounds(0,
786 top_down_ ? button_height : 0,
788 height() - button_height);
789 settings_view_->SetBounds(0,
790 top_down_ ? button_height : 0,
792 height() - button_height);
794 bool is_scrollable = false;
795 if (scroller_->visible())
796 is_scrollable = scroller_->height() < message_list_view_->height();
798 is_scrollable = settings_view_->IsScrollable();
802 // Draw separator line on the top of the button bar if it is on the bottom
803 // or draw it at the bottom if the bar is on the top.
804 button_bar_->SetBorder(views::Border::CreateSolidSidedBorder(
805 top_down_ ? 0 : 1, 0, top_down_ ? 1 : 0, 0, kFooterDelimiterColor));
807 button_bar_->SetBorder(views::Border::CreateEmptyBorder(
808 top_down_ ? 0 : 1, 0, top_down_ ? 1 : 0, 0));
810 button_bar_->SchedulePaint();
812 button_bar_->SetBounds(0,
813 top_down_ ? 0 : height() - button_height,
817 GetWidget()->GetRootView()->SchedulePaint();
820 gfx::Size MessageCenterView::GetPreferredSize() {
821 if (settings_transition_animation_ &&
822 settings_transition_animation_->is_animating()) {
823 int content_width = std::max(source_view_->GetPreferredSize().width(),
824 target_view_->GetPreferredSize().width());
825 int width = std::max(content_width,
826 button_bar_->GetPreferredSize().width());
827 return gfx::Size(width, GetHeightForWidth(width));
831 for (int i = 0; i < child_count(); ++i) {
832 views::View* child = child_at(0);
833 if (child->visible())
834 width = std::max(width, child->GetPreferredSize().width());
836 return gfx::Size(width, GetHeightForWidth(width));
839 int MessageCenterView::GetHeightForWidth(int width) {
840 if (settings_transition_animation_ &&
841 settings_transition_animation_->is_animating()) {
842 int content_height = target_height_;
843 if (settings_transition_animation_->current_part_index() == 0) {
844 content_height = settings_transition_animation_->CurrentValueBetween(
845 source_height_, target_height_);
847 return button_bar_->GetHeightForWidth(width) + content_height;
850 int content_height = 0;
851 if (scroller_->visible())
852 content_height += scroller_->GetHeightForWidth(width);
854 content_height += settings_view_->GetHeightForWidth(width);
855 return button_bar_->GetHeightForWidth(width) +
856 button_bar_->GetInsets().height() + content_height;
859 bool MessageCenterView::OnMouseWheel(const ui::MouseWheelEvent& event) {
860 // Do not rely on the default scroll event handler of ScrollView because
861 // the scroll happens only when the focus is on the ScrollView. The
862 // notification center will allow the scrolling even when the focus is on
864 if (scroller_->bounds().Contains(event.location()))
865 return scroller_->OnMouseWheel(event);
866 return views::View::OnMouseWheel(event);
869 void MessageCenterView::OnMouseExited(const ui::MouseEvent& event) {
873 message_list_view_->ResetRepositionSession();
874 NotificationsChanged();
877 void MessageCenterView::OnNotificationAdded(const std::string& id) {
879 const NotificationList::Notifications& notifications =
880 message_center_->GetVisibleNotifications();
881 for (NotificationList::Notifications::const_iterator iter =
882 notifications.begin(); iter != notifications.end();
884 if ((*iter)->id() == id) {
885 AddNotificationAt(*(*iter), index);
888 if (notification_views_.size() >= kMaxVisibleMessageCenterNotifications)
891 NotificationsChanged();
894 void MessageCenterView::OnNotificationRemoved(const std::string& id,
896 NotificationViewsMap::iterator view_iter = notification_views_.find(id);
897 if (view_iter == notification_views_.end())
899 NotificationView* view = view_iter->second;
900 int index = message_list_view_->GetIndexOf(view);
902 message_list_view_->SetRepositionTarget(view->bounds());
903 // Moves the keyboard focus to the next notification if the removed
904 // notification is focused so that the user can dismiss notifications
905 // without re-focusing by tab key.
906 if (view->IsCloseButtonFocused() ||
907 view == GetFocusManager()->GetFocusedView()) {
908 views::View* next_focused_view = NULL;
909 if (message_list_view_->child_count() > index + 1)
910 next_focused_view = message_list_view_->child_at(index + 1);
912 next_focused_view = message_list_view_->child_at(index - 1);
914 if (next_focused_view) {
915 if (view->IsCloseButtonFocused())
916 // Safe cast since all views in MessageListView are MessageViews.
917 static_cast<MessageView*>(
918 next_focused_view)->RequestFocusOnCloseButton();
920 next_focused_view->RequestFocus();
924 message_list_view_->RemoveNotificationAt(index);
925 notification_views_.erase(view_iter);
926 NotificationsChanged();
929 void MessageCenterView::OnNotificationUpdated(const std::string& id) {
930 NotificationViewsMap::const_iterator view_iter = notification_views_.find(id);
931 if (view_iter == notification_views_.end())
933 NotificationView* view = view_iter->second;
934 size_t index = message_list_view_->GetIndexOf(view);
936 // TODO(dimich): add MessageCenter::GetVisibleNotificationById(id)
937 const NotificationList::Notifications& notifications =
938 message_center_->GetVisibleNotifications();
939 for (NotificationList::Notifications::const_iterator iter =
940 notifications.begin(); iter != notifications.end(); ++iter) {
941 if ((*iter)->id() == id) {
942 bool expanded = true;
943 if (IsExperimentalNotificationUIEnabled())
944 expanded = (*iter)->is_expanded();
945 NotificationView* view =
946 NotificationView::Create(this,
949 false); // Not creating a top-level
951 view->set_context_menu_controller(context_menu_controller_.get());
952 view->set_scroller(scroller_);
953 message_list_view_->UpdateNotificationAt(view, index);
954 notification_views_[id] = view;
955 NotificationsChanged();
961 void MessageCenterView::ClickOnNotification(
962 const std::string& notification_id) {
963 message_center_->ClickOnNotification(notification_id);
966 void MessageCenterView::RemoveNotification(const std::string& notification_id,
968 message_center_->RemoveNotification(notification_id, by_user);
971 scoped_ptr<ui::MenuModel> MessageCenterView::CreateMenuModel(
972 const NotifierId& notifier_id,
973 const base::string16& display_source) {
974 return tray_->CreateNotificationMenuModel(notifier_id, display_source);
977 bool MessageCenterView::HasClickedListener(const std::string& notification_id) {
978 return message_center_->HasClickedListener(notification_id);
981 void MessageCenterView::ClickOnNotificationButton(
982 const std::string& notification_id,
984 message_center_->ClickOnNotificationButton(notification_id, button_index);
987 void MessageCenterView::ExpandNotification(const std::string& notification_id) {
988 message_center_->ExpandNotification(notification_id);
991 void MessageCenterView::AnimationEnded(const gfx::Animation* animation) {
992 DCHECK_EQ(animation, settings_transition_animation_.get());
994 Visibility visibility = target_view_ == settings_view_
995 ? VISIBILITY_SETTINGS
996 : VISIBILITY_MESSAGE_CENTER;
997 message_center_->SetVisibility(visibility);
999 source_view_->SetVisible(false);
1000 target_view_->SetVisible(true);
1001 if (source_view_->layer())
1002 source_view_->layer()->SetOpacity(1.0);
1003 if (target_view_->layer())
1004 target_view_->layer()->SetOpacity(1.0);
1005 settings_transition_animation_.reset();
1006 PreferredSizeChanged();
1010 void MessageCenterView::AnimationProgressed(const gfx::Animation* animation) {
1011 DCHECK_EQ(animation, settings_transition_animation_.get());
1012 PreferredSizeChanged();
1013 if (settings_transition_animation_->current_part_index() == 1 &&
1014 source_view_->layer()) {
1015 source_view_->layer()->SetOpacity(
1016 1.0 - settings_transition_animation_->GetCurrentValue());
1018 } else if (settings_transition_animation_->current_part_index() == 2 &&
1019 target_view_->layer()) {
1020 target_view_->layer()->SetOpacity(
1021 settings_transition_animation_->GetCurrentValue());
1026 void MessageCenterView::AnimationCanceled(const gfx::Animation* animation) {
1027 DCHECK_EQ(animation, settings_transition_animation_.get());
1028 AnimationEnded(animation);
1032 void MessageCenterView::AddMessageViewAt(MessageView* view, int index) {
1033 view->set_scroller(scroller_);
1034 message_list_view_->AddNotificationAt(view, index);
1037 void MessageCenterView::AddNotificationAt(const Notification& notification,
1039 // NotificationViews are expanded by default here until
1040 // http://crbug.com/217902 is fixed. TODO(dharcourt): Fix.
1041 bool expanded = true;
1042 if (IsExperimentalNotificationUIEnabled())
1043 expanded = notification.is_expanded();
1044 NotificationView* view =
1045 NotificationView::Create(this,
1048 false); // Not creating a top-level
1050 view->set_context_menu_controller(context_menu_controller_.get());
1051 notification_views_[notification.id()] = view;
1052 AddMessageViewAt(view, index);
1055 void MessageCenterView::NotificationsChanged() {
1056 bool no_message_views = notification_views_.empty();
1058 // When the child view is removed from the hierarchy, its focus is cleared.
1059 // In this case we want to save which view has focus so that the user can
1060 // continue to interact with notifications in the order they were expecting.
1061 views::FocusManager* focus_manager = scroller_->GetFocusManager();
1062 View* focused_view = NULL;
1063 // |focus_manager| can be NULL in tests.
1065 focused_view = focus_manager->GetFocusedView();
1067 // All the children of this view are owned by |this|.
1068 scroller_->contents()->RemoveAllChildViews(/*delete_children=*/false);
1069 scroller_->contents()->AddChildView(
1070 no_message_views ? empty_list_view_.get() : message_list_view_.get());
1072 button_bar_->SetCloseAllButtonEnabled(!no_message_views);
1073 scroller_->SetFocusable(!no_message_views);
1075 if (focus_manager && focused_view)
1076 focus_manager->SetFocusedView(focused_view);
1078 scroller_->InvalidateLayout();
1079 PreferredSizeChanged();
1083 void MessageCenterView::SetNotificationViewForTest(MessageView* view) {
1084 message_list_view_->AddNotificationAt(view, 0);
1087 } // namespace message_center