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 "ui/base/l10n/l10n_util.h"
14 #include "ui/gfx/animation/multi_animation.h"
15 #include "ui/gfx/canvas.h"
16 #include "ui/gfx/geometry/insets.h"
17 #include "ui/gfx/geometry/rect.h"
18 #include "ui/gfx/geometry/size.h"
19 #include "ui/message_center/message_center.h"
20 #include "ui/message_center/message_center_style.h"
21 #include "ui/message_center/message_center_tray.h"
22 #include "ui/message_center/message_center_types.h"
23 #include "ui/message_center/views/message_center_button_bar.h"
24 #include "ui/message_center/views/message_list_view.h"
25 #include "ui/message_center/views/message_view.h"
26 #include "ui/message_center/views/message_view_context_menu_controller.h"
27 #include "ui/message_center/views/notification_view.h"
28 #include "ui/message_center/views/notifier_settings_view.h"
29 #include "ui/resources/grit/ui_resources.h"
30 #include "ui/strings/grit/ui_strings.h"
31 #include "ui/views/background.h"
32 #include "ui/views/border.h"
33 #include "ui/views/controls/button/button.h"
34 #include "ui/views/controls/label.h"
35 #include "ui/views/controls/scroll_view.h"
36 #include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
37 #include "ui/views/layout/fill_layout.h"
38 #include "ui/views/widget/widget.h"
40 namespace message_center
{
44 const SkColor kNoNotificationsTextColor
= SkColorSetRGB(0xb4, 0xb4, 0xb4);
45 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
46 const SkColor kTransparentColor
= SkColorSetARGB(0, 0, 0, 0);
49 const int kDefaultAnimationDurationMs
= 120;
50 const int kDefaultFrameRateHz
= 60;
52 void SetViewHierarchyEnabled(views::View
* view
, bool enabled
) {
53 for (int i
= 0; i
< view
->child_count(); i
++)
54 SetViewHierarchyEnabled(view
->child_at(i
), enabled
);
55 view
->SetEnabled(enabled
);
60 class NoNotificationMessageView
: public views::View
{
62 NoNotificationMessageView();
63 ~NoNotificationMessageView() override
;
65 // Overridden from views::View.
66 gfx::Size
GetPreferredSize() const override
;
67 int GetHeightForWidth(int width
) const override
;
68 void Layout() override
;
73 DISALLOW_COPY_AND_ASSIGN(NoNotificationMessageView
);
76 NoNotificationMessageView::NoNotificationMessageView() {
77 label_
= new views::Label(l10n_util::GetStringUTF16(
78 IDS_MESSAGE_CENTER_NO_MESSAGES
));
79 label_
->SetAutoColorReadabilityEnabled(false);
80 label_
->SetEnabledColor(kNoNotificationsTextColor
);
81 // Set transparent background to ensure that subpixel rendering
82 // is disabled. See crbug.com/169056
83 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
84 label_
->SetBackgroundColor(kTransparentColor
);
89 NoNotificationMessageView::~NoNotificationMessageView() {
92 gfx::Size
NoNotificationMessageView::GetPreferredSize() const {
93 return gfx::Size(kMinScrollViewHeight
, label_
->GetPreferredSize().width());
96 int NoNotificationMessageView::GetHeightForWidth(int width
) const {
97 return kMinScrollViewHeight
;
100 void NoNotificationMessageView::Layout() {
101 int text_height
= label_
->GetHeightForWidth(width());
102 int margin
= (height() - text_height
) / 2;
103 label_
->SetBounds(0, margin
, width(), text_height
);
106 // MessageCenterView ///////////////////////////////////////////////////////////
108 MessageCenterView::MessageCenterView(MessageCenter
* message_center
,
109 MessageCenterTray
* tray
,
111 bool initially_settings_visible
,
113 const base::string16
& title
)
114 : message_center_(message_center
),
117 settings_view_(NULL
),
120 settings_visible_(initially_settings_visible
),
126 context_menu_controller_(new MessageViewContextMenuController(this)) {
127 message_center_
->AddObserver(this);
128 set_notify_enter_exit_on_child(true);
129 set_background(views::Background::CreateSolidBackground(
130 kMessageCenterBackgroundColor
));
132 NotifierSettingsProvider
* notifier_settings_provider
=
133 message_center_
->GetNotifierSettingsProvider();
134 button_bar_
= new MessageCenterButtonBar(this,
136 notifier_settings_provider
,
137 initially_settings_visible
,
140 const int button_height
= button_bar_
->GetPreferredSize().height();
142 scroller_
= new views::ScrollView();
143 scroller_
->ClipHeightTo(kMinScrollViewHeight
, max_height
- button_height
);
144 scroller_
->SetVerticalScrollBar(new views::OverlayScrollBar(false));
145 scroller_
->set_background(
146 views::Background::CreateSolidBackground(kMessageCenterBackgroundColor
));
148 scroller_
->SetPaintToLayer(true);
149 scroller_
->SetFillsBoundsOpaquely(false);
150 scroller_
->layer()->SetMasksToBounds(true);
152 empty_list_view_
.reset(new NoNotificationMessageView
);
153 empty_list_view_
->set_owned_by_client();
154 message_list_view_
.reset(new MessageListView(this, top_down
));
155 message_list_view_
->set_owned_by_client();
157 // We want to swap the contents of the scroll view between the empty list
158 // view and the message list view, without constructing them afresh each
159 // time. So, since the scroll view deletes old contents each time you
160 // set the contents (regardless of the |owned_by_client_| setting) we need
161 // an intermediate view for the contents whose children we can swap in and
163 views::View
* scroller_contents
= new views::View();
164 scroller_contents
->SetLayoutManager(new views::FillLayout());
165 scroller_contents
->AddChildView(empty_list_view_
.get());
166 scroller_
->SetContents(scroller_contents
);
168 settings_view_
= new NotifierSettingsView(notifier_settings_provider
);
170 if (initially_settings_visible
)
171 scroller_
->SetVisible(false);
173 settings_view_
->SetVisible(false);
175 AddChildView(scroller_
);
176 AddChildView(settings_view_
);
177 AddChildView(button_bar_
);
180 MessageCenterView::~MessageCenterView() {
182 message_center_
->RemoveObserver(this);
185 void MessageCenterView::SetNotifications(
186 const NotificationList::Notifications
& notifications
) {
190 notification_views_
.clear();
193 for (NotificationList::Notifications::const_iterator iter
=
194 notifications
.begin(); iter
!= notifications
.end(); ++iter
) {
195 AddNotificationAt(*(*iter
), index
++);
197 message_center_
->DisplayedNotification(
198 (*iter
)->id(), message_center::DISPLAY_SOURCE_MESSAGE_CENTER
);
199 if (notification_views_
.size() >= kMaxVisibleMessageCenterNotifications
)
203 NotificationsChanged();
204 scroller_
->RequestFocus();
207 void MessageCenterView::SetSettingsVisible(bool visible
) {
211 if (visible
== settings_visible_
)
214 settings_visible_
= visible
;
217 source_view_
= scroller_
;
218 target_view_
= settings_view_
;
220 source_view_
= settings_view_
;
221 target_view_
= scroller_
;
223 source_height_
= source_view_
->GetHeightForWidth(width());
224 target_height_
= target_view_
->GetHeightForWidth(width());
226 gfx::MultiAnimation::Parts parts
;
227 // First part: slide resize animation.
228 parts
.push_back(gfx::MultiAnimation::Part(
229 (source_height_
== target_height_
) ? 0 : kDefaultAnimationDurationMs
,
230 gfx::Tween::EASE_OUT
));
231 // Second part: fade-out the source_view.
232 if (source_view_
->layer()) {
233 parts
.push_back(gfx::MultiAnimation::Part(
234 kDefaultAnimationDurationMs
, gfx::Tween::LINEAR
));
236 parts
.push_back(gfx::MultiAnimation::Part());
238 // Third part: fade-in the target_view.
239 if (target_view_
->layer()) {
240 parts
.push_back(gfx::MultiAnimation::Part(
241 kDefaultAnimationDurationMs
, gfx::Tween::LINEAR
));
242 target_view_
->layer()->SetOpacity(0);
243 target_view_
->SetVisible(true);
245 parts
.push_back(gfx::MultiAnimation::Part());
247 settings_transition_animation_
.reset(new gfx::MultiAnimation(
248 parts
, base::TimeDelta::FromMicroseconds(1000000 / kDefaultFrameRateHz
)));
249 settings_transition_animation_
->set_delegate(this);
250 settings_transition_animation_
->set_continuous(false);
251 settings_transition_animation_
->Start();
253 button_bar_
->SetBackArrowVisible(visible
);
256 void MessageCenterView::ClearAllNotifications() {
260 SetViewHierarchyEnabled(scroller_
, false);
261 button_bar_
->SetAllButtonsEnabled(false);
262 message_list_view_
->ClearAllNotifications(scroller_
->GetVisibleRect());
265 void MessageCenterView::OnAllNotificationsCleared() {
266 SetViewHierarchyEnabled(scroller_
, true);
267 button_bar_
->SetAllButtonsEnabled(true);
268 button_bar_
->SetCloseAllButtonEnabled(false);
269 message_center_
->RemoveAllVisibleNotifications(true); // Action by user.
272 size_t MessageCenterView::NumMessageViewsForTest() const {
273 return message_list_view_
->child_count();
276 void MessageCenterView::OnSettingsChanged() {
277 scroller_
->InvalidateLayout();
278 PreferredSizeChanged();
282 void MessageCenterView::SetIsClosing(bool is_closing
) {
283 is_closing_
= is_closing
;
285 message_center_
->RemoveObserver(this);
287 message_center_
->AddObserver(this);
290 void MessageCenterView::Layout() {
294 int button_height
= button_bar_
->GetHeightForWidth(width()) +
295 button_bar_
->GetInsets().height();
296 // Skip unnecessary re-layout of contents during the resize animation.
297 bool animating
= settings_transition_animation_
&&
298 settings_transition_animation_
->is_animating();
299 if (animating
&& settings_transition_animation_
->current_part_index() == 0) {
301 button_bar_
->SetBounds(
302 0, height() - button_height
, width(), button_height
);
307 scroller_
->SetBounds(0,
308 top_down_
? button_height
: 0,
310 height() - button_height
);
311 settings_view_
->SetBounds(0,
312 top_down_
? button_height
: 0,
314 height() - button_height
);
316 bool is_scrollable
= false;
317 if (scroller_
->visible())
318 is_scrollable
= scroller_
->height() < message_list_view_
->height();
320 is_scrollable
= settings_view_
->IsScrollable();
324 // Draw separator line on the top of the button bar if it is on the bottom
325 // or draw it at the bottom if the bar is on the top.
326 button_bar_
->SetBorder(views::Border::CreateSolidSidedBorder(
327 top_down_
? 0 : 1, 0, top_down_
? 1 : 0, 0, kFooterDelimiterColor
));
329 button_bar_
->SetBorder(views::Border::CreateEmptyBorder(
330 top_down_
? 0 : 1, 0, top_down_
? 1 : 0, 0));
332 button_bar_
->SchedulePaint();
334 button_bar_
->SetBounds(0,
335 top_down_
? 0 : height() - button_height
,
339 GetWidget()->GetRootView()->SchedulePaint();
342 gfx::Size
MessageCenterView::GetPreferredSize() const {
343 if (settings_transition_animation_
&&
344 settings_transition_animation_
->is_animating()) {
345 int content_width
= std::max(source_view_
->GetPreferredSize().width(),
346 target_view_
->GetPreferredSize().width());
347 int width
= std::max(content_width
,
348 button_bar_
->GetPreferredSize().width());
349 return gfx::Size(width
, GetHeightForWidth(width
));
353 for (int i
= 0; i
< child_count(); ++i
) {
354 const views::View
* child
= child_at(0);
355 if (child
->visible())
356 width
= std::max(width
, child
->GetPreferredSize().width());
358 return gfx::Size(width
, GetHeightForWidth(width
));
361 int MessageCenterView::GetHeightForWidth(int width
) const {
362 if (settings_transition_animation_
&&
363 settings_transition_animation_
->is_animating()) {
364 int content_height
= target_height_
;
365 if (settings_transition_animation_
->current_part_index() == 0) {
366 content_height
= settings_transition_animation_
->CurrentValueBetween(
367 source_height_
, target_height_
);
369 return button_bar_
->GetHeightForWidth(width
) + content_height
;
372 int content_height
= 0;
373 if (scroller_
->visible())
374 content_height
+= scroller_
->GetHeightForWidth(width
);
376 content_height
+= settings_view_
->GetHeightForWidth(width
);
377 return button_bar_
->GetHeightForWidth(width
) +
378 button_bar_
->GetInsets().height() + content_height
;
381 bool MessageCenterView::OnMouseWheel(const ui::MouseWheelEvent
& event
) {
382 // Do not rely on the default scroll event handler of ScrollView because
383 // the scroll happens only when the focus is on the ScrollView. The
384 // notification center will allow the scrolling even when the focus is on
386 if (scroller_
->bounds().Contains(event
.location()))
387 return scroller_
->OnMouseWheel(event
);
388 return views::View::OnMouseWheel(event
);
391 void MessageCenterView::OnMouseExited(const ui::MouseEvent
& event
) {
395 message_list_view_
->ResetRepositionSession();
396 NotificationsChanged();
399 void MessageCenterView::OnNotificationAdded(const std::string
& id
) {
401 const NotificationList::Notifications
& notifications
=
402 message_center_
->GetVisibleNotifications();
403 for (NotificationList::Notifications::const_iterator iter
=
404 notifications
.begin(); iter
!= notifications
.end();
406 if ((*iter
)->id() == id
) {
407 AddNotificationAt(*(*iter
), index
);
410 if (notification_views_
.size() >= kMaxVisibleMessageCenterNotifications
)
413 NotificationsChanged();
416 void MessageCenterView::OnNotificationRemoved(const std::string
& id
,
418 NotificationViewsMap::iterator view_iter
= notification_views_
.find(id
);
419 if (view_iter
== notification_views_
.end())
421 NotificationView
* view
= view_iter
->second
;
422 int index
= message_list_view_
->GetIndexOf(view
);
425 message_list_view_
->SetRepositionTarget(view
->bounds());
426 // Moves the keyboard focus to the next notification if the removed
427 // notification is focused so that the user can dismiss notifications
428 // without re-focusing by tab key.
429 if (view
->IsCloseButtonFocused() ||
430 view
== GetFocusManager()->GetFocusedView()) {
431 views::View
* next_focused_view
= NULL
;
432 if (message_list_view_
->child_count() > index
+ 1)
433 next_focused_view
= message_list_view_
->child_at(index
+ 1);
435 next_focused_view
= message_list_view_
->child_at(index
- 1);
437 if (next_focused_view
) {
438 if (view
->IsCloseButtonFocused()) {
439 // Safe cast since all views in MessageListView are MessageViews.
440 static_cast<MessageView
*>(
441 next_focused_view
)->RequestFocusOnCloseButton();
443 next_focused_view
->RequestFocus();
448 message_list_view_
->RemoveNotification(view
);
449 notification_views_
.erase(view_iter
);
450 NotificationsChanged();
453 void MessageCenterView::OnNotificationUpdated(const std::string
& id
) {
454 NotificationViewsMap::const_iterator view_iter
= notification_views_
.find(id
);
455 if (view_iter
== notification_views_
.end())
457 NotificationView
* view
= view_iter
->second
;
458 // TODO(dimich): add MessageCenter::GetVisibleNotificationById(id)
459 const NotificationList::Notifications
& notifications
=
460 message_center_
->GetVisibleNotifications();
461 for (NotificationList::Notifications::const_iterator iter
=
462 notifications
.begin(); iter
!= notifications
.end(); ++iter
) {
463 if ((*iter
)->id() == id
) {
464 int old_width
= view
->width();
465 int old_height
= view
->GetHeightForWidth(old_width
);
466 message_list_view_
->UpdateNotification(view
, **iter
);
467 if (view
->GetHeightForWidth(old_width
) != old_height
)
468 NotificationsChanged();
474 void MessageCenterView::ClickOnNotification(
475 const std::string
& notification_id
) {
476 message_center_
->ClickOnNotification(notification_id
);
479 void MessageCenterView::RemoveNotification(const std::string
& notification_id
,
481 message_center_
->RemoveNotification(notification_id
, by_user
);
484 scoped_ptr
<ui::MenuModel
> MessageCenterView::CreateMenuModel(
485 const NotifierId
& notifier_id
,
486 const base::string16
& display_source
) {
487 return tray_
->CreateNotificationMenuModel(notifier_id
, display_source
);
490 bool MessageCenterView::HasClickedListener(const std::string
& notification_id
) {
491 return message_center_
->HasClickedListener(notification_id
);
494 void MessageCenterView::ClickOnNotificationButton(
495 const std::string
& notification_id
,
497 message_center_
->ClickOnNotificationButton(notification_id
, button_index
);
500 void MessageCenterView::AnimationEnded(const gfx::Animation
* animation
) {
501 DCHECK_EQ(animation
, settings_transition_animation_
.get());
503 Visibility visibility
= target_view_
== settings_view_
504 ? VISIBILITY_SETTINGS
505 : VISIBILITY_MESSAGE_CENTER
;
506 message_center_
->SetVisibility(visibility
);
508 source_view_
->SetVisible(false);
509 target_view_
->SetVisible(true);
510 if (source_view_
->layer())
511 source_view_
->layer()->SetOpacity(1.0);
512 if (target_view_
->layer())
513 target_view_
->layer()->SetOpacity(1.0);
514 settings_transition_animation_
.reset();
515 PreferredSizeChanged();
519 void MessageCenterView::AnimationProgressed(const gfx::Animation
* animation
) {
520 DCHECK_EQ(animation
, settings_transition_animation_
.get());
521 PreferredSizeChanged();
522 if (settings_transition_animation_
->current_part_index() == 1 &&
523 source_view_
->layer()) {
524 source_view_
->layer()->SetOpacity(
525 1.0 - settings_transition_animation_
->GetCurrentValue());
527 } else if (settings_transition_animation_
->current_part_index() == 2 &&
528 target_view_
->layer()) {
529 target_view_
->layer()->SetOpacity(
530 settings_transition_animation_
->GetCurrentValue());
535 void MessageCenterView::AnimationCanceled(const gfx::Animation
* animation
) {
536 DCHECK_EQ(animation
, settings_transition_animation_
.get());
537 AnimationEnded(animation
);
540 void MessageCenterView::AddNotificationAt(const Notification
& notification
,
542 NotificationView
* view
=
543 NotificationView::Create(this, notification
, false); // Not top-level.
544 view
->set_context_menu_controller(context_menu_controller_
.get());
545 notification_views_
[notification
.id()] = view
;
546 view
->set_scroller(scroller_
);
547 message_list_view_
->AddNotificationAt(view
, index
);
550 void MessageCenterView::NotificationsChanged() {
551 bool no_message_views
= notification_views_
.empty();
553 // When the child view is removed from the hierarchy, its focus is cleared.
554 // In this case we want to save which view has focus so that the user can
555 // continue to interact with notifications in the order they were expecting.
556 views::FocusManager
* focus_manager
= scroller_
->GetFocusManager();
557 View
* focused_view
= NULL
;
558 // |focus_manager| can be NULL in tests.
560 focused_view
= focus_manager
->GetFocusedView();
562 // All the children of this view are owned by |this|.
563 scroller_
->contents()->RemoveAllChildViews(/*delete_children=*/false);
564 scroller_
->contents()->AddChildView(
565 no_message_views
? empty_list_view_
.get() : message_list_view_
.get());
567 button_bar_
->SetCloseAllButtonEnabled(!no_message_views
);
568 scroller_
->SetFocusable(!no_message_views
);
570 if (focus_manager
&& focused_view
)
571 focus_manager
->SetFocusedView(focused_view
);
573 scroller_
->InvalidateLayout();
574 PreferredSizeChanged();
578 void MessageCenterView::SetNotificationViewForTest(MessageView
* view
) {
579 message_list_view_
->AddNotificationAt(view
, 0);
582 } // namespace message_center