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/command_line.h"
11 #include "base/memory/weak_ptr.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/stl_util.h"
14 #include "ui/base/l10n/l10n_util.h"
15 #include "ui/gfx/animation/multi_animation.h"
16 #include "ui/gfx/animation/slide_animation.h"
17 #include "ui/gfx/canvas.h"
18 #include "ui/gfx/insets.h"
19 #include "ui/gfx/rect.h"
20 #include "ui/gfx/size.h"
21 #include "ui/message_center/message_center.h"
22 #include "ui/message_center/message_center_style.h"
23 #include "ui/message_center/message_center_switches.h"
24 #include "ui/message_center/message_center_tray.h"
25 #include "ui/message_center/message_center_types.h"
26 #include "ui/message_center/views/message_center_button_bar.h"
27 #include "ui/message_center/views/message_view.h"
28 #include "ui/message_center/views/message_view_context_menu_controller.h"
29 #include "ui/message_center/views/notification_view.h"
30 #include "ui/message_center/views/notifier_settings_view.h"
31 #include "ui/resources/grit/ui_resources.h"
32 #include "ui/strings/grit/ui_strings.h"
33 #include "ui/views/animation/bounds_animator.h"
34 #include "ui/views/animation/bounds_animator_observer.h"
35 #include "ui/views/background.h"
36 #include "ui/views/border.h"
37 #include "ui/views/controls/button/button.h"
38 #include "ui/views/controls/label.h"
39 #include "ui/views/controls/scroll_view.h"
40 #include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
41 #include "ui/views/layout/box_layout.h"
42 #include "ui/views/layout/fill_layout.h"
43 #include "ui/views/widget/widget.h"
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;
55 const int kDefaultAnimationDurationMs
= 120;
56 const int kDefaultFrameRateHz
= 60;
59 class NoNotificationMessageView
: public views::View
{
61 NoNotificationMessageView();
62 virtual ~NoNotificationMessageView();
64 // Overridden from views::View.
65 virtual gfx::Size
GetPreferredSize() const OVERRIDE
;
66 virtual int GetHeightForWidth(int width
) const OVERRIDE
;
67 virtual void Layout() OVERRIDE
;
72 DISALLOW_COPY_AND_ASSIGN(NoNotificationMessageView
);
75 NoNotificationMessageView::NoNotificationMessageView() {
76 label_
= new views::Label(l10n_util::GetStringUTF16(
77 IDS_MESSAGE_CENTER_NO_MESSAGES
));
78 label_
->SetAutoColorReadabilityEnabled(false);
79 label_
->SetEnabledColor(kNoNotificationsTextColor
);
80 // Set transparent background to ensure that subpixel rendering
81 // is disabled. See crbug.com/169056
82 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
83 label_
->SetBackgroundColor(kTransparentColor
);
88 NoNotificationMessageView::~NoNotificationMessageView() {
91 gfx::Size
NoNotificationMessageView::GetPreferredSize() const {
92 return gfx::Size(kMinScrollViewHeight
, label_
->GetPreferredSize().width());
95 int NoNotificationMessageView::GetHeightForWidth(int width
) const {
96 return kMinScrollViewHeight
;
99 void NoNotificationMessageView::Layout() {
100 int text_height
= label_
->GetHeightForWidth(width());
101 int margin
= (height() - text_height
) / 2;
102 label_
->SetBounds(0, margin
, width(), text_height
);
105 // Displays a list of messages for rich notifications. Functions as an array of
106 // MessageViews and animates them on transitions. It also supports
108 class MessageListView
: public views::View
,
109 public views::BoundsAnimatorObserver
{
111 explicit MessageListView(MessageCenterView
* message_center_view
,
113 virtual ~MessageListView();
115 void AddNotificationAt(MessageView
* view
, int i
);
116 void RemoveNotification(MessageView
* view
);
117 void UpdateNotification(MessageView
* view
, const Notification
& notification
);
118 void SetRepositionTarget(const gfx::Rect
& target_rect
);
119 void ResetRepositionSession();
120 void ClearAllNotifications(const gfx::Rect
& visible_scroll_rect
);
123 // Overridden from views::View.
124 virtual void Layout() OVERRIDE
;
125 virtual gfx::Size
GetPreferredSize() const OVERRIDE
;
126 virtual int GetHeightForWidth(int width
) const OVERRIDE
;
127 virtual void PaintChildren(gfx::Canvas
* canvas
,
128 const views::CullSet
& cull_set
) OVERRIDE
;
129 virtual void ReorderChildLayers(ui::Layer
* parent_layer
) OVERRIDE
;
131 // Overridden from views::BoundsAnimatorObserver.
132 virtual void OnBoundsAnimatorProgressed(
133 views::BoundsAnimator
* animator
) OVERRIDE
;
134 virtual void OnBoundsAnimatorDone(views::BoundsAnimator
* animator
) OVERRIDE
;
137 bool IsValidChild(const views::View
* child
) const;
138 void DoUpdateIfPossible();
140 // Animates all notifications below target upwards to align with the top of
141 // the last closed notification.
142 void AnimateNotificationsBelowTarget();
143 // Animates all notifications above target downwards to align with the top of
144 // the last closed notification.
145 void AnimateNotificationsAboveTarget();
147 // Schedules animation for a child to the specified position. Returns false
148 // if |child| will disappear after the animation.
149 bool AnimateChild(views::View
* child
, int top
, int height
);
151 // Animate clearing one notification.
152 void AnimateClearingOneNotification();
153 MessageCenterView
* message_center_view() const {
154 return message_center_view_
;
157 MessageCenterView
* message_center_view_
; // Weak reference.
158 // The top position of the reposition target rectangle.
161 bool has_deferred_task_
;
162 bool clear_all_started_
;
164 std::set
<views::View
*> adding_views_
;
165 std::set
<views::View
*> deleting_views_
;
166 std::set
<views::View
*> deleted_when_done_
;
167 std::list
<views::View
*> clearing_all_views_
;
168 scoped_ptr
<views::BoundsAnimator
> animator_
;
169 base::WeakPtrFactory
<MessageListView
> weak_ptr_factory_
;
171 DISALLOW_COPY_AND_ASSIGN(MessageListView
);
174 MessageListView::MessageListView(MessageCenterView
* message_center_view
,
176 : message_center_view_(message_center_view
),
179 has_deferred_task_(false),
180 clear_all_started_(false),
182 weak_ptr_factory_(this) {
183 views::BoxLayout
* layout
=
184 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 1);
185 layout
->SetDefaultFlex(1);
186 SetLayoutManager(layout
);
188 // Set the margin to 0 for the layout. BoxLayout assumes the same margin
189 // for top and bottom, but the bottom margin here should be smaller
190 // because of the shadow of message view. Use an empty border instead
191 // to provide this margin.
192 gfx::Insets shadow_insets
= MessageView::GetShadowInsets();
193 set_background(views::Background::CreateSolidBackground(
194 kMessageCenterBackgroundColor
));
195 SetBorder(views::Border::CreateEmptyBorder(
196 top_down
? 0 : kMarginBetweenItems
- shadow_insets
.top(), /* top */
197 kMarginBetweenItems
- shadow_insets
.left(), /* left */
198 top_down
? kMarginBetweenItems
- shadow_insets
.bottom() : 0, /* bottom */
199 kMarginBetweenItems
- shadow_insets
.right() /* right */));
202 MessageListView::~MessageListView() {
204 animator_
->RemoveObserver(this);
207 void MessageListView::Layout() {
211 gfx::Rect child_area
= GetContentsBounds();
212 int top
= child_area
.y();
214 kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
216 for (int i
= 0; i
< child_count(); ++i
) {
217 views::View
* child
= child_at(i
);
218 if (!child
->visible())
220 int height
= child
->GetHeightForWidth(child_area
.width());
221 child
->SetBounds(child_area
.x(), top
, child_area
.width(), height
);
222 top
+= height
+ between_items
;
226 void MessageListView::AddNotificationAt(MessageView
* view
, int index
) {
227 // |index| refers to a position in a subset of valid children. |real_index|
228 // in a list includes the invalid children, so we compute the real index by
229 // walking the list until |index| number of valid children are encountered,
230 // or to the end of the list.
232 while (real_index
< child_count()) {
233 if (IsValidChild(child_at(real_index
))) {
241 AddChildViewAt(view
, real_index
);
242 if (GetContentsBounds().IsEmpty())
245 adding_views_
.insert(view
);
246 DoUpdateIfPossible();
249 void MessageListView::RemoveNotification(MessageView
* view
) {
250 DCHECK_EQ(view
->parent(), this);
251 if (GetContentsBounds().IsEmpty()) {
255 deleting_views_
.insert(view
);
258 animator_
->StopAnimatingView(view
);
261 DoUpdateIfPossible();
265 void MessageListView::UpdateNotification(MessageView
* view
,
266 const Notification
& notification
) {
267 int index
= GetIndexOf(view
);
268 DCHECK_LE(0, index
); // GetIndexOf is negative if not a child.
271 animator_
->StopAnimatingView(view
);
272 if (deleting_views_
.find(view
) != deleting_views_
.end())
273 deleting_views_
.erase(view
);
274 if (deleted_when_done_
.find(view
) != deleted_when_done_
.end())
275 deleted_when_done_
.erase(view
);
276 view
->UpdateWithNotification(notification
);
277 DoUpdateIfPossible();
280 gfx::Size
MessageListView::GetPreferredSize() const {
282 for (int i
= 0; i
< child_count(); i
++) {
283 const views::View
* child
= child_at(i
);
284 if (IsValidChild(child
))
285 width
= std::max(width
, child
->GetPreferredSize().width());
288 return gfx::Size(width
+ GetInsets().width(),
289 GetHeightForWidth(width
+ GetInsets().width()));
292 int MessageListView::GetHeightForWidth(int width
) const {
293 if (fixed_height_
> 0)
294 return fixed_height_
;
296 width
-= GetInsets().width();
299 for (int i
= 0; i
< child_count(); ++i
) {
300 const views::View
* child
= child_at(i
);
301 if (!IsValidChild(child
))
303 height
+= child
->GetHeightForWidth(width
) + padding
;
304 padding
= kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
307 return height
+ GetInsets().height();
310 void MessageListView::PaintChildren(gfx::Canvas
* canvas
,
311 const views::CullSet
& cull_set
) {
312 // Paint in the inversed order. Otherwise upper notification may be
313 // hidden by the lower one.
314 for (int i
= child_count() - 1; i
>= 0; --i
) {
315 if (!child_at(i
)->layer())
316 child_at(i
)->Paint(canvas
, cull_set
);
320 void MessageListView::ReorderChildLayers(ui::Layer
* parent_layer
) {
321 // Reorder children to stack the last child layer at the top. Otherwise
322 // upper notification may be hidden by the lower one.
323 for (int i
= 0; i
< child_count(); ++i
) {
324 if (child_at(i
)->layer())
325 parent_layer
->StackAtBottom(child_at(i
)->layer());
329 void MessageListView::SetRepositionTarget(const gfx::Rect
& target
) {
330 reposition_top_
= target
.y();
331 fixed_height_
= GetHeightForWidth(width());
334 void MessageListView::ResetRepositionSession() {
335 // Don't call DoUpdateIfPossible(), but let Layout() do the task without
336 // animation. Reset will cause the change of the bubble size itself, and
337 // animation from the old location will look weird.
338 if (reposition_top_
>= 0 && animator_
.get()) {
339 has_deferred_task_
= false;
340 // cancel cause OnBoundsAnimatorDone which deletes |deleted_when_done_|.
342 STLDeleteContainerPointers(deleting_views_
.begin(), deleting_views_
.end());
343 deleting_views_
.clear();
344 adding_views_
.clear();
348 reposition_top_
= -1;
352 void MessageListView::ClearAllNotifications(
353 const gfx::Rect
& visible_scroll_rect
) {
354 for (int i
= 0; i
< child_count(); ++i
) {
355 views::View
* child
= child_at(i
);
356 if (!child
->visible())
358 if (gfx::IntersectRects(child
->bounds(), visible_scroll_rect
).IsEmpty())
360 clearing_all_views_
.push_back(child
);
362 DoUpdateIfPossible();
365 void MessageListView::OnBoundsAnimatorProgressed(
366 views::BoundsAnimator
* animator
) {
367 DCHECK_EQ(animator_
.get(), animator
);
368 for (std::set
<views::View
*>::iterator iter
= deleted_when_done_
.begin();
369 iter
!= deleted_when_done_
.end(); ++iter
) {
370 const gfx::SlideAnimation
* animation
= animator
->GetAnimationForView(*iter
);
372 (*iter
)->layer()->SetOpacity(animation
->CurrentValueBetween(1.0, 0.0));
376 void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator
* animator
) {
377 STLDeleteContainerPointers(
378 deleted_when_done_
.begin(), deleted_when_done_
.end());
379 deleted_when_done_
.clear();
381 if (clear_all_started_
) {
382 clear_all_started_
= false;
383 message_center_view()->OnAllNotificationsCleared();
386 if (has_deferred_task_
) {
387 has_deferred_task_
= false;
388 DoUpdateIfPossible();
392 GetWidget()->SynthesizeMouseMoveEvent();
395 bool MessageListView::IsValidChild(const views::View
* child
) const {
396 return child
->visible() &&
397 deleting_views_
.find(const_cast<views::View
*>(child
)) ==
398 deleting_views_
.end() &&
399 deleted_when_done_
.find(const_cast<views::View
*>(child
)) ==
400 deleted_when_done_
.end();
403 void MessageListView::DoUpdateIfPossible() {
404 gfx::Rect child_area
= GetContentsBounds();
405 if (child_area
.IsEmpty())
408 if (animator_
.get() && animator_
->IsAnimating()) {
409 has_deferred_task_
= true;
413 if (!animator_
.get()) {
414 animator_
.reset(new views::BoundsAnimator(this));
415 animator_
->AddObserver(this);
418 if (!clearing_all_views_
.empty()) {
419 AnimateClearingOneNotification();
424 CommandLine::ForCurrentProcess()->HasSwitch(
425 switches::kEnableMessageCenterAlwaysScrollUpUponNotificationRemoval
))
426 AnimateNotificationsBelowTarget();
428 AnimateNotificationsAboveTarget();
430 adding_views_
.clear();
431 deleting_views_
.clear();
434 void MessageListView::AnimateNotificationsBelowTarget() {
436 for (int i
= 0; i
< child_count(); ++i
) {
437 views::View
* child
= child_at(i
);
438 if (!IsValidChild(child
)) {
439 AnimateChild(child
, child
->y(), child
->height());
440 } else if (reposition_top_
< 0 || child
->y() > reposition_top_
) {
441 // Find first notification below target (or all notifications if no
447 if (last_index
> 0) {
449 kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
450 int top
= (reposition_top_
> 0) ? reposition_top_
: GetInsets().top();
452 for (int i
= last_index
; i
< child_count(); ++i
) {
453 // Animate notifications below target upwards.
454 views::View
* child
= child_at(i
);
455 if (AnimateChild(child
, top
, child
->height()))
456 top
+= child
->height() + between_items
;
461 void MessageListView::AnimateNotificationsAboveTarget() {
463 for (int i
= child_count() - 1; i
>= 0; --i
) {
464 views::View
* child
= child_at(i
);
465 if (!IsValidChild(child
)) {
466 AnimateChild(child
, child
->y(), child
->height());
467 } else if (reposition_top_
< 0 || child
->y() < reposition_top_
) {
468 // Find first notification above target (or all notifications if no
474 if (last_index
>= 0) {
476 kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
477 int bottom
= (reposition_top_
> 0)
478 ? reposition_top_
+ child_at(last_index
)->height()
479 : GetHeightForWidth(width()) - GetInsets().bottom();
480 for (int i
= last_index
; i
>= 0; --i
) {
481 // Animate notifications above target downwards.
482 views::View
* child
= child_at(i
);
483 if (AnimateChild(child
, bottom
- child
->height(), child
->height()))
484 bottom
-= child
->height() + between_items
;
489 bool MessageListView::AnimateChild(views::View
* child
, int top
, int height
) {
490 gfx::Rect child_area
= GetContentsBounds();
491 if (adding_views_
.find(child
) != adding_views_
.end()) {
492 child
->SetBounds(child_area
.right(), top
, child_area
.width(), height
);
493 animator_
->AnimateViewTo(
494 child
, gfx::Rect(child_area
.x(), top
, child_area
.width(), height
));
495 } else if (deleting_views_
.find(child
) != deleting_views_
.end()) {
496 DCHECK(child
->layer());
497 // No moves, but animate to fade-out.
498 animator_
->AnimateViewTo(child
, child
->bounds());
499 deleted_when_done_
.insert(child
);
502 gfx::Rect
target(child_area
.x(), top
, child_area
.width(), height
);
503 if (child
->bounds().origin() != target
.origin())
504 animator_
->AnimateViewTo(child
, target
);
506 child
->SetBoundsRect(target
);
511 void MessageListView::AnimateClearingOneNotification() {
512 DCHECK(!clearing_all_views_
.empty());
514 clear_all_started_
= true;
516 views::View
* child
= clearing_all_views_
.front();
517 clearing_all_views_
.pop_front();
519 // Slide from left to right.
520 gfx::Rect new_bounds
= child
->bounds();
521 new_bounds
.set_x(new_bounds
.right() + kMarginBetweenItems
);
522 animator_
->AnimateViewTo(child
, new_bounds
);
524 // Schedule to start sliding out next notification after a short delay.
525 if (!clearing_all_views_
.empty()) {
526 base::MessageLoop::current()->PostDelayedTask(
528 base::Bind(&MessageListView::AnimateClearingOneNotification
,
529 weak_ptr_factory_
.GetWeakPtr()),
530 base::TimeDelta::FromMilliseconds(
531 kAnimateClearingNextNotificationDelayMS
));
535 // MessageCenterView ///////////////////////////////////////////////////////////
537 MessageCenterView::MessageCenterView(MessageCenter
* message_center
,
538 MessageCenterTray
* tray
,
540 bool initially_settings_visible
,
542 const base::string16
& title
)
543 : message_center_(message_center
),
546 settings_view_(NULL
),
549 settings_visible_(initially_settings_visible
),
555 context_menu_controller_(new MessageViewContextMenuController(this)) {
556 message_center_
->AddObserver(this);
557 set_notify_enter_exit_on_child(true);
558 set_background(views::Background::CreateSolidBackground(
559 kMessageCenterBackgroundColor
));
561 NotifierSettingsProvider
* notifier_settings_provider
=
562 message_center_
->GetNotifierSettingsProvider();
563 button_bar_
= new MessageCenterButtonBar(this,
565 notifier_settings_provider
,
566 initially_settings_visible
,
569 const int button_height
= button_bar_
->GetPreferredSize().height();
571 scroller_
= new views::ScrollView();
572 scroller_
->ClipHeightTo(kMinScrollViewHeight
, max_height
- button_height
);
573 scroller_
->SetVerticalScrollBar(new views::OverlayScrollBar(false));
574 scroller_
->set_background(
575 views::Background::CreateSolidBackground(kMessageCenterBackgroundColor
));
577 scroller_
->SetPaintToLayer(true);
578 scroller_
->SetFillsBoundsOpaquely(false);
579 scroller_
->layer()->SetMasksToBounds(true);
581 empty_list_view_
.reset(new NoNotificationMessageView
);
582 empty_list_view_
->set_owned_by_client();
583 message_list_view_
.reset(new MessageListView(this, top_down
));
584 message_list_view_
->set_owned_by_client();
586 // We want to swap the contents of the scroll view between the empty list
587 // view and the message list view, without constructing them afresh each
588 // time. So, since the scroll view deletes old contents each time you
589 // set the contents (regardless of the |owned_by_client_| setting) we need
590 // an intermediate view for the contents whose children we can swap in and
592 views::View
* scroller_contents
= new views::View();
593 scroller_contents
->SetLayoutManager(new views::FillLayout());
594 scroller_contents
->AddChildView(empty_list_view_
.get());
595 scroller_
->SetContents(scroller_contents
);
597 settings_view_
= new NotifierSettingsView(notifier_settings_provider
);
599 if (initially_settings_visible
)
600 scroller_
->SetVisible(false);
602 settings_view_
->SetVisible(false);
604 AddChildView(scroller_
);
605 AddChildView(settings_view_
);
606 AddChildView(button_bar_
);
609 MessageCenterView::~MessageCenterView() {
611 message_center_
->RemoveObserver(this);
614 void MessageCenterView::SetNotifications(
615 const NotificationList::Notifications
& notifications
) {
619 notification_views_
.clear();
622 for (NotificationList::Notifications::const_iterator iter
=
623 notifications
.begin(); iter
!= notifications
.end(); ++iter
) {
624 AddNotificationAt(*(*iter
), index
++);
626 message_center_
->DisplayedNotification(
627 (*iter
)->id(), message_center::DISPLAY_SOURCE_MESSAGE_CENTER
);
628 if (notification_views_
.size() >= kMaxVisibleMessageCenterNotifications
)
632 NotificationsChanged();
633 scroller_
->RequestFocus();
636 void MessageCenterView::SetSettingsVisible(bool visible
) {
640 if (visible
== settings_visible_
)
643 settings_visible_
= visible
;
646 source_view_
= scroller_
;
647 target_view_
= settings_view_
;
649 source_view_
= settings_view_
;
650 target_view_
= scroller_
;
652 source_height_
= source_view_
->GetHeightForWidth(width());
653 target_height_
= target_view_
->GetHeightForWidth(width());
655 gfx::MultiAnimation::Parts parts
;
656 // First part: slide resize animation.
657 parts
.push_back(gfx::MultiAnimation::Part(
658 (source_height_
== target_height_
) ? 0 : kDefaultAnimationDurationMs
,
659 gfx::Tween::EASE_OUT
));
660 // Second part: fade-out the source_view.
661 if (source_view_
->layer()) {
662 parts
.push_back(gfx::MultiAnimation::Part(
663 kDefaultAnimationDurationMs
, gfx::Tween::LINEAR
));
665 parts
.push_back(gfx::MultiAnimation::Part());
667 // Third part: fade-in the target_view.
668 if (target_view_
->layer()) {
669 parts
.push_back(gfx::MultiAnimation::Part(
670 kDefaultAnimationDurationMs
, gfx::Tween::LINEAR
));
671 target_view_
->layer()->SetOpacity(0);
672 target_view_
->SetVisible(true);
674 parts
.push_back(gfx::MultiAnimation::Part());
676 settings_transition_animation_
.reset(new gfx::MultiAnimation(
677 parts
, base::TimeDelta::FromMicroseconds(1000000 / kDefaultFrameRateHz
)));
678 settings_transition_animation_
->set_delegate(this);
679 settings_transition_animation_
->set_continuous(false);
680 settings_transition_animation_
->Start();
682 button_bar_
->SetBackArrowVisible(visible
);
685 void MessageCenterView::ClearAllNotifications() {
689 scroller_
->SetEnabled(false);
690 button_bar_
->SetAllButtonsEnabled(false);
691 message_list_view_
->ClearAllNotifications(scroller_
->GetVisibleRect());
694 void MessageCenterView::OnAllNotificationsCleared() {
695 scroller_
->SetEnabled(true);
696 button_bar_
->SetAllButtonsEnabled(true);
697 button_bar_
->SetCloseAllButtonEnabled(false);
698 message_center_
->RemoveAllVisibleNotifications(true); // Action by user.
701 size_t MessageCenterView::NumMessageViewsForTest() const {
702 return message_list_view_
->child_count();
705 void MessageCenterView::OnSettingsChanged() {
706 scroller_
->InvalidateLayout();
707 PreferredSizeChanged();
711 void MessageCenterView::SetIsClosing(bool is_closing
) {
712 is_closing_
= is_closing
;
714 message_center_
->RemoveObserver(this);
716 message_center_
->AddObserver(this);
719 void MessageCenterView::Layout() {
723 int button_height
= button_bar_
->GetHeightForWidth(width()) +
724 button_bar_
->GetInsets().height();
725 // Skip unnecessary re-layout of contents during the resize animation.
726 bool animating
= settings_transition_animation_
&&
727 settings_transition_animation_
->is_animating();
728 if (animating
&& settings_transition_animation_
->current_part_index() == 0) {
730 button_bar_
->SetBounds(
731 0, height() - button_height
, width(), button_height
);
736 scroller_
->SetBounds(0,
737 top_down_
? button_height
: 0,
739 height() - button_height
);
740 settings_view_
->SetBounds(0,
741 top_down_
? button_height
: 0,
743 height() - button_height
);
745 bool is_scrollable
= false;
746 if (scroller_
->visible())
747 is_scrollable
= scroller_
->height() < message_list_view_
->height();
749 is_scrollable
= settings_view_
->IsScrollable();
753 // Draw separator line on the top of the button bar if it is on the bottom
754 // or draw it at the bottom if the bar is on the top.
755 button_bar_
->SetBorder(views::Border::CreateSolidSidedBorder(
756 top_down_
? 0 : 1, 0, top_down_
? 1 : 0, 0, kFooterDelimiterColor
));
758 button_bar_
->SetBorder(views::Border::CreateEmptyBorder(
759 top_down_
? 0 : 1, 0, top_down_
? 1 : 0, 0));
761 button_bar_
->SchedulePaint();
763 button_bar_
->SetBounds(0,
764 top_down_
? 0 : height() - button_height
,
768 GetWidget()->GetRootView()->SchedulePaint();
771 gfx::Size
MessageCenterView::GetPreferredSize() const {
772 if (settings_transition_animation_
&&
773 settings_transition_animation_
->is_animating()) {
774 int content_width
= std::max(source_view_
->GetPreferredSize().width(),
775 target_view_
->GetPreferredSize().width());
776 int width
= std::max(content_width
,
777 button_bar_
->GetPreferredSize().width());
778 return gfx::Size(width
, GetHeightForWidth(width
));
782 for (int i
= 0; i
< child_count(); ++i
) {
783 const views::View
* child
= child_at(0);
784 if (child
->visible())
785 width
= std::max(width
, child
->GetPreferredSize().width());
787 return gfx::Size(width
, GetHeightForWidth(width
));
790 int MessageCenterView::GetHeightForWidth(int width
) const {
791 if (settings_transition_animation_
&&
792 settings_transition_animation_
->is_animating()) {
793 int content_height
= target_height_
;
794 if (settings_transition_animation_
->current_part_index() == 0) {
795 content_height
= settings_transition_animation_
->CurrentValueBetween(
796 source_height_
, target_height_
);
798 return button_bar_
->GetHeightForWidth(width
) + content_height
;
801 int content_height
= 0;
802 if (scroller_
->visible())
803 content_height
+= scroller_
->GetHeightForWidth(width
);
805 content_height
+= settings_view_
->GetHeightForWidth(width
);
806 return button_bar_
->GetHeightForWidth(width
) +
807 button_bar_
->GetInsets().height() + content_height
;
810 bool MessageCenterView::OnMouseWheel(const ui::MouseWheelEvent
& event
) {
811 // Do not rely on the default scroll event handler of ScrollView because
812 // the scroll happens only when the focus is on the ScrollView. The
813 // notification center will allow the scrolling even when the focus is on
815 if (scroller_
->bounds().Contains(event
.location()))
816 return scroller_
->OnMouseWheel(event
);
817 return views::View::OnMouseWheel(event
);
820 void MessageCenterView::OnMouseExited(const ui::MouseEvent
& event
) {
824 message_list_view_
->ResetRepositionSession();
825 NotificationsChanged();
828 void MessageCenterView::OnNotificationAdded(const std::string
& id
) {
830 const NotificationList::Notifications
& notifications
=
831 message_center_
->GetVisibleNotifications();
832 for (NotificationList::Notifications::const_iterator iter
=
833 notifications
.begin(); iter
!= notifications
.end();
835 if ((*iter
)->id() == id
) {
836 AddNotificationAt(*(*iter
), index
);
839 if (notification_views_
.size() >= kMaxVisibleMessageCenterNotifications
)
842 NotificationsChanged();
845 void MessageCenterView::OnNotificationRemoved(const std::string
& id
,
847 NotificationViewsMap::iterator view_iter
= notification_views_
.find(id
);
848 if (view_iter
== notification_views_
.end())
850 NotificationView
* view
= view_iter
->second
;
851 int index
= message_list_view_
->GetIndexOf(view
);
854 message_list_view_
->SetRepositionTarget(view
->bounds());
855 // Moves the keyboard focus to the next notification if the removed
856 // notification is focused so that the user can dismiss notifications
857 // without re-focusing by tab key.
858 if (view
->IsCloseButtonFocused() ||
859 view
== GetFocusManager()->GetFocusedView()) {
860 views::View
* next_focused_view
= NULL
;
861 if (message_list_view_
->child_count() > index
+ 1)
862 next_focused_view
= message_list_view_
->child_at(index
+ 1);
864 next_focused_view
= message_list_view_
->child_at(index
- 1);
866 if (next_focused_view
) {
867 if (view
->IsCloseButtonFocused())
868 // Safe cast since all views in MessageListView are MessageViews.
869 static_cast<MessageView
*>(
870 next_focused_view
)->RequestFocusOnCloseButton();
872 next_focused_view
->RequestFocus();
876 message_list_view_
->RemoveNotification(view
);
877 notification_views_
.erase(view_iter
);
878 NotificationsChanged();
881 void MessageCenterView::OnNotificationUpdated(const std::string
& id
) {
882 NotificationViewsMap::const_iterator view_iter
= notification_views_
.find(id
);
883 if (view_iter
== notification_views_
.end())
885 NotificationView
* view
= view_iter
->second
;
886 // TODO(dimich): add MessageCenter::GetVisibleNotificationById(id)
887 const NotificationList::Notifications
& notifications
=
888 message_center_
->GetVisibleNotifications();
889 for (NotificationList::Notifications::const_iterator iter
=
890 notifications
.begin(); iter
!= notifications
.end(); ++iter
) {
891 if ((*iter
)->id() == id
) {
892 int old_width
= view
->width();
893 int old_height
= view
->GetHeightForWidth(old_width
);
894 message_list_view_
->UpdateNotification(view
, **iter
);
895 if (view
->GetHeightForWidth(old_width
) != old_height
)
896 NotificationsChanged();
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