Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / message_center / views / message_center_view.cc
blob486feebd7c4a69b51f0047bc00b4f2d3126f3e27
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"
7 #include <list>
8 #include <map>
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 {
42 namespace {
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);
47 #endif
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);
58 } // namespace
60 class NoNotificationMessageView : public views::View {
61 public:
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;
70 private:
71 views::Label* label_;
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);
85 #endif
86 AddChildView(label_);
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,
110 int max_height,
111 bool initially_settings_visible,
112 bool top_down,
113 const base::string16& title)
114 : message_center_(message_center),
115 tray_(tray),
116 scroller_(NULL),
117 settings_view_(NULL),
118 button_bar_(NULL),
119 top_down_(top_down),
120 settings_visible_(initially_settings_visible),
121 source_view_(NULL),
122 source_height_(0),
123 target_view_(NULL),
124 target_height_(0),
125 is_closing_(false),
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,
135 message_center,
136 notifier_settings_provider,
137 initially_settings_visible,
138 title);
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
162 // out.
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);
172 else
173 settings_view_->SetVisible(false);
175 AddChildView(scroller_);
176 AddChildView(settings_view_);
177 AddChildView(button_bar_);
180 MessageCenterView::~MessageCenterView() {
181 if (!is_closing_)
182 message_center_->RemoveObserver(this);
185 void MessageCenterView::SetNotifications(
186 const NotificationList::Notifications& notifications) {
187 if (is_closing_)
188 return;
190 notification_views_.clear();
192 int index = 0;
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)
200 break;
203 NotificationsChanged();
204 scroller_->RequestFocus();
207 void MessageCenterView::SetSettingsVisible(bool visible) {
208 if (is_closing_)
209 return;
211 if (visible == settings_visible_)
212 return;
214 settings_visible_ = visible;
216 if (visible) {
217 source_view_ = scroller_;
218 target_view_ = settings_view_;
219 } else {
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));
235 } else {
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);
244 } else {
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() {
257 if (is_closing_)
258 return;
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();
279 Layout();
282 void MessageCenterView::SetIsClosing(bool is_closing) {
283 is_closing_ = is_closing;
284 if (is_closing)
285 message_center_->RemoveObserver(this);
286 else
287 message_center_->AddObserver(this);
290 void MessageCenterView::Layout() {
291 if (is_closing_)
292 return;
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) {
300 if (!top_down_) {
301 button_bar_->SetBounds(
302 0, height() - button_height, width(), button_height);
304 return;
307 scroller_->SetBounds(0,
308 top_down_ ? button_height : 0,
309 width(),
310 height() - button_height);
311 settings_view_->SetBounds(0,
312 top_down_ ? button_height : 0,
313 width(),
314 height() - button_height);
316 bool is_scrollable = false;
317 if (scroller_->visible())
318 is_scrollable = scroller_->height() < message_list_view_->height();
319 else
320 is_scrollable = settings_view_->IsScrollable();
322 if (!animating) {
323 if (is_scrollable) {
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));
328 } else {
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,
336 width(),
337 button_height);
338 if (GetWidget())
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));
352 int width = 0;
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);
375 else
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
385 // the buttons.
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) {
392 if (is_closing_)
393 return;
395 message_list_view_->ResetRepositionSession();
396 NotificationsChanged();
399 void MessageCenterView::OnNotificationAdded(const std::string& id) {
400 int index = 0;
401 const NotificationList::Notifications& notifications =
402 message_center_->GetVisibleNotifications();
403 for (NotificationList::Notifications::const_iterator iter =
404 notifications.begin(); iter != notifications.end();
405 ++iter, ++index) {
406 if ((*iter)->id() == id) {
407 AddNotificationAt(*(*iter), index);
408 break;
410 if (notification_views_.size() >= kMaxVisibleMessageCenterNotifications)
411 break;
413 NotificationsChanged();
416 void MessageCenterView::OnNotificationRemoved(const std::string& id,
417 bool by_user) {
418 NotificationViewsMap::iterator view_iter = notification_views_.find(id);
419 if (view_iter == notification_views_.end())
420 return;
421 NotificationView* view = view_iter->second;
422 int index = message_list_view_->GetIndexOf(view);
423 DCHECK_LE(0, index);
424 if (by_user) {
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);
434 else if (index > 0)
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();
442 } else {
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())
456 return;
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();
469 break;
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,
480 bool by_user) {
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,
496 int button_index) {
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();
516 Layout();
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());
526 SchedulePaint();
527 } else if (settings_transition_animation_->current_part_index() == 2 &&
528 target_view_->layer()) {
529 target_view_->layer()->SetOpacity(
530 settings_transition_animation_->GetCurrentValue());
531 SchedulePaint();
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,
541 int index) {
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.
559 if (focus_manager)
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();
575 Layout();
578 void MessageCenterView::SetNotificationViewForTest(MessageView* view) {
579 message_list_view_->AddNotificationAt(view, 0);
582 } // namespace message_center