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;
58 void SetViewHierarchyEnabled(views::View
* view
, bool enabled
) {
59 for (int i
= 0; i
< view
->child_count(); i
++)
60 SetViewHierarchyEnabled(view
->child_at(i
), enabled
);
61 view
->SetEnabled(enabled
);
66 class NoNotificationMessageView
: public views::View
{
68 NoNotificationMessageView();
69 ~NoNotificationMessageView() override
;
71 // Overridden from views::View.
72 gfx::Size
GetPreferredSize() const override
;
73 int GetHeightForWidth(int width
) const override
;
74 void Layout() override
;
79 DISALLOW_COPY_AND_ASSIGN(NoNotificationMessageView
);
82 NoNotificationMessageView::NoNotificationMessageView() {
83 label_
= new views::Label(l10n_util::GetStringUTF16(
84 IDS_MESSAGE_CENTER_NO_MESSAGES
));
85 label_
->SetAutoColorReadabilityEnabled(false);
86 label_
->SetEnabledColor(kNoNotificationsTextColor
);
87 // Set transparent background to ensure that subpixel rendering
88 // is disabled. See crbug.com/169056
89 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
90 label_
->SetBackgroundColor(kTransparentColor
);
95 NoNotificationMessageView::~NoNotificationMessageView() {
98 gfx::Size
NoNotificationMessageView::GetPreferredSize() const {
99 return gfx::Size(kMinScrollViewHeight
, label_
->GetPreferredSize().width());
102 int NoNotificationMessageView::GetHeightForWidth(int width
) const {
103 return kMinScrollViewHeight
;
106 void NoNotificationMessageView::Layout() {
107 int text_height
= label_
->GetHeightForWidth(width());
108 int margin
= (height() - text_height
) / 2;
109 label_
->SetBounds(0, margin
, width(), text_height
);
112 // Displays a list of messages for rich notifications. Functions as an array of
113 // MessageViews and animates them on transitions. It also supports
115 class MessageListView
: public views::View
,
116 public views::BoundsAnimatorObserver
{
118 explicit MessageListView(MessageCenterView
* message_center_view
,
120 ~MessageListView() override
;
122 void AddNotificationAt(MessageView
* view
, int i
);
123 void RemoveNotification(MessageView
* view
);
124 void UpdateNotification(MessageView
* view
, const Notification
& notification
);
125 void SetRepositionTarget(const gfx::Rect
& target_rect
);
126 void ResetRepositionSession();
127 void ClearAllNotifications(const gfx::Rect
& visible_scroll_rect
);
130 // Overridden from views::View.
131 void Layout() override
;
132 gfx::Size
GetPreferredSize() const override
;
133 int GetHeightForWidth(int width
) const override
;
134 void PaintChildren(gfx::Canvas
* canvas
,
135 const views::CullSet
& cull_set
) override
;
136 void ReorderChildLayers(ui::Layer
* parent_layer
) override
;
138 // Overridden from views::BoundsAnimatorObserver.
139 void OnBoundsAnimatorProgressed(views::BoundsAnimator
* animator
) override
;
140 void OnBoundsAnimatorDone(views::BoundsAnimator
* animator
) override
;
143 bool IsValidChild(const views::View
* child
) const;
144 void DoUpdateIfPossible();
146 // Animates all notifications below target upwards to align with the top of
147 // the last closed notification.
148 void AnimateNotificationsBelowTarget();
149 // Animates all notifications above target downwards to align with the top of
150 // the last closed notification.
151 void AnimateNotificationsAboveTarget();
153 // Schedules animation for a child to the specified position. Returns false
154 // if |child| will disappear after the animation.
155 bool AnimateChild(views::View
* child
, int top
, int height
);
157 // Animate clearing one notification.
158 void AnimateClearingOneNotification();
159 MessageCenterView
* message_center_view() const {
160 return message_center_view_
;
163 MessageCenterView
* message_center_view_
; // Weak reference.
164 // The top position of the reposition target rectangle.
167 bool has_deferred_task_
;
168 bool clear_all_started_
;
170 std::set
<views::View
*> adding_views_
;
171 std::set
<views::View
*> deleting_views_
;
172 std::set
<views::View
*> deleted_when_done_
;
173 std::list
<views::View
*> clearing_all_views_
;
174 scoped_ptr
<views::BoundsAnimator
> animator_
;
175 base::WeakPtrFactory
<MessageListView
> weak_ptr_factory_
;
177 DISALLOW_COPY_AND_ASSIGN(MessageListView
);
180 MessageListView::MessageListView(MessageCenterView
* message_center_view
,
182 : message_center_view_(message_center_view
),
185 has_deferred_task_(false),
186 clear_all_started_(false),
188 weak_ptr_factory_(this) {
189 views::BoxLayout
* layout
=
190 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 1);
191 layout
->SetDefaultFlex(1);
192 SetLayoutManager(layout
);
194 // Set the margin to 0 for the layout. BoxLayout assumes the same margin
195 // for top and bottom, but the bottom margin here should be smaller
196 // because of the shadow of message view. Use an empty border instead
197 // to provide this margin.
198 gfx::Insets shadow_insets
= MessageView::GetShadowInsets();
199 set_background(views::Background::CreateSolidBackground(
200 kMessageCenterBackgroundColor
));
201 SetBorder(views::Border::CreateEmptyBorder(
202 top_down
? 0 : kMarginBetweenItems
- shadow_insets
.top(), /* top */
203 kMarginBetweenItems
- shadow_insets
.left(), /* left */
204 top_down
? kMarginBetweenItems
- shadow_insets
.bottom() : 0, /* bottom */
205 kMarginBetweenItems
- shadow_insets
.right() /* right */));
208 MessageListView::~MessageListView() {
210 animator_
->RemoveObserver(this);
213 void MessageListView::Layout() {
217 gfx::Rect child_area
= GetContentsBounds();
218 int top
= child_area
.y();
220 kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
222 for (int i
= 0; i
< child_count(); ++i
) {
223 views::View
* child
= child_at(i
);
224 if (!child
->visible())
226 int height
= child
->GetHeightForWidth(child_area
.width());
227 child
->SetBounds(child_area
.x(), top
, child_area
.width(), height
);
228 top
+= height
+ between_items
;
232 void MessageListView::AddNotificationAt(MessageView
* view
, int index
) {
233 // |index| refers to a position in a subset of valid children. |real_index|
234 // in a list includes the invalid children, so we compute the real index by
235 // walking the list until |index| number of valid children are encountered,
236 // or to the end of the list.
238 while (real_index
< child_count()) {
239 if (IsValidChild(child_at(real_index
))) {
247 AddChildViewAt(view
, real_index
);
248 if (GetContentsBounds().IsEmpty())
251 adding_views_
.insert(view
);
252 DoUpdateIfPossible();
255 void MessageListView::RemoveNotification(MessageView
* view
) {
256 DCHECK_EQ(view
->parent(), this);
257 if (GetContentsBounds().IsEmpty()) {
261 deleting_views_
.insert(view
);
264 animator_
->StopAnimatingView(view
);
267 DoUpdateIfPossible();
271 void MessageListView::UpdateNotification(MessageView
* view
,
272 const Notification
& notification
) {
273 int index
= GetIndexOf(view
);
274 DCHECK_LE(0, index
); // GetIndexOf is negative if not a child.
277 animator_
->StopAnimatingView(view
);
278 if (deleting_views_
.find(view
) != deleting_views_
.end())
279 deleting_views_
.erase(view
);
280 if (deleted_when_done_
.find(view
) != deleted_when_done_
.end())
281 deleted_when_done_
.erase(view
);
282 view
->UpdateWithNotification(notification
);
283 DoUpdateIfPossible();
286 gfx::Size
MessageListView::GetPreferredSize() const {
288 for (int i
= 0; i
< child_count(); i
++) {
289 const views::View
* child
= child_at(i
);
290 if (IsValidChild(child
))
291 width
= std::max(width
, child
->GetPreferredSize().width());
294 return gfx::Size(width
+ GetInsets().width(),
295 GetHeightForWidth(width
+ GetInsets().width()));
298 int MessageListView::GetHeightForWidth(int width
) const {
299 if (fixed_height_
> 0)
300 return fixed_height_
;
302 width
-= GetInsets().width();
305 for (int i
= 0; i
< child_count(); ++i
) {
306 const views::View
* child
= child_at(i
);
307 if (!IsValidChild(child
))
309 height
+= child
->GetHeightForWidth(width
) + padding
;
310 padding
= kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
313 return height
+ GetInsets().height();
316 void MessageListView::PaintChildren(gfx::Canvas
* canvas
,
317 const views::CullSet
& cull_set
) {
318 // Paint in the inversed order. Otherwise upper notification may be
319 // hidden by the lower one.
320 for (int i
= child_count() - 1; i
>= 0; --i
) {
321 if (!child_at(i
)->layer())
322 child_at(i
)->Paint(canvas
, cull_set
);
326 void MessageListView::ReorderChildLayers(ui::Layer
* parent_layer
) {
327 // Reorder children to stack the last child layer at the top. Otherwise
328 // upper notification may be hidden by the lower one.
329 for (int i
= 0; i
< child_count(); ++i
) {
330 if (child_at(i
)->layer())
331 parent_layer
->StackAtBottom(child_at(i
)->layer());
335 void MessageListView::SetRepositionTarget(const gfx::Rect
& target
) {
336 reposition_top_
= target
.y();
337 fixed_height_
= GetHeightForWidth(width());
340 void MessageListView::ResetRepositionSession() {
341 // Don't call DoUpdateIfPossible(), but let Layout() do the task without
342 // animation. Reset will cause the change of the bubble size itself, and
343 // animation from the old location will look weird.
344 if (reposition_top_
>= 0 && animator_
.get()) {
345 has_deferred_task_
= false;
346 // cancel cause OnBoundsAnimatorDone which deletes |deleted_when_done_|.
348 STLDeleteContainerPointers(deleting_views_
.begin(), deleting_views_
.end());
349 deleting_views_
.clear();
350 adding_views_
.clear();
354 reposition_top_
= -1;
358 void MessageListView::ClearAllNotifications(
359 const gfx::Rect
& visible_scroll_rect
) {
360 for (int i
= 0; i
< child_count(); ++i
) {
361 views::View
* child
= child_at(i
);
362 if (!child
->visible())
364 if (gfx::IntersectRects(child
->bounds(), visible_scroll_rect
).IsEmpty())
366 clearing_all_views_
.push_back(child
);
368 DoUpdateIfPossible();
371 void MessageListView::OnBoundsAnimatorProgressed(
372 views::BoundsAnimator
* animator
) {
373 DCHECK_EQ(animator_
.get(), animator
);
374 for (std::set
<views::View
*>::iterator iter
= deleted_when_done_
.begin();
375 iter
!= deleted_when_done_
.end(); ++iter
) {
376 const gfx::SlideAnimation
* animation
= animator
->GetAnimationForView(*iter
);
378 (*iter
)->layer()->SetOpacity(animation
->CurrentValueBetween(1.0, 0.0));
382 void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator
* animator
) {
383 STLDeleteContainerPointers(
384 deleted_when_done_
.begin(), deleted_when_done_
.end());
385 deleted_when_done_
.clear();
387 if (clear_all_started_
) {
388 clear_all_started_
= false;
389 message_center_view()->OnAllNotificationsCleared();
392 if (has_deferred_task_
) {
393 has_deferred_task_
= false;
394 DoUpdateIfPossible();
398 GetWidget()->SynthesizeMouseMoveEvent();
401 bool MessageListView::IsValidChild(const views::View
* child
) const {
402 return child
->visible() &&
403 deleting_views_
.find(const_cast<views::View
*>(child
)) ==
404 deleting_views_
.end() &&
405 deleted_when_done_
.find(const_cast<views::View
*>(child
)) ==
406 deleted_when_done_
.end();
409 void MessageListView::DoUpdateIfPossible() {
410 gfx::Rect child_area
= GetContentsBounds();
411 if (child_area
.IsEmpty())
414 if (animator_
.get() && animator_
->IsAnimating()) {
415 has_deferred_task_
= true;
419 if (!animator_
.get()) {
420 animator_
.reset(new views::BoundsAnimator(this));
421 animator_
->AddObserver(this);
424 if (!clearing_all_views_
.empty()) {
425 AnimateClearingOneNotification();
430 CommandLine::ForCurrentProcess()->HasSwitch(
431 switches::kEnableMessageCenterAlwaysScrollUpUponNotificationRemoval
))
432 AnimateNotificationsBelowTarget();
434 AnimateNotificationsAboveTarget();
436 adding_views_
.clear();
437 deleting_views_
.clear();
440 void MessageListView::AnimateNotificationsBelowTarget() {
442 for (int i
= 0; i
< child_count(); ++i
) {
443 views::View
* child
= child_at(i
);
444 if (!IsValidChild(child
)) {
445 AnimateChild(child
, child
->y(), child
->height());
446 } else if (reposition_top_
< 0 || child
->y() > reposition_top_
) {
447 // Find first notification below target (or all notifications if no
453 if (last_index
> 0) {
455 kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
456 int top
= (reposition_top_
> 0) ? reposition_top_
: GetInsets().top();
458 for (int i
= last_index
; i
< child_count(); ++i
) {
459 // Animate notifications below target upwards.
460 views::View
* child
= child_at(i
);
461 if (AnimateChild(child
, top
, child
->height()))
462 top
+= child
->height() + between_items
;
467 void MessageListView::AnimateNotificationsAboveTarget() {
469 for (int i
= child_count() - 1; i
>= 0; --i
) {
470 views::View
* child
= child_at(i
);
471 if (!IsValidChild(child
)) {
472 AnimateChild(child
, child
->y(), child
->height());
473 } else if (reposition_top_
< 0 || child
->y() < reposition_top_
) {
474 // Find first notification above target (or all notifications if no
480 if (last_index
>= 0) {
482 kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
483 int bottom
= (reposition_top_
> 0)
484 ? reposition_top_
+ child_at(last_index
)->height()
485 : GetHeightForWidth(width()) - GetInsets().bottom();
486 for (int i
= last_index
; i
>= 0; --i
) {
487 // Animate notifications above target downwards.
488 views::View
* child
= child_at(i
);
489 if (AnimateChild(child
, bottom
- child
->height(), child
->height()))
490 bottom
-= child
->height() + between_items
;
495 bool MessageListView::AnimateChild(views::View
* child
, int top
, int height
) {
496 gfx::Rect child_area
= GetContentsBounds();
497 if (adding_views_
.find(child
) != adding_views_
.end()) {
498 child
->SetBounds(child_area
.right(), top
, child_area
.width(), height
);
499 animator_
->AnimateViewTo(
500 child
, gfx::Rect(child_area
.x(), top
, child_area
.width(), height
));
501 } else if (deleting_views_
.find(child
) != deleting_views_
.end()) {
502 DCHECK(child
->layer());
503 // No moves, but animate to fade-out.
504 animator_
->AnimateViewTo(child
, child
->bounds());
505 deleted_when_done_
.insert(child
);
508 gfx::Rect
target(child_area
.x(), top
, child_area
.width(), height
);
509 if (child
->bounds().origin() != target
.origin())
510 animator_
->AnimateViewTo(child
, target
);
512 child
->SetBoundsRect(target
);
517 void MessageListView::AnimateClearingOneNotification() {
518 DCHECK(!clearing_all_views_
.empty());
520 clear_all_started_
= true;
522 views::View
* child
= clearing_all_views_
.front();
523 clearing_all_views_
.pop_front();
525 // Slide from left to right.
526 gfx::Rect new_bounds
= child
->bounds();
527 new_bounds
.set_x(new_bounds
.right() + kMarginBetweenItems
);
528 animator_
->AnimateViewTo(child
, new_bounds
);
530 // Schedule to start sliding out next notification after a short delay.
531 if (!clearing_all_views_
.empty()) {
532 base::MessageLoop::current()->PostDelayedTask(
534 base::Bind(&MessageListView::AnimateClearingOneNotification
,
535 weak_ptr_factory_
.GetWeakPtr()),
536 base::TimeDelta::FromMilliseconds(
537 kAnimateClearingNextNotificationDelayMS
));
541 // MessageCenterView ///////////////////////////////////////////////////////////
543 MessageCenterView::MessageCenterView(MessageCenter
* message_center
,
544 MessageCenterTray
* tray
,
546 bool initially_settings_visible
,
548 const base::string16
& title
)
549 : message_center_(message_center
),
552 settings_view_(NULL
),
555 settings_visible_(initially_settings_visible
),
561 context_menu_controller_(new MessageViewContextMenuController(this)) {
562 message_center_
->AddObserver(this);
563 set_notify_enter_exit_on_child(true);
564 set_background(views::Background::CreateSolidBackground(
565 kMessageCenterBackgroundColor
));
567 NotifierSettingsProvider
* notifier_settings_provider
=
568 message_center_
->GetNotifierSettingsProvider();
569 button_bar_
= new MessageCenterButtonBar(this,
571 notifier_settings_provider
,
572 initially_settings_visible
,
575 const int button_height
= button_bar_
->GetPreferredSize().height();
577 scroller_
= new views::ScrollView();
578 scroller_
->ClipHeightTo(kMinScrollViewHeight
, max_height
- button_height
);
579 scroller_
->SetVerticalScrollBar(new views::OverlayScrollBar(false));
580 scroller_
->set_background(
581 views::Background::CreateSolidBackground(kMessageCenterBackgroundColor
));
583 scroller_
->SetPaintToLayer(true);
584 scroller_
->SetFillsBoundsOpaquely(false);
585 scroller_
->layer()->SetMasksToBounds(true);
587 empty_list_view_
.reset(new NoNotificationMessageView
);
588 empty_list_view_
->set_owned_by_client();
589 message_list_view_
.reset(new MessageListView(this, top_down
));
590 message_list_view_
->set_owned_by_client();
592 // We want to swap the contents of the scroll view between the empty list
593 // view and the message list view, without constructing them afresh each
594 // time. So, since the scroll view deletes old contents each time you
595 // set the contents (regardless of the |owned_by_client_| setting) we need
596 // an intermediate view for the contents whose children we can swap in and
598 views::View
* scroller_contents
= new views::View();
599 scroller_contents
->SetLayoutManager(new views::FillLayout());
600 scroller_contents
->AddChildView(empty_list_view_
.get());
601 scroller_
->SetContents(scroller_contents
);
603 settings_view_
= new NotifierSettingsView(notifier_settings_provider
);
605 if (initially_settings_visible
)
606 scroller_
->SetVisible(false);
608 settings_view_
->SetVisible(false);
610 AddChildView(scroller_
);
611 AddChildView(settings_view_
);
612 AddChildView(button_bar_
);
615 MessageCenterView::~MessageCenterView() {
617 message_center_
->RemoveObserver(this);
620 void MessageCenterView::SetNotifications(
621 const NotificationList::Notifications
& notifications
) {
625 notification_views_
.clear();
628 for (NotificationList::Notifications::const_iterator iter
=
629 notifications
.begin(); iter
!= notifications
.end(); ++iter
) {
630 AddNotificationAt(*(*iter
), index
++);
632 message_center_
->DisplayedNotification(
633 (*iter
)->id(), message_center::DISPLAY_SOURCE_MESSAGE_CENTER
);
634 if (notification_views_
.size() >= kMaxVisibleMessageCenterNotifications
)
638 NotificationsChanged();
639 scroller_
->RequestFocus();
642 void MessageCenterView::SetSettingsVisible(bool visible
) {
646 if (visible
== settings_visible_
)
649 settings_visible_
= visible
;
652 source_view_
= scroller_
;
653 target_view_
= settings_view_
;
655 source_view_
= settings_view_
;
656 target_view_
= scroller_
;
658 source_height_
= source_view_
->GetHeightForWidth(width());
659 target_height_
= target_view_
->GetHeightForWidth(width());
661 gfx::MultiAnimation::Parts parts
;
662 // First part: slide resize animation.
663 parts
.push_back(gfx::MultiAnimation::Part(
664 (source_height_
== target_height_
) ? 0 : kDefaultAnimationDurationMs
,
665 gfx::Tween::EASE_OUT
));
666 // Second part: fade-out the source_view.
667 if (source_view_
->layer()) {
668 parts
.push_back(gfx::MultiAnimation::Part(
669 kDefaultAnimationDurationMs
, gfx::Tween::LINEAR
));
671 parts
.push_back(gfx::MultiAnimation::Part());
673 // Third part: fade-in the target_view.
674 if (target_view_
->layer()) {
675 parts
.push_back(gfx::MultiAnimation::Part(
676 kDefaultAnimationDurationMs
, gfx::Tween::LINEAR
));
677 target_view_
->layer()->SetOpacity(0);
678 target_view_
->SetVisible(true);
680 parts
.push_back(gfx::MultiAnimation::Part());
682 settings_transition_animation_
.reset(new gfx::MultiAnimation(
683 parts
, base::TimeDelta::FromMicroseconds(1000000 / kDefaultFrameRateHz
)));
684 settings_transition_animation_
->set_delegate(this);
685 settings_transition_animation_
->set_continuous(false);
686 settings_transition_animation_
->Start();
688 button_bar_
->SetBackArrowVisible(visible
);
691 void MessageCenterView::ClearAllNotifications() {
695 SetViewHierarchyEnabled(scroller_
, false);
696 button_bar_
->SetAllButtonsEnabled(false);
697 message_list_view_
->ClearAllNotifications(scroller_
->GetVisibleRect());
700 void MessageCenterView::OnAllNotificationsCleared() {
701 SetViewHierarchyEnabled(scroller_
, true);
702 button_bar_
->SetAllButtonsEnabled(true);
703 button_bar_
->SetCloseAllButtonEnabled(false);
704 message_center_
->RemoveAllVisibleNotifications(true); // Action by user.
707 size_t MessageCenterView::NumMessageViewsForTest() const {
708 return message_list_view_
->child_count();
711 void MessageCenterView::OnSettingsChanged() {
712 scroller_
->InvalidateLayout();
713 PreferredSizeChanged();
717 void MessageCenterView::SetIsClosing(bool is_closing
) {
718 is_closing_
= is_closing
;
720 message_center_
->RemoveObserver(this);
722 message_center_
->AddObserver(this);
725 void MessageCenterView::Layout() {
729 int button_height
= button_bar_
->GetHeightForWidth(width()) +
730 button_bar_
->GetInsets().height();
731 // Skip unnecessary re-layout of contents during the resize animation.
732 bool animating
= settings_transition_animation_
&&
733 settings_transition_animation_
->is_animating();
734 if (animating
&& settings_transition_animation_
->current_part_index() == 0) {
736 button_bar_
->SetBounds(
737 0, height() - button_height
, width(), button_height
);
742 scroller_
->SetBounds(0,
743 top_down_
? button_height
: 0,
745 height() - button_height
);
746 settings_view_
->SetBounds(0,
747 top_down_
? button_height
: 0,
749 height() - button_height
);
751 bool is_scrollable
= false;
752 if (scroller_
->visible())
753 is_scrollable
= scroller_
->height() < message_list_view_
->height();
755 is_scrollable
= settings_view_
->IsScrollable();
759 // Draw separator line on the top of the button bar if it is on the bottom
760 // or draw it at the bottom if the bar is on the top.
761 button_bar_
->SetBorder(views::Border::CreateSolidSidedBorder(
762 top_down_
? 0 : 1, 0, top_down_
? 1 : 0, 0, kFooterDelimiterColor
));
764 button_bar_
->SetBorder(views::Border::CreateEmptyBorder(
765 top_down_
? 0 : 1, 0, top_down_
? 1 : 0, 0));
767 button_bar_
->SchedulePaint();
769 button_bar_
->SetBounds(0,
770 top_down_
? 0 : height() - button_height
,
774 GetWidget()->GetRootView()->SchedulePaint();
777 gfx::Size
MessageCenterView::GetPreferredSize() const {
778 if (settings_transition_animation_
&&
779 settings_transition_animation_
->is_animating()) {
780 int content_width
= std::max(source_view_
->GetPreferredSize().width(),
781 target_view_
->GetPreferredSize().width());
782 int width
= std::max(content_width
,
783 button_bar_
->GetPreferredSize().width());
784 return gfx::Size(width
, GetHeightForWidth(width
));
788 for (int i
= 0; i
< child_count(); ++i
) {
789 const views::View
* child
= child_at(0);
790 if (child
->visible())
791 width
= std::max(width
, child
->GetPreferredSize().width());
793 return gfx::Size(width
, GetHeightForWidth(width
));
796 int MessageCenterView::GetHeightForWidth(int width
) const {
797 if (settings_transition_animation_
&&
798 settings_transition_animation_
->is_animating()) {
799 int content_height
= target_height_
;
800 if (settings_transition_animation_
->current_part_index() == 0) {
801 content_height
= settings_transition_animation_
->CurrentValueBetween(
802 source_height_
, target_height_
);
804 return button_bar_
->GetHeightForWidth(width
) + content_height
;
807 int content_height
= 0;
808 if (scroller_
->visible())
809 content_height
+= scroller_
->GetHeightForWidth(width
);
811 content_height
+= settings_view_
->GetHeightForWidth(width
);
812 return button_bar_
->GetHeightForWidth(width
) +
813 button_bar_
->GetInsets().height() + content_height
;
816 bool MessageCenterView::OnMouseWheel(const ui::MouseWheelEvent
& event
) {
817 // Do not rely on the default scroll event handler of ScrollView because
818 // the scroll happens only when the focus is on the ScrollView. The
819 // notification center will allow the scrolling even when the focus is on
821 if (scroller_
->bounds().Contains(event
.location()))
822 return scroller_
->OnMouseWheel(event
);
823 return views::View::OnMouseWheel(event
);
826 void MessageCenterView::OnMouseExited(const ui::MouseEvent
& event
) {
830 message_list_view_
->ResetRepositionSession();
831 NotificationsChanged();
834 void MessageCenterView::OnNotificationAdded(const std::string
& id
) {
836 const NotificationList::Notifications
& notifications
=
837 message_center_
->GetVisibleNotifications();
838 for (NotificationList::Notifications::const_iterator iter
=
839 notifications
.begin(); iter
!= notifications
.end();
841 if ((*iter
)->id() == id
) {
842 AddNotificationAt(*(*iter
), index
);
845 if (notification_views_
.size() >= kMaxVisibleMessageCenterNotifications
)
848 NotificationsChanged();
851 void MessageCenterView::OnNotificationRemoved(const std::string
& id
,
853 NotificationViewsMap::iterator view_iter
= notification_views_
.find(id
);
854 if (view_iter
== notification_views_
.end())
856 NotificationView
* view
= view_iter
->second
;
857 int index
= message_list_view_
->GetIndexOf(view
);
860 message_list_view_
->SetRepositionTarget(view
->bounds());
861 // Moves the keyboard focus to the next notification if the removed
862 // notification is focused so that the user can dismiss notifications
863 // without re-focusing by tab key.
864 if (view
->IsCloseButtonFocused() ||
865 view
== GetFocusManager()->GetFocusedView()) {
866 views::View
* next_focused_view
= NULL
;
867 if (message_list_view_
->child_count() > index
+ 1)
868 next_focused_view
= message_list_view_
->child_at(index
+ 1);
870 next_focused_view
= message_list_view_
->child_at(index
- 1);
872 if (next_focused_view
) {
873 if (view
->IsCloseButtonFocused()) {
874 // Safe cast since all views in MessageListView are MessageViews.
875 static_cast<MessageView
*>(
876 next_focused_view
)->RequestFocusOnCloseButton();
878 next_focused_view
->RequestFocus();
883 message_list_view_
->RemoveNotification(view
);
884 notification_views_
.erase(view_iter
);
885 NotificationsChanged();
888 void MessageCenterView::OnNotificationUpdated(const std::string
& id
) {
889 NotificationViewsMap::const_iterator view_iter
= notification_views_
.find(id
);
890 if (view_iter
== notification_views_
.end())
892 NotificationView
* view
= view_iter
->second
;
893 // TODO(dimich): add MessageCenter::GetVisibleNotificationById(id)
894 const NotificationList::Notifications
& notifications
=
895 message_center_
->GetVisibleNotifications();
896 for (NotificationList::Notifications::const_iterator iter
=
897 notifications
.begin(); iter
!= notifications
.end(); ++iter
) {
898 if ((*iter
)->id() == id
) {
899 int old_width
= view
->width();
900 int old_height
= view
->GetHeightForWidth(old_width
);
901 message_list_view_
->UpdateNotification(view
, **iter
);
902 if (view
->GetHeightForWidth(old_width
) != old_height
)
903 NotificationsChanged();
909 void MessageCenterView::ClickOnNotification(
910 const std::string
& notification_id
) {
911 message_center_
->ClickOnNotification(notification_id
);
914 void MessageCenterView::RemoveNotification(const std::string
& notification_id
,
916 message_center_
->RemoveNotification(notification_id
, by_user
);
919 scoped_ptr
<ui::MenuModel
> MessageCenterView::CreateMenuModel(
920 const NotifierId
& notifier_id
,
921 const base::string16
& display_source
) {
922 return tray_
->CreateNotificationMenuModel(notifier_id
, display_source
);
925 bool MessageCenterView::HasClickedListener(const std::string
& notification_id
) {
926 return message_center_
->HasClickedListener(notification_id
);
929 void MessageCenterView::ClickOnNotificationButton(
930 const std::string
& notification_id
,
932 message_center_
->ClickOnNotificationButton(notification_id
, button_index
);
935 void MessageCenterView::AnimationEnded(const gfx::Animation
* animation
) {
936 DCHECK_EQ(animation
, settings_transition_animation_
.get());
938 Visibility visibility
= target_view_
== settings_view_
939 ? VISIBILITY_SETTINGS
940 : VISIBILITY_MESSAGE_CENTER
;
941 message_center_
->SetVisibility(visibility
);
943 source_view_
->SetVisible(false);
944 target_view_
->SetVisible(true);
945 if (source_view_
->layer())
946 source_view_
->layer()->SetOpacity(1.0);
947 if (target_view_
->layer())
948 target_view_
->layer()->SetOpacity(1.0);
949 settings_transition_animation_
.reset();
950 PreferredSizeChanged();
954 void MessageCenterView::AnimationProgressed(const gfx::Animation
* animation
) {
955 DCHECK_EQ(animation
, settings_transition_animation_
.get());
956 PreferredSizeChanged();
957 if (settings_transition_animation_
->current_part_index() == 1 &&
958 source_view_
->layer()) {
959 source_view_
->layer()->SetOpacity(
960 1.0 - settings_transition_animation_
->GetCurrentValue());
962 } else if (settings_transition_animation_
->current_part_index() == 2 &&
963 target_view_
->layer()) {
964 target_view_
->layer()->SetOpacity(
965 settings_transition_animation_
->GetCurrentValue());
970 void MessageCenterView::AnimationCanceled(const gfx::Animation
* animation
) {
971 DCHECK_EQ(animation
, settings_transition_animation_
.get());
972 AnimationEnded(animation
);
975 void MessageCenterView::AddNotificationAt(const Notification
& notification
,
977 NotificationView
* view
=
978 NotificationView::Create(this, notification
, false); // Not top-level.
979 view
->set_context_menu_controller(context_menu_controller_
.get());
980 notification_views_
[notification
.id()] = view
;
981 view
->set_scroller(scroller_
);
982 message_list_view_
->AddNotificationAt(view
, index
);
985 void MessageCenterView::NotificationsChanged() {
986 bool no_message_views
= notification_views_
.empty();
988 // When the child view is removed from the hierarchy, its focus is cleared.
989 // In this case we want to save which view has focus so that the user can
990 // continue to interact with notifications in the order they were expecting.
991 views::FocusManager
* focus_manager
= scroller_
->GetFocusManager();
992 View
* focused_view
= NULL
;
993 // |focus_manager| can be NULL in tests.
995 focused_view
= focus_manager
->GetFocusedView();
997 // All the children of this view are owned by |this|.
998 scroller_
->contents()->RemoveAllChildViews(/*delete_children=*/false);
999 scroller_
->contents()->AddChildView(
1000 no_message_views
? empty_list_view_
.get() : message_list_view_
.get());
1002 button_bar_
->SetCloseAllButtonEnabled(!no_message_views
);
1003 scroller_
->SetFocusable(!no_message_views
);
1005 if (focus_manager
&& focused_view
)
1006 focus_manager
->SetFocusedView(focused_view
);
1008 scroller_
->InvalidateLayout();
1009 PreferredSizeChanged();
1013 void MessageCenterView::SetNotificationViewForTest(MessageView
* view
) {
1014 message_list_view_
->AddNotificationAt(view
, 0);
1017 } // namespace message_center