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/gfx/animation/multi_animation.h"
17 #include "ui/gfx/animation/slide_animation.h"
18 #include "ui/gfx/canvas.h"
19 #include "ui/gfx/insets.h"
20 #include "ui/gfx/rect.h"
21 #include "ui/gfx/size.h"
22 #include "ui/message_center/message_center.h"
23 #include "ui/message_center/message_center_style.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/views/animation/bounds_animator.h"
32 #include "ui/views/animation/bounds_animator_observer.h"
33 #include "ui/views/background.h"
34 #include "ui/views/border.h"
35 #include "ui/views/controls/button/button.h"
36 #include "ui/views/controls/label.h"
37 #include "ui/views/controls/scroll_view.h"
38 #include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
39 #include "ui/views/layout/box_layout.h"
40 #include "ui/views/layout/fill_layout.h"
41 #include "ui/views/widget/widget.h"
43 namespace message_center
{
47 const SkColor kNoNotificationsTextColor
= SkColorSetRGB(0xb4, 0xb4, 0xb4);
48 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
49 const SkColor kTransparentColor
= SkColorSetARGB(0, 0, 0, 0);
51 const int kAnimateClearingNextNotificationDelayMS
= 40;
53 const int kDefaultAnimationDurationMs
= 120;
54 const int kDefaultFrameRateHz
= 60;
57 class NoNotificationMessageView
: public views::View
{
59 NoNotificationMessageView();
60 virtual ~NoNotificationMessageView();
62 // Overridden from views::View.
63 virtual gfx::Size
GetPreferredSize() const OVERRIDE
;
64 virtual int GetHeightForWidth(int width
) const OVERRIDE
;
65 virtual void Layout() OVERRIDE
;
70 DISALLOW_COPY_AND_ASSIGN(NoNotificationMessageView
);
73 NoNotificationMessageView::NoNotificationMessageView() {
74 label_
= new views::Label(l10n_util::GetStringUTF16(
75 IDS_MESSAGE_CENTER_NO_MESSAGES
));
76 label_
->SetAutoColorReadabilityEnabled(false);
77 label_
->SetEnabledColor(kNoNotificationsTextColor
);
78 // Set transparent background to ensure that subpixel rendering
79 // is disabled. See crbug.com/169056
80 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
81 label_
->SetBackgroundColor(kTransparentColor
);
86 NoNotificationMessageView::~NoNotificationMessageView() {
89 gfx::Size
NoNotificationMessageView::GetPreferredSize() const {
90 return gfx::Size(kMinScrollViewHeight
, label_
->GetPreferredSize().width());
93 int NoNotificationMessageView::GetHeightForWidth(int width
) const {
94 return kMinScrollViewHeight
;
97 void NoNotificationMessageView::Layout() {
98 int text_height
= label_
->GetHeightForWidth(width());
99 int margin
= (height() - text_height
) / 2;
100 label_
->SetBounds(0, margin
, width(), text_height
);
103 // Displays a list of messages for rich notifications. Functions as an array of
104 // MessageViews and animates them on transitions. It also supports
106 class MessageListView
: public views::View
,
107 public views::BoundsAnimatorObserver
{
109 explicit MessageListView(MessageCenterView
* message_center_view
,
111 virtual ~MessageListView();
113 void AddNotificationAt(MessageView
* view
, int i
);
114 void RemoveNotification(MessageView
* view
);
115 void UpdateNotification(MessageView
* view
, const Notification
& notification
);
116 void SetRepositionTarget(const gfx::Rect
& target_rect
);
117 void ResetRepositionSession();
118 void ClearAllNotifications(const gfx::Rect
& visible_scroll_rect
);
121 // Overridden from views::View.
122 virtual void Layout() OVERRIDE
;
123 virtual gfx::Size
GetPreferredSize() const OVERRIDE
;
124 virtual int GetHeightForWidth(int width
) const OVERRIDE
;
125 virtual void PaintChildren(gfx::Canvas
* canvas
,
126 const views::CullSet
& cull_set
) 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(const views::View
* child
) const;
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
->SetDefaultFlex(1);
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 const Notification
& notification
) {
265 int index
= GetIndexOf(view
);
266 DCHECK_LE(0, index
); // GetIndexOf is negative if not a child.
269 animator_
->StopAnimatingView(view
);
270 if (deleting_views_
.find(view
) != deleting_views_
.end())
271 deleting_views_
.erase(view
);
272 if (deleted_when_done_
.find(view
) != deleted_when_done_
.end())
273 deleted_when_done_
.erase(view
);
274 view
->UpdateWithNotification(notification
);
275 DoUpdateIfPossible();
278 gfx::Size
MessageListView::GetPreferredSize() const {
280 for (int i
= 0; i
< child_count(); i
++) {
281 const views::View
* child
= child_at(i
);
282 if (IsValidChild(child
))
283 width
= std::max(width
, child
->GetPreferredSize().width());
286 return gfx::Size(width
+ GetInsets().width(),
287 GetHeightForWidth(width
+ GetInsets().width()));
290 int MessageListView::GetHeightForWidth(int width
) const {
291 if (fixed_height_
> 0)
292 return fixed_height_
;
294 width
-= GetInsets().width();
297 for (int i
= 0; i
< child_count(); ++i
) {
298 const views::View
* child
= child_at(i
);
299 if (!IsValidChild(child
))
301 height
+= child
->GetHeightForWidth(width
) + padding
;
302 padding
= kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
305 return height
+ GetInsets().height();
308 void MessageListView::PaintChildren(gfx::Canvas
* canvas
,
309 const views::CullSet
& cull_set
) {
310 // Paint in the inversed order. Otherwise upper notification may be
311 // hidden by the lower one.
312 for (int i
= child_count() - 1; i
>= 0; --i
) {
313 if (!child_at(i
)->layer())
314 child_at(i
)->Paint(canvas
, cull_set
);
318 void MessageListView::ReorderChildLayers(ui::Layer
* parent_layer
) {
319 // Reorder children to stack the last child layer at the top. Otherwise
320 // upper notification may be hidden by the lower one.
321 for (int i
= 0; i
< child_count(); ++i
) {
322 if (child_at(i
)->layer())
323 parent_layer
->StackAtBottom(child_at(i
)->layer());
327 void MessageListView::SetRepositionTarget(const gfx::Rect
& target
) {
328 reposition_top_
= target
.y();
329 fixed_height_
= GetHeightForWidth(width());
332 void MessageListView::ResetRepositionSession() {
333 // Don't call DoUpdateIfPossible(), but let Layout() do the task without
334 // animation. Reset will cause the change of the bubble size itself, and
335 // animation from the old location will look weird.
336 if (reposition_top_
>= 0 && animator_
.get()) {
337 has_deferred_task_
= false;
338 // cancel cause OnBoundsAnimatorDone which deletes |deleted_when_done_|.
340 STLDeleteContainerPointers(deleting_views_
.begin(), deleting_views_
.end());
341 deleting_views_
.clear();
342 adding_views_
.clear();
346 reposition_top_
= -1;
350 void MessageListView::ClearAllNotifications(
351 const gfx::Rect
& visible_scroll_rect
) {
352 for (int i
= 0; i
< child_count(); ++i
) {
353 views::View
* child
= child_at(i
);
354 if (!child
->visible())
356 if (gfx::IntersectRects(child
->bounds(), visible_scroll_rect
).IsEmpty())
358 clearing_all_views_
.push_back(child
);
360 DoUpdateIfPossible();
363 void MessageListView::OnBoundsAnimatorProgressed(
364 views::BoundsAnimator
* animator
) {
365 DCHECK_EQ(animator_
.get(), animator
);
366 for (std::set
<views::View
*>::iterator iter
= deleted_when_done_
.begin();
367 iter
!= deleted_when_done_
.end(); ++iter
) {
368 const gfx::SlideAnimation
* animation
= animator
->GetAnimationForView(*iter
);
370 (*iter
)->layer()->SetOpacity(animation
->CurrentValueBetween(1.0, 0.0));
374 void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator
* animator
) {
375 STLDeleteContainerPointers(
376 deleted_when_done_
.begin(), deleted_when_done_
.end());
377 deleted_when_done_
.clear();
379 if (clear_all_started_
) {
380 clear_all_started_
= false;
381 message_center_view()->OnAllNotificationsCleared();
384 if (has_deferred_task_
) {
385 has_deferred_task_
= false;
386 DoUpdateIfPossible();
390 GetWidget()->SynthesizeMouseMoveEvent();
393 bool MessageListView::IsValidChild(const views::View
* child
) const {
394 return child
->visible() &&
395 deleting_views_
.find(const_cast<views::View
*>(child
)) ==
396 deleting_views_
.end() &&
397 deleted_when_done_
.find(const_cast<views::View
*>(child
)) ==
398 deleted_when_done_
.end();
401 void MessageListView::DoUpdateIfPossible() {
402 gfx::Rect child_area
= GetContentsBounds();
403 if (child_area
.IsEmpty())
406 if (animator_
.get() && animator_
->IsAnimating()) {
407 has_deferred_task_
= true;
411 if (!animator_
.get()) {
412 animator_
.reset(new views::BoundsAnimator(this));
413 animator_
->AddObserver(this);
416 if (!clearing_all_views_
.empty()) {
417 AnimateClearingOneNotification();
422 AnimateNotificationsBelowTarget();
424 AnimateNotificationsAboveTarget();
426 adding_views_
.clear();
427 deleting_views_
.clear();
430 void MessageListView::AnimateNotificationsBelowTarget() {
432 for (int i
= 0; i
< child_count(); ++i
) {
433 views::View
* child
= child_at(i
);
434 if (!IsValidChild(child
)) {
435 AnimateChild(child
, child
->y(), child
->height());
436 } else if (reposition_top_
< 0 || child
->y() > reposition_top_
) {
437 // Find first notification below target (or all notifications if no
443 if (last_index
> 0) {
445 kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
446 int top
= (reposition_top_
> 0) ? reposition_top_
: GetInsets().top();
448 for (int i
= last_index
; i
< child_count(); ++i
) {
449 // Animate notifications below target upwards.
450 views::View
* child
= child_at(i
);
451 if (AnimateChild(child
, top
, child
->height()))
452 top
+= child
->height() + between_items
;
457 void MessageListView::AnimateNotificationsAboveTarget() {
459 for (int i
= child_count() - 1; i
>= 0; --i
) {
460 views::View
* child
= child_at(i
);
461 if (!IsValidChild(child
)) {
462 AnimateChild(child
, child
->y(), child
->height());
463 } else if (reposition_top_
< 0 || child
->y() < reposition_top_
) {
464 // Find first notification above target (or all notifications if no
470 if (last_index
>= 0) {
472 kMarginBetweenItems
- MessageView::GetShadowInsets().bottom();
473 int bottom
= (reposition_top_
> 0)
474 ? reposition_top_
+ child_at(last_index
)->height()
475 : GetHeightForWidth(width()) - GetInsets().bottom();
476 for (int i
= last_index
; i
>= 0; --i
) {
477 // Animate notifications above target downwards.
478 views::View
* child
= child_at(i
);
479 if (AnimateChild(child
, bottom
- child
->height(), child
->height()))
480 bottom
-= child
->height() + between_items
;
485 bool MessageListView::AnimateChild(views::View
* child
, int top
, int height
) {
486 gfx::Rect child_area
= GetContentsBounds();
487 if (adding_views_
.find(child
) != adding_views_
.end()) {
488 child
->SetBounds(child_area
.right(), top
, child_area
.width(), height
);
489 animator_
->AnimateViewTo(
490 child
, gfx::Rect(child_area
.x(), top
, child_area
.width(), height
));
491 } else if (deleting_views_
.find(child
) != deleting_views_
.end()) {
492 DCHECK(child
->layer());
493 // No moves, but animate to fade-out.
494 animator_
->AnimateViewTo(child
, child
->bounds());
495 deleted_when_done_
.insert(child
);
498 gfx::Rect
target(child_area
.x(), top
, child_area
.width(), height
);
499 if (child
->bounds().origin() != target
.origin())
500 animator_
->AnimateViewTo(child
, target
);
502 child
->SetBoundsRect(target
);
507 void MessageListView::AnimateClearingOneNotification() {
508 DCHECK(!clearing_all_views_
.empty());
510 clear_all_started_
= true;
512 views::View
* child
= clearing_all_views_
.front();
513 clearing_all_views_
.pop_front();
515 // Slide from left to right.
516 gfx::Rect new_bounds
= child
->bounds();
517 new_bounds
.set_x(new_bounds
.right() + kMarginBetweenItems
);
518 animator_
->AnimateViewTo(child
, new_bounds
);
520 // Schedule to start sliding out next notification after a short delay.
521 if (!clearing_all_views_
.empty()) {
522 base::MessageLoop::current()->PostDelayedTask(
524 base::Bind(&MessageListView::AnimateClearingOneNotification
,
525 weak_ptr_factory_
.GetWeakPtr()),
526 base::TimeDelta::FromMilliseconds(
527 kAnimateClearingNextNotificationDelayMS
));
531 // MessageCenterView ///////////////////////////////////////////////////////////
533 MessageCenterView::MessageCenterView(MessageCenter
* message_center
,
534 MessageCenterTray
* tray
,
536 bool initially_settings_visible
,
538 const base::string16
& title
)
539 : message_center_(message_center
),
542 settings_view_(NULL
),
545 settings_visible_(initially_settings_visible
),
551 context_menu_controller_(new MessageViewContextMenuController(this)) {
552 message_center_
->AddObserver(this);
553 set_notify_enter_exit_on_child(true);
554 set_background(views::Background::CreateSolidBackground(
555 kMessageCenterBackgroundColor
));
557 NotifierSettingsProvider
* notifier_settings_provider
=
558 message_center_
->GetNotifierSettingsProvider();
559 button_bar_
= new MessageCenterButtonBar(this,
561 notifier_settings_provider
,
562 initially_settings_visible
,
565 const int button_height
= button_bar_
->GetPreferredSize().height();
567 scroller_
= new views::ScrollView();
568 scroller_
->ClipHeightTo(kMinScrollViewHeight
, max_height
- button_height
);
569 scroller_
->SetVerticalScrollBar(new views::OverlayScrollBar(false));
570 scroller_
->set_background(
571 views::Background::CreateSolidBackground(kMessageCenterBackgroundColor
));
573 scroller_
->SetPaintToLayer(true);
574 scroller_
->SetFillsBoundsOpaquely(false);
575 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(
623 (*iter
)->id(), message_center::DISPLAY_SOURCE_MESSAGE_CENTER
);
624 if (notification_views_
.size() >= kMaxVisibleMessageCenterNotifications
)
628 NotificationsChanged();
629 scroller_
->RequestFocus();
632 void MessageCenterView::SetSettingsVisible(bool visible
) {
636 if (visible
== settings_visible_
)
639 settings_visible_
= visible
;
642 source_view_
= scroller_
;
643 target_view_
= settings_view_
;
645 source_view_
= settings_view_
;
646 target_view_
= scroller_
;
648 source_height_
= source_view_
->GetHeightForWidth(width());
649 target_height_
= target_view_
->GetHeightForWidth(width());
651 gfx::MultiAnimation::Parts parts
;
652 // First part: slide resize animation.
653 parts
.push_back(gfx::MultiAnimation::Part(
654 (source_height_
== target_height_
) ? 0 : kDefaultAnimationDurationMs
,
655 gfx::Tween::EASE_OUT
));
656 // Second part: fade-out the source_view.
657 if (source_view_
->layer()) {
658 parts
.push_back(gfx::MultiAnimation::Part(
659 kDefaultAnimationDurationMs
, gfx::Tween::LINEAR
));
661 parts
.push_back(gfx::MultiAnimation::Part());
663 // Third part: fade-in the target_view.
664 if (target_view_
->layer()) {
665 parts
.push_back(gfx::MultiAnimation::Part(
666 kDefaultAnimationDurationMs
, gfx::Tween::LINEAR
));
667 target_view_
->layer()->SetOpacity(0);
668 target_view_
->SetVisible(true);
670 parts
.push_back(gfx::MultiAnimation::Part());
672 settings_transition_animation_
.reset(new gfx::MultiAnimation(
673 parts
, base::TimeDelta::FromMicroseconds(1000000 / kDefaultFrameRateHz
)));
674 settings_transition_animation_
->set_delegate(this);
675 settings_transition_animation_
->set_continuous(false);
676 settings_transition_animation_
->Start();
678 button_bar_
->SetBackArrowVisible(visible
);
681 void MessageCenterView::ClearAllNotifications() {
685 scroller_
->SetEnabled(false);
686 button_bar_
->SetAllButtonsEnabled(false);
687 message_list_view_
->ClearAllNotifications(scroller_
->GetVisibleRect());
690 void MessageCenterView::OnAllNotificationsCleared() {
691 scroller_
->SetEnabled(true);
692 button_bar_
->SetAllButtonsEnabled(true);
693 button_bar_
->SetCloseAllButtonEnabled(false);
694 message_center_
->RemoveAllVisibleNotifications(true); // Action by user.
697 size_t MessageCenterView::NumMessageViewsForTest() const {
698 return message_list_view_
->child_count();
701 void MessageCenterView::OnSettingsChanged() {
702 scroller_
->InvalidateLayout();
703 PreferredSizeChanged();
707 void MessageCenterView::SetIsClosing(bool is_closing
) {
708 is_closing_
= is_closing
;
710 message_center_
->RemoveObserver(this);
712 message_center_
->AddObserver(this);
715 void MessageCenterView::Layout() {
719 int button_height
= button_bar_
->GetHeightForWidth(width()) +
720 button_bar_
->GetInsets().height();
721 // Skip unnecessary re-layout of contents during the resize animation.
722 bool animating
= settings_transition_animation_
&&
723 settings_transition_animation_
->is_animating();
724 if (animating
&& settings_transition_animation_
->current_part_index() == 0) {
726 button_bar_
->SetBounds(
727 0, height() - button_height
, width(), button_height
);
732 scroller_
->SetBounds(0,
733 top_down_
? button_height
: 0,
735 height() - button_height
);
736 settings_view_
->SetBounds(0,
737 top_down_
? button_height
: 0,
739 height() - button_height
);
741 bool is_scrollable
= false;
742 if (scroller_
->visible())
743 is_scrollable
= scroller_
->height() < message_list_view_
->height();
745 is_scrollable
= settings_view_
->IsScrollable();
749 // Draw separator line on the top of the button bar if it is on the bottom
750 // or draw it at the bottom if the bar is on the top.
751 button_bar_
->SetBorder(views::Border::CreateSolidSidedBorder(
752 top_down_
? 0 : 1, 0, top_down_
? 1 : 0, 0, kFooterDelimiterColor
));
754 button_bar_
->SetBorder(views::Border::CreateEmptyBorder(
755 top_down_
? 0 : 1, 0, top_down_
? 1 : 0, 0));
757 button_bar_
->SchedulePaint();
759 button_bar_
->SetBounds(0,
760 top_down_
? 0 : height() - button_height
,
764 GetWidget()->GetRootView()->SchedulePaint();
767 gfx::Size
MessageCenterView::GetPreferredSize() const {
768 if (settings_transition_animation_
&&
769 settings_transition_animation_
->is_animating()) {
770 int content_width
= std::max(source_view_
->GetPreferredSize().width(),
771 target_view_
->GetPreferredSize().width());
772 int width
= std::max(content_width
,
773 button_bar_
->GetPreferredSize().width());
774 return gfx::Size(width
, GetHeightForWidth(width
));
778 for (int i
= 0; i
< child_count(); ++i
) {
779 const views::View
* child
= child_at(0);
780 if (child
->visible())
781 width
= std::max(width
, child
->GetPreferredSize().width());
783 return gfx::Size(width
, GetHeightForWidth(width
));
786 int MessageCenterView::GetHeightForWidth(int width
) const {
787 if (settings_transition_animation_
&&
788 settings_transition_animation_
->is_animating()) {
789 int content_height
= target_height_
;
790 if (settings_transition_animation_
->current_part_index() == 0) {
791 content_height
= settings_transition_animation_
->CurrentValueBetween(
792 source_height_
, target_height_
);
794 return button_bar_
->GetHeightForWidth(width
) + content_height
;
797 int content_height
= 0;
798 if (scroller_
->visible())
799 content_height
+= scroller_
->GetHeightForWidth(width
);
801 content_height
+= settings_view_
->GetHeightForWidth(width
);
802 return button_bar_
->GetHeightForWidth(width
) +
803 button_bar_
->GetInsets().height() + content_height
;
806 bool MessageCenterView::OnMouseWheel(const ui::MouseWheelEvent
& event
) {
807 // Do not rely on the default scroll event handler of ScrollView because
808 // the scroll happens only when the focus is on the ScrollView. The
809 // notification center will allow the scrolling even when the focus is on
811 if (scroller_
->bounds().Contains(event
.location()))
812 return scroller_
->OnMouseWheel(event
);
813 return views::View::OnMouseWheel(event
);
816 void MessageCenterView::OnMouseExited(const ui::MouseEvent
& event
) {
820 message_list_view_
->ResetRepositionSession();
821 NotificationsChanged();
824 void MessageCenterView::OnNotificationAdded(const std::string
& id
) {
826 const NotificationList::Notifications
& notifications
=
827 message_center_
->GetVisibleNotifications();
828 for (NotificationList::Notifications::const_iterator iter
=
829 notifications
.begin(); iter
!= notifications
.end();
831 if ((*iter
)->id() == id
) {
832 AddNotificationAt(*(*iter
), index
);
835 if (notification_views_
.size() >= kMaxVisibleMessageCenterNotifications
)
838 NotificationsChanged();
841 void MessageCenterView::OnNotificationRemoved(const std::string
& id
,
843 NotificationViewsMap::iterator view_iter
= notification_views_
.find(id
);
844 if (view_iter
== notification_views_
.end())
846 NotificationView
* view
= view_iter
->second
;
847 int index
= message_list_view_
->GetIndexOf(view
);
850 message_list_view_
->SetRepositionTarget(view
->bounds());
851 // Moves the keyboard focus to the next notification if the removed
852 // notification is focused so that the user can dismiss notifications
853 // without re-focusing by tab key.
854 if (view
->IsCloseButtonFocused() ||
855 view
== GetFocusManager()->GetFocusedView()) {
856 views::View
* next_focused_view
= NULL
;
857 if (message_list_view_
->child_count() > index
+ 1)
858 next_focused_view
= message_list_view_
->child_at(index
+ 1);
860 next_focused_view
= message_list_view_
->child_at(index
- 1);
862 if (next_focused_view
) {
863 if (view
->IsCloseButtonFocused())
864 // Safe cast since all views in MessageListView are MessageViews.
865 static_cast<MessageView
*>(
866 next_focused_view
)->RequestFocusOnCloseButton();
868 next_focused_view
->RequestFocus();
872 message_list_view_
->RemoveNotification(view
);
873 notification_views_
.erase(view_iter
);
874 NotificationsChanged();
877 void MessageCenterView::OnNotificationUpdated(const std::string
& id
) {
878 NotificationViewsMap::const_iterator view_iter
= notification_views_
.find(id
);
879 if (view_iter
== notification_views_
.end())
881 NotificationView
* view
= view_iter
->second
;
882 // TODO(dimich): add MessageCenter::GetVisibleNotificationById(id)
883 const NotificationList::Notifications
& notifications
=
884 message_center_
->GetVisibleNotifications();
885 for (NotificationList::Notifications::const_iterator iter
=
886 notifications
.begin(); iter
!= notifications
.end(); ++iter
) {
887 if ((*iter
)->id() == id
) {
888 int old_width
= view
->width();
889 int old_height
= view
->GetHeightForWidth(old_width
);
890 message_list_view_
->UpdateNotification(view
, **iter
);
891 if (view
->GetHeightForWidth(old_width
) != old_height
)
892 NotificationsChanged();
898 void MessageCenterView::ClickOnNotification(
899 const std::string
& notification_id
) {
900 message_center_
->ClickOnNotification(notification_id
);
903 void MessageCenterView::RemoveNotification(const std::string
& notification_id
,
905 message_center_
->RemoveNotification(notification_id
, by_user
);
908 scoped_ptr
<ui::MenuModel
> MessageCenterView::CreateMenuModel(
909 const NotifierId
& notifier_id
,
910 const base::string16
& display_source
) {
911 return tray_
->CreateNotificationMenuModel(notifier_id
, display_source
);
914 bool MessageCenterView::HasClickedListener(const std::string
& notification_id
) {
915 return message_center_
->HasClickedListener(notification_id
);
918 void MessageCenterView::ClickOnNotificationButton(
919 const std::string
& notification_id
,
921 message_center_
->ClickOnNotificationButton(notification_id
, button_index
);
924 void MessageCenterView::AnimationEnded(const gfx::Animation
* animation
) {
925 DCHECK_EQ(animation
, settings_transition_animation_
.get());
927 Visibility visibility
= target_view_
== settings_view_
928 ? VISIBILITY_SETTINGS
929 : VISIBILITY_MESSAGE_CENTER
;
930 message_center_
->SetVisibility(visibility
);
932 source_view_
->SetVisible(false);
933 target_view_
->SetVisible(true);
934 if (source_view_
->layer())
935 source_view_
->layer()->SetOpacity(1.0);
936 if (target_view_
->layer())
937 target_view_
->layer()->SetOpacity(1.0);
938 settings_transition_animation_
.reset();
939 PreferredSizeChanged();
943 void MessageCenterView::AnimationProgressed(const gfx::Animation
* animation
) {
944 DCHECK_EQ(animation
, settings_transition_animation_
.get());
945 PreferredSizeChanged();
946 if (settings_transition_animation_
->current_part_index() == 1 &&
947 source_view_
->layer()) {
948 source_view_
->layer()->SetOpacity(
949 1.0 - settings_transition_animation_
->GetCurrentValue());
951 } else if (settings_transition_animation_
->current_part_index() == 2 &&
952 target_view_
->layer()) {
953 target_view_
->layer()->SetOpacity(
954 settings_transition_animation_
->GetCurrentValue());
959 void MessageCenterView::AnimationCanceled(const gfx::Animation
* animation
) {
960 DCHECK_EQ(animation
, settings_transition_animation_
.get());
961 AnimationEnded(animation
);
964 void MessageCenterView::AddNotificationAt(const Notification
& notification
,
966 NotificationView
* view
=
967 NotificationView::Create(this, notification
, false); // Not top-level.
968 view
->set_context_menu_controller(context_menu_controller_
.get());
969 notification_views_
[notification
.id()] = view
;
970 view
->set_scroller(scroller_
);
971 message_list_view_
->AddNotificationAt(view
, index
);
974 void MessageCenterView::NotificationsChanged() {
975 bool no_message_views
= notification_views_
.empty();
977 // When the child view is removed from the hierarchy, its focus is cleared.
978 // In this case we want to save which view has focus so that the user can
979 // continue to interact with notifications in the order they were expecting.
980 views::FocusManager
* focus_manager
= scroller_
->GetFocusManager();
981 View
* focused_view
= NULL
;
982 // |focus_manager| can be NULL in tests.
984 focused_view
= focus_manager
->GetFocusedView();
986 // All the children of this view are owned by |this|.
987 scroller_
->contents()->RemoveAllChildViews(/*delete_children=*/false);
988 scroller_
->contents()->AddChildView(
989 no_message_views
? empty_list_view_
.get() : message_list_view_
.get());
991 button_bar_
->SetCloseAllButtonEnabled(!no_message_views
);
992 scroller_
->SetFocusable(!no_message_views
);
994 if (focus_manager
&& focused_view
)
995 focus_manager
->SetFocusedView(focused_view
);
997 scroller_
->InvalidateLayout();
998 PreferredSizeChanged();
1002 void MessageCenterView::SetNotificationViewForTest(MessageView
* view
) {
1003 message_list_view_
->AddNotificationAt(view
, 0);
1006 } // namespace message_center