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/bounded_scroll_view.h"
29 #include "ui/message_center/views/message_center_button_bar.h"
30 #include "ui/message_center/views/message_view.h"
31 #include "ui/message_center/views/message_view_context_menu_controller.h"
32 #include "ui/message_center/views/notification_view.h"
33 #include "ui/message_center/views/notifier_settings_view.h"
34 #include "ui/views/animation/bounds_animator.h"
35 #include "ui/views/animation/bounds_animator_observer.h"
36 #include "ui/views/background.h"
37 #include "ui/views/border.h"
38 #include "ui/views/controls/button/button.h"
39 #include "ui/views/controls/label.h"
40 #include "ui/views/layout/box_layout.h"
41 #include "ui/views/layout/fill_layout.h"
42 #include "ui/views/widget/widget.h"
44 namespace message_center {
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);
52 const int kAnimateClearingNextNotificationDelayMS = 40;
54 const int kDefaultAnimationDurationMs = 120;
55 const int kDefaultFrameRateHz = 60;
58 class NoNotificationMessageView : public views::View {
60 NoNotificationMessageView();
61 virtual ~NoNotificationMessageView();
63 // Overridden from views::View.
64 virtual gfx::Size GetPreferredSize() OVERRIDE;
65 virtual int GetHeightForWidth(int width) OVERRIDE;
66 virtual void Layout() OVERRIDE;
71 DISALLOW_COPY_AND_ASSIGN(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);
87 NoNotificationMessageView::~NoNotificationMessageView() {
90 gfx::Size NoNotificationMessageView::GetPreferredSize() {
91 return gfx::Size(kMinScrollViewHeight, label_->GetPreferredSize().width());
94 int NoNotificationMessageView::GetHeightForWidth(int width) {
95 return kMinScrollViewHeight;
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);
104 // Displays a list of messages for rich notifications. Functions as an array of
105 // MessageViews and animates them on transitions. It also supports
107 class MessageListView : public views::View,
108 public views::BoundsAnimatorObserver {
110 explicit MessageListView(MessageCenterView* message_center_view,
112 virtual ~MessageListView();
114 void AddNotificationAt(MessageView* view, int i);
115 void RemoveNotification(MessageView* view);
116 void UpdateNotification(MessageView* view, MessageView* new_view);
117 void SetRepositionTarget(const gfx::Rect& target_rect);
118 void ResetRepositionSession();
119 void ClearAllNotifications(const gfx::Rect& visible_scroll_rect);
122 // Overridden from views::View.
123 virtual void Layout() OVERRIDE;
124 virtual gfx::Size GetPreferredSize() OVERRIDE;
125 virtual int GetHeightForWidth(int width) OVERRIDE;
126 virtual void PaintChildren(gfx::Canvas* canvas) OVERRIDE;
127 virtual void ReorderChildLayers(ui::Layer* parent_layer) OVERRIDE;
129 // Overridden from views::BoundsAnimatorObserver.
130 virtual void OnBoundsAnimatorProgressed(
131 views::BoundsAnimator* animator) OVERRIDE;
132 virtual void OnBoundsAnimatorDone(views::BoundsAnimator* animator) OVERRIDE;
135 bool IsValidChild(views::View* child);
136 void DoUpdateIfPossible();
138 // Animates all notifications below target upwards to align with the top of
139 // the last closed notification.
140 void AnimateNotificationsBelowTarget();
141 // Animates all notifications above target downwards to align with the top of
142 // the last closed notification.
143 void AnimateNotificationsAboveTarget();
145 // Schedules animation for a child to the specified position. Returns false
146 // if |child| will disappear after the animation.
147 bool AnimateChild(views::View* child, int top, int height);
149 // Animate clearing one notification.
150 void AnimateClearingOneNotification();
151 MessageCenterView* message_center_view() const {
152 return message_center_view_;
155 MessageCenterView* message_center_view_; // Weak reference.
156 // The top position of the reposition target rectangle.
159 bool has_deferred_task_;
160 bool clear_all_started_;
162 std::set<views::View*> adding_views_;
163 std::set<views::View*> deleting_views_;
164 std::set<views::View*> deleted_when_done_;
165 std::list<views::View*> clearing_all_views_;
166 scoped_ptr<views::BoundsAnimator> animator_;
167 base::WeakPtrFactory<MessageListView> weak_ptr_factory_;
169 DISALLOW_COPY_AND_ASSIGN(MessageListView);
172 MessageListView::MessageListView(MessageCenterView* message_center_view,
174 : message_center_view_(message_center_view),
177 has_deferred_task_(false),
178 clear_all_started_(false),
180 weak_ptr_factory_(this) {
181 views::BoxLayout* layout =
182 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1);
183 layout->set_spread_blank_space(true);
184 SetLayoutManager(layout);
186 // Set the margin to 0 for the layout. BoxLayout assumes the same margin
187 // for top and bottom, but the bottom margin here should be smaller
188 // because of the shadow of message view. Use an empty border instead
189 // to provide this margin.
190 gfx::Insets shadow_insets = MessageView::GetShadowInsets();
191 set_background(views::Background::CreateSolidBackground(
192 kMessageCenterBackgroundColor));
193 SetBorder(views::Border::CreateEmptyBorder(
194 top_down ? 0 : kMarginBetweenItems - shadow_insets.top(), /* top */
195 kMarginBetweenItems - shadow_insets.left(), /* left */
196 top_down ? kMarginBetweenItems - shadow_insets.bottom() : 0, /* bottom */
197 kMarginBetweenItems - shadow_insets.right() /* right */));
200 MessageListView::~MessageListView() {
202 animator_->RemoveObserver(this);
205 void MessageListView::Layout() {
209 gfx::Rect child_area = GetContentsBounds();
210 int top = child_area.y();
212 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
214 for (int i = 0; i < child_count(); ++i) {
215 views::View* child = child_at(i);
216 if (!child->visible())
218 int height = child->GetHeightForWidth(child_area.width());
219 child->SetBounds(child_area.x(), top, child_area.width(), height);
220 top += height + between_items;
224 void MessageListView::AddNotificationAt(MessageView* view, int index) {
225 // |index| refers to a position in a subset of valid children. |real_index|
226 // in a list includes the invalid children, so we compute the real index by
227 // walking the list until |index| number of valid children are encountered,
228 // or to the end of the list.
230 while (real_index < child_count()) {
231 if (IsValidChild(child_at(real_index))) {
239 AddChildViewAt(view, real_index);
240 if (GetContentsBounds().IsEmpty())
243 adding_views_.insert(view);
244 DoUpdateIfPossible();
247 void MessageListView::RemoveNotification(MessageView* view) {
248 DCHECK_EQ(view->parent(), this);
249 if (GetContentsBounds().IsEmpty()) {
253 deleting_views_.insert(view);
256 animator_->StopAnimatingView(view);
259 DoUpdateIfPossible();
263 void MessageListView::UpdateNotification(MessageView* view,
264 MessageView* new_view) {
265 int index = GetIndexOf(view);
266 DCHECK_LE(0, index); // GetIndexOf is negative if not a child.
269 animator_->StopAnimatingView(view);
270 gfx::Rect old_bounds = view->bounds();
271 if (deleting_views_.find(view) != deleting_views_.end())
272 deleting_views_.erase(view);
273 if (deleted_when_done_.find(view) != deleted_when_done_.end())
274 deleted_when_done_.erase(view);
276 AddChildViewAt(new_view, index);
277 new_view->SetBounds(old_bounds.x(),
280 new_view->GetHeightForWidth(old_bounds.width()));
281 DoUpdateIfPossible();
284 gfx::Size MessageListView::GetPreferredSize() {
286 for (int i = 0; i < child_count(); i++) {
287 views::View* child = child_at(i);
288 if (IsValidChild(child))
289 width = std::max(width, child->GetPreferredSize().width());
292 return gfx::Size(width + GetInsets().width(),
293 GetHeightForWidth(width + GetInsets().width()));
296 int MessageListView::GetHeightForWidth(int width) {
297 if (fixed_height_ > 0)
298 return fixed_height_;
300 width -= GetInsets().width();
303 for (int i = 0; i < child_count(); ++i) {
304 views::View* child = child_at(i);
305 if (!IsValidChild(child))
307 height += child->GetHeightForWidth(width) + padding;
308 padding = kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
311 return height + GetInsets().height();
314 void MessageListView::PaintChildren(gfx::Canvas* canvas) {
315 // Paint in the inversed order. Otherwise upper notification may be
316 // hidden by the lower one.
317 for (int i = child_count() - 1; i >= 0; --i) {
318 if (!child_at(i)->layer())
319 child_at(i)->Paint(canvas);
323 void MessageListView::ReorderChildLayers(ui::Layer* parent_layer) {
324 // Reorder children to stack the last child layer at the top. Otherwise
325 // upper notification may be hidden by the lower one.
326 for (int i = 0; i < child_count(); ++i) {
327 if (child_at(i)->layer())
328 parent_layer->StackAtBottom(child_at(i)->layer());
332 void MessageListView::SetRepositionTarget(const gfx::Rect& target) {
333 reposition_top_ = target.y();
334 fixed_height_ = GetHeightForWidth(width());
337 void MessageListView::ResetRepositionSession() {
338 // Don't call DoUpdateIfPossible(), but let Layout() do the task without
339 // animation. Reset will cause the change of the bubble size itself, and
340 // animation from the old location will look weird.
341 if (reposition_top_ >= 0 && animator_.get()) {
342 has_deferred_task_ = false;
343 // cancel cause OnBoundsAnimatorDone which deletes |deleted_when_done_|.
345 STLDeleteContainerPointers(deleting_views_.begin(), deleting_views_.end());
346 deleting_views_.clear();
347 adding_views_.clear();
351 reposition_top_ = -1;
355 void MessageListView::ClearAllNotifications(
356 const gfx::Rect& visible_scroll_rect) {
357 for (int i = 0; i < child_count(); ++i) {
358 views::View* child = child_at(i);
359 if (!child->visible())
361 if (gfx::IntersectRects(child->bounds(), visible_scroll_rect).IsEmpty())
363 clearing_all_views_.push_back(child);
365 DoUpdateIfPossible();
368 void MessageListView::OnBoundsAnimatorProgressed(
369 views::BoundsAnimator* animator) {
370 DCHECK_EQ(animator_.get(), animator);
371 for (std::set<views::View*>::iterator iter = deleted_when_done_.begin();
372 iter != deleted_when_done_.end(); ++iter) {
373 const gfx::SlideAnimation* animation = animator->GetAnimationForView(*iter);
375 (*iter)->layer()->SetOpacity(animation->CurrentValueBetween(1.0, 0.0));
379 void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
380 STLDeleteContainerPointers(
381 deleted_when_done_.begin(), deleted_when_done_.end());
382 deleted_when_done_.clear();
384 if (clear_all_started_) {
385 clear_all_started_ = false;
386 message_center_view()->OnAllNotificationsCleared();
389 if (has_deferred_task_) {
390 has_deferred_task_ = false;
391 DoUpdateIfPossible();
395 GetWidget()->SynthesizeMouseMoveEvent();
398 bool MessageListView::IsValidChild(views::View* child) {
399 return child->visible() &&
400 deleting_views_.find(child) == deleting_views_.end() &&
401 deleted_when_done_.find(child) == deleted_when_done_.end();
404 void MessageListView::DoUpdateIfPossible() {
405 gfx::Rect child_area = GetContentsBounds();
406 if (child_area.IsEmpty())
409 if (animator_.get() && animator_->IsAnimating()) {
410 has_deferred_task_ = true;
414 if (!animator_.get()) {
415 animator_.reset(new views::BoundsAnimator(this));
416 animator_->AddObserver(this);
419 if (!clearing_all_views_.empty()) {
420 AnimateClearingOneNotification();
425 AnimateNotificationsBelowTarget();
427 AnimateNotificationsAboveTarget();
429 adding_views_.clear();
430 deleting_views_.clear();
433 void MessageListView::AnimateNotificationsBelowTarget() {
435 for (int i = 0; i < child_count(); ++i) {
436 views::View* child = child_at(i);
437 if (!IsValidChild(child)) {
438 AnimateChild(child, child->y(), child->height());
439 } else if (reposition_top_ < 0 || child->y() > reposition_top_) {
440 // Find first notification below target (or all notifications if no
446 if (last_index > 0) {
448 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
449 int top = (reposition_top_ > 0) ? reposition_top_ : GetInsets().top();
451 for (int i = last_index; i < child_count(); ++i) {
452 // Animate notifications below target upwards.
453 views::View* child = child_at(i);
454 if (AnimateChild(child, top, child->height()))
455 top += child->height() + between_items;
460 void MessageListView::AnimateNotificationsAboveTarget() {
462 for (int i = child_count() - 1; i >= 0; --i) {
463 views::View* child = child_at(i);
464 if (!IsValidChild(child)) {
465 AnimateChild(child, child->y(), child->height());
466 } else if (reposition_top_ < 0 || child->y() < reposition_top_) {
467 // Find first notification above target (or all notifications if no
473 if (last_index >= 0) {
475 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
476 int bottom = (reposition_top_ > 0)
477 ? reposition_top_ + child_at(last_index)->height()
478 : GetHeightForWidth(width()) - GetInsets().bottom();
479 for (int i = last_index; i >= 0; --i) {
480 // Animate notifications above target downwards.
481 views::View* child = child_at(i);
482 if (AnimateChild(child, bottom - child->height(), child->height()))
483 bottom -= child->height() + between_items;
488 bool MessageListView::AnimateChild(views::View* child, int top, int height) {
489 gfx::Rect child_area = GetContentsBounds();
490 if (adding_views_.find(child) != adding_views_.end()) {
491 child->SetBounds(child_area.right(), top, child_area.width(), height);
492 animator_->AnimateViewTo(
493 child, gfx::Rect(child_area.x(), top, child_area.width(), height));
494 } else if (deleting_views_.find(child) != deleting_views_.end()) {
495 DCHECK(child->layer());
496 // No moves, but animate to fade-out.
497 animator_->AnimateViewTo(child, child->bounds());
498 deleted_when_done_.insert(child);
501 gfx::Rect target(child_area.x(), top, child_area.width(), height);
502 if (child->bounds().origin() != target.origin())
503 animator_->AnimateViewTo(child, target);
505 child->SetBoundsRect(target);
510 void MessageListView::AnimateClearingOneNotification() {
511 DCHECK(!clearing_all_views_.empty());
513 clear_all_started_ = true;
515 views::View* child = clearing_all_views_.front();
516 clearing_all_views_.pop_front();
518 // Slide from left to right.
519 gfx::Rect new_bounds = child->bounds();
520 new_bounds.set_x(new_bounds.right() + kMarginBetweenItems);
521 animator_->AnimateViewTo(child, new_bounds);
523 // Schedule to start sliding out next notification after a short delay.
524 if (!clearing_all_views_.empty()) {
525 base::MessageLoop::current()->PostDelayedTask(
527 base::Bind(&MessageListView::AnimateClearingOneNotification,
528 weak_ptr_factory_.GetWeakPtr()),
529 base::TimeDelta::FromMilliseconds(
530 kAnimateClearingNextNotificationDelayMS));
534 // MessageCenterView ///////////////////////////////////////////////////////////
536 MessageCenterView::MessageCenterView(MessageCenter* message_center,
537 MessageCenterTray* tray,
539 bool initially_settings_visible,
541 : message_center_(message_center),
544 settings_view_(NULL),
547 settings_visible_(initially_settings_visible),
553 context_menu_controller_(new MessageViewContextMenuController(this)) {
554 message_center_->AddObserver(this);
555 set_notify_enter_exit_on_child(true);
556 set_background(views::Background::CreateSolidBackground(
557 kMessageCenterBackgroundColor));
559 NotifierSettingsProvider* notifier_settings_provider =
560 message_center_->GetNotifierSettingsProvider();
561 button_bar_ = new MessageCenterButtonBar(this,
563 notifier_settings_provider,
564 initially_settings_visible);
566 const int button_height = button_bar_->GetPreferredSize().height();
569 new BoundedScrollView(kMinScrollViewHeight, max_height - button_height);
571 if (get_use_acceleration_when_possible()) {
572 scroller_->SetPaintToLayer(true);
573 scroller_->SetFillsBoundsOpaquely(false);
574 scroller_->layer()->SetMasksToBounds(true);
577 empty_list_view_.reset(new NoNotificationMessageView);
578 empty_list_view_->set_owned_by_client();
579 message_list_view_.reset(new MessageListView(this, top_down));
580 message_list_view_->set_owned_by_client();
582 // We want to swap the contents of the scroll view between the empty list
583 // view and the message list view, without constructing them afresh each
584 // time. So, since the scroll view deletes old contents each time you
585 // set the contents (regardless of the |owned_by_client_| setting) we need
586 // an intermediate view for the contents whose children we can swap in and
588 views::View* scroller_contents = new views::View();
589 scroller_contents->SetLayoutManager(new views::FillLayout());
590 scroller_contents->AddChildView(empty_list_view_.get());
591 scroller_->SetContents(scroller_contents);
593 settings_view_ = new NotifierSettingsView(notifier_settings_provider);
595 if (initially_settings_visible)
596 scroller_->SetVisible(false);
598 settings_view_->SetVisible(false);
600 AddChildView(scroller_);
601 AddChildView(settings_view_);
602 AddChildView(button_bar_);
605 MessageCenterView::~MessageCenterView() {
607 message_center_->RemoveObserver(this);
610 void MessageCenterView::SetNotifications(
611 const NotificationList::Notifications& notifications) {
615 notification_views_.clear();
618 for (NotificationList::Notifications::const_iterator iter =
619 notifications.begin(); iter != notifications.end(); ++iter) {
620 AddNotificationAt(*(*iter), index++);
622 message_center_->DisplayedNotification((*iter)->id());
623 if (notification_views_.size() >= kMaxVisibleMessageCenterNotifications)
627 NotificationsChanged();
628 scroller_->RequestFocus();
631 void MessageCenterView::SetSettingsVisible(bool visible) {
635 if (visible == settings_visible_)
638 settings_visible_ = visible;
641 source_view_ = scroller_;
642 target_view_ = settings_view_;
644 source_view_ = settings_view_;
645 target_view_ = scroller_;
647 source_height_ = source_view_->GetHeightForWidth(width());
648 target_height_ = target_view_->GetHeightForWidth(width());
650 gfx::MultiAnimation::Parts parts;
651 // First part: slide resize animation.
652 parts.push_back(gfx::MultiAnimation::Part(
653 (source_height_ == target_height_) ? 0 : kDefaultAnimationDurationMs,
654 gfx::Tween::EASE_OUT));
655 // Second part: fade-out the source_view.
656 if (source_view_->layer()) {
657 parts.push_back(gfx::MultiAnimation::Part(
658 kDefaultAnimationDurationMs, gfx::Tween::LINEAR));
660 parts.push_back(gfx::MultiAnimation::Part());
662 // Third part: fade-in the target_view.
663 if (target_view_->layer()) {
664 parts.push_back(gfx::MultiAnimation::Part(
665 kDefaultAnimationDurationMs, gfx::Tween::LINEAR));
666 target_view_->layer()->SetOpacity(0);
667 target_view_->SetVisible(true);
669 parts.push_back(gfx::MultiAnimation::Part());
671 settings_transition_animation_.reset(new gfx::MultiAnimation(
672 parts, base::TimeDelta::FromMicroseconds(1000000 / kDefaultFrameRateHz)));
673 settings_transition_animation_->set_delegate(this);
674 settings_transition_animation_->set_continuous(false);
675 settings_transition_animation_->Start();
677 button_bar_->SetBackArrowVisible(visible);
680 void MessageCenterView::ClearAllNotifications() {
684 scroller_->SetEnabled(false);
685 button_bar_->SetAllButtonsEnabled(false);
686 message_list_view_->ClearAllNotifications(scroller_->GetVisibleRect());
689 void MessageCenterView::OnAllNotificationsCleared() {
690 scroller_->SetEnabled(true);
691 button_bar_->SetAllButtonsEnabled(true);
692 button_bar_->SetCloseAllButtonEnabled(false);
693 message_center_->RemoveAllVisibleNotifications(true); // Action by user.
696 size_t MessageCenterView::NumMessageViewsForTest() const {
697 return message_list_view_->child_count();
700 void MessageCenterView::OnSettingsChanged() {
701 scroller_->InvalidateLayout();
702 PreferredSizeChanged();
706 void MessageCenterView::SetIsClosing(bool is_closing) {
707 is_closing_ = is_closing;
709 message_center_->RemoveObserver(this);
711 message_center_->AddObserver(this);
714 void MessageCenterView::Layout() {
718 int button_height = button_bar_->GetHeightForWidth(width()) +
719 button_bar_->GetInsets().height();
720 // Skip unnecessary re-layout of contents during the resize animation.
721 bool animating = settings_transition_animation_ &&
722 settings_transition_animation_->is_animating();
723 if (animating && settings_transition_animation_->current_part_index() == 0) {
725 button_bar_->SetBounds(
726 0, height() - button_height, width(), button_height);
731 scroller_->SetBounds(0,
732 top_down_ ? button_height : 0,
734 height() - button_height);
735 settings_view_->SetBounds(0,
736 top_down_ ? button_height : 0,
738 height() - button_height);
740 bool is_scrollable = false;
741 if (scroller_->visible())
742 is_scrollable = scroller_->height() < message_list_view_->height();
744 is_scrollable = settings_view_->IsScrollable();
748 // Draw separator line on the top of the button bar if it is on the bottom
749 // or draw it at the bottom if the bar is on the top.
750 button_bar_->SetBorder(views::Border::CreateSolidSidedBorder(
751 top_down_ ? 0 : 1, 0, top_down_ ? 1 : 0, 0, kFooterDelimiterColor));
753 button_bar_->SetBorder(views::Border::CreateEmptyBorder(
754 top_down_ ? 0 : 1, 0, top_down_ ? 1 : 0, 0));
756 button_bar_->SchedulePaint();
758 button_bar_->SetBounds(0,
759 top_down_ ? 0 : height() - button_height,
763 GetWidget()->GetRootView()->SchedulePaint();
766 gfx::Size MessageCenterView::GetPreferredSize() {
767 if (settings_transition_animation_ &&
768 settings_transition_animation_->is_animating()) {
769 int content_width = std::max(source_view_->GetPreferredSize().width(),
770 target_view_->GetPreferredSize().width());
771 int width = std::max(content_width,
772 button_bar_->GetPreferredSize().width());
773 return gfx::Size(width, GetHeightForWidth(width));
777 for (int i = 0; i < child_count(); ++i) {
778 views::View* child = child_at(0);
779 if (child->visible())
780 width = std::max(width, child->GetPreferredSize().width());
782 return gfx::Size(width, GetHeightForWidth(width));
785 int MessageCenterView::GetHeightForWidth(int width) {
786 if (settings_transition_animation_ &&
787 settings_transition_animation_->is_animating()) {
788 int content_height = target_height_;
789 if (settings_transition_animation_->current_part_index() == 0) {
790 content_height = settings_transition_animation_->CurrentValueBetween(
791 source_height_, target_height_);
793 return button_bar_->GetHeightForWidth(width) + content_height;
796 int content_height = 0;
797 if (scroller_->visible())
798 content_height += scroller_->GetHeightForWidth(width);
800 content_height += settings_view_->GetHeightForWidth(width);
801 return button_bar_->GetHeightForWidth(width) +
802 button_bar_->GetInsets().height() + content_height;
805 bool MessageCenterView::OnMouseWheel(const ui::MouseWheelEvent& event) {
806 // Do not rely on the default scroll event handler of ScrollView because
807 // the scroll happens only when the focus is on the ScrollView. The
808 // notification center will allow the scrolling even when the focus is on
810 if (scroller_->bounds().Contains(event.location()))
811 return scroller_->OnMouseWheel(event);
812 return views::View::OnMouseWheel(event);
815 void MessageCenterView::OnMouseExited(const ui::MouseEvent& event) {
819 message_list_view_->ResetRepositionSession();
820 NotificationsChanged();
823 void MessageCenterView::OnNotificationAdded(const std::string& id) {
825 const NotificationList::Notifications& notifications =
826 message_center_->GetVisibleNotifications();
827 for (NotificationList::Notifications::const_iterator iter =
828 notifications.begin(); iter != notifications.end();
830 if ((*iter)->id() == id) {
831 AddNotificationAt(*(*iter), index);
834 if (notification_views_.size() >= kMaxVisibleMessageCenterNotifications)
837 NotificationsChanged();
840 void MessageCenterView::OnNotificationRemoved(const std::string& id,
842 NotificationViewsMap::iterator view_iter = notification_views_.find(id);
843 if (view_iter == notification_views_.end())
845 NotificationView* view = view_iter->second;
846 int index = message_list_view_->GetIndexOf(view);
849 message_list_view_->SetRepositionTarget(view->bounds());
850 // Moves the keyboard focus to the next notification if the removed
851 // notification is focused so that the user can dismiss notifications
852 // without re-focusing by tab key.
853 if (view->IsCloseButtonFocused() ||
854 view == GetFocusManager()->GetFocusedView()) {
855 views::View* next_focused_view = NULL;
856 if (message_list_view_->child_count() > index + 1)
857 next_focused_view = message_list_view_->child_at(index + 1);
859 next_focused_view = message_list_view_->child_at(index - 1);
861 if (next_focused_view) {
862 if (view->IsCloseButtonFocused())
863 // Safe cast since all views in MessageListView are MessageViews.
864 static_cast<MessageView*>(
865 next_focused_view)->RequestFocusOnCloseButton();
867 next_focused_view->RequestFocus();
871 message_list_view_->RemoveNotification(view);
872 notification_views_.erase(view_iter);
873 NotificationsChanged();
876 void MessageCenterView::OnNotificationUpdated(const std::string& id) {
877 NotificationViewsMap::const_iterator view_iter = notification_views_.find(id);
878 if (view_iter == notification_views_.end())
880 NotificationView* view = view_iter->second;
881 // TODO(dimich): add MessageCenter::GetVisibleNotificationById(id)
882 const NotificationList::Notifications& notifications =
883 message_center_->GetVisibleNotifications();
884 for (NotificationList::Notifications::const_iterator iter =
885 notifications.begin(); iter != notifications.end(); ++iter) {
886 if ((*iter)->id() == id) {
887 NotificationView* new_view =
888 NotificationView::Create(this,
890 false); // Not creating a top-level
892 new_view->set_context_menu_controller(context_menu_controller_.get());
893 new_view->set_scroller(scroller_);
894 message_list_view_->UpdateNotification(view, new_view);
895 notification_views_[id] = new_view;
896 NotificationsChanged();
902 void MessageCenterView::ClickOnNotification(
903 const std::string& notification_id) {
904 message_center_->ClickOnNotification(notification_id);
907 void MessageCenterView::RemoveNotification(const std::string& notification_id,
909 message_center_->RemoveNotification(notification_id, by_user);
912 scoped_ptr<ui::MenuModel> MessageCenterView::CreateMenuModel(
913 const NotifierId& notifier_id,
914 const base::string16& display_source) {
915 return tray_->CreateNotificationMenuModel(notifier_id, display_source);
918 bool MessageCenterView::HasClickedListener(const std::string& notification_id) {
919 return message_center_->HasClickedListener(notification_id);
922 void MessageCenterView::ClickOnNotificationButton(
923 const std::string& notification_id,
925 message_center_->ClickOnNotificationButton(notification_id, button_index);
928 void MessageCenterView::AnimationEnded(const gfx::Animation* animation) {
929 DCHECK_EQ(animation, settings_transition_animation_.get());
931 Visibility visibility = target_view_ == settings_view_
932 ? VISIBILITY_SETTINGS
933 : VISIBILITY_MESSAGE_CENTER;
934 message_center_->SetVisibility(visibility);
936 source_view_->SetVisible(false);
937 target_view_->SetVisible(true);
938 if (source_view_->layer())
939 source_view_->layer()->SetOpacity(1.0);
940 if (target_view_->layer())
941 target_view_->layer()->SetOpacity(1.0);
942 settings_transition_animation_.reset();
943 PreferredSizeChanged();
947 void MessageCenterView::AnimationProgressed(const gfx::Animation* animation) {
948 DCHECK_EQ(animation, settings_transition_animation_.get());
949 PreferredSizeChanged();
950 if (settings_transition_animation_->current_part_index() == 1 &&
951 source_view_->layer()) {
952 source_view_->layer()->SetOpacity(
953 1.0 - settings_transition_animation_->GetCurrentValue());
955 } else if (settings_transition_animation_->current_part_index() == 2 &&
956 target_view_->layer()) {
957 target_view_->layer()->SetOpacity(
958 settings_transition_animation_->GetCurrentValue());
963 void MessageCenterView::AnimationCanceled(const gfx::Animation* animation) {
964 DCHECK_EQ(animation, settings_transition_animation_.get());
965 AnimationEnded(animation);
968 void MessageCenterView::AddNotificationAt(const Notification& notification,
970 NotificationView* view =
971 NotificationView::Create(this, notification, false); // Not top-level.
972 view->set_context_menu_controller(context_menu_controller_.get());
973 notification_views_[notification.id()] = view;
974 view->set_scroller(scroller_);
975 message_list_view_->AddNotificationAt(view, index);
978 void MessageCenterView::NotificationsChanged() {
979 bool no_message_views = notification_views_.empty();
981 // When the child view is removed from the hierarchy, its focus is cleared.
982 // In this case we want to save which view has focus so that the user can
983 // continue to interact with notifications in the order they were expecting.
984 views::FocusManager* focus_manager = scroller_->GetFocusManager();
985 View* focused_view = NULL;
986 // |focus_manager| can be NULL in tests.
988 focused_view = focus_manager->GetFocusedView();
990 // All the children of this view are owned by |this|.
991 scroller_->contents()->RemoveAllChildViews(/*delete_children=*/false);
992 scroller_->contents()->AddChildView(
993 no_message_views ? empty_list_view_.get() : message_list_view_.get());
995 button_bar_->SetCloseAllButtonEnabled(!no_message_views);
996 scroller_->SetFocusable(!no_message_views);
998 if (focus_manager && focused_view)
999 focus_manager->SetFocusedView(focused_view);
1001 scroller_->InvalidateLayout();
1002 PreferredSizeChanged();
1006 void MessageCenterView::SetNotificationViewForTest(MessageView* view) {
1007 message_list_view_->AddNotificationAt(view, 0);
1010 } // namespace message_center