Blink roll 25b6bd3a7a131ffe68d809546ad1a20707915cdc:3a503f41ae42e5b79cfcd2ff10e65afde...
[chromium-blink-merge.git] / ui / message_center / views / message_center_view.cc
blobb9b6f169bea411b0aca7ae2bec12c372c34ce2c8
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/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 {
47 namespace {
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);
52 #endif
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);
64 } // namespace
66 class NoNotificationMessageView : public views::View {
67 public:
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;
76 private:
77 views::Label* label_;
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);
91 #endif
92 AddChildView(label_);
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
114 // repositioning.
115 class MessageListView : public views::View,
116 public views::BoundsAnimatorObserver {
117 public:
118 explicit MessageListView(MessageCenterView* message_center_view,
119 bool top_down);
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);
129 protected:
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;
142 private:
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.
165 int reposition_top_;
166 int fixed_height_;
167 bool has_deferred_task_;
168 bool clear_all_started_;
169 bool top_down_;
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,
181 bool top_down)
182 : message_center_view_(message_center_view),
183 reposition_top_(-1),
184 fixed_height_(0),
185 has_deferred_task_(false),
186 clear_all_started_(false),
187 top_down_(top_down),
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() {
209 if (animator_.get())
210 animator_->RemoveObserver(this);
213 void MessageListView::Layout() {
214 if (animator_.get())
215 return;
217 gfx::Rect child_area = GetContentsBounds();
218 int top = child_area.y();
219 int between_items =
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())
225 continue;
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.
237 int real_index = 0;
238 while (real_index < child_count()) {
239 if (IsValidChild(child_at(real_index))) {
240 --index;
241 if (index < 0)
242 break;
244 ++real_index;
247 AddChildViewAt(view, real_index);
248 if (GetContentsBounds().IsEmpty())
249 return;
251 adding_views_.insert(view);
252 DoUpdateIfPossible();
255 void MessageListView::RemoveNotification(MessageView* view) {
256 DCHECK_EQ(view->parent(), this);
257 if (GetContentsBounds().IsEmpty()) {
258 delete view;
259 } else {
260 if (view->layer()) {
261 deleting_views_.insert(view);
262 } else {
263 if (animator_.get())
264 animator_->StopAnimatingView(view);
265 delete 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.
276 if (animator_.get())
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 {
287 int width = 0;
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();
303 int height = 0;
304 int padding = 0;
305 for (int i = 0; i < child_count(); ++i) {
306 const views::View* child = child_at(i);
307 if (!IsValidChild(child))
308 continue;
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_|.
347 animator_->Cancel();
348 STLDeleteContainerPointers(deleting_views_.begin(), deleting_views_.end());
349 deleting_views_.clear();
350 adding_views_.clear();
351 animator_.reset();
354 reposition_top_ = -1;
355 fixed_height_ = 0;
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())
363 continue;
364 if (gfx::IntersectRects(child->bounds(), visible_scroll_rect).IsEmpty())
365 continue;
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);
377 if (animation)
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();
397 if (GetWidget())
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())
412 return;
414 if (animator_.get() && animator_->IsAnimating()) {
415 has_deferred_task_ = true;
416 return;
419 if (!animator_.get()) {
420 animator_.reset(new views::BoundsAnimator(this));
421 animator_->AddObserver(this);
424 if (!clearing_all_views_.empty()) {
425 AnimateClearingOneNotification();
426 return;
429 if (top_down_ ||
430 CommandLine::ForCurrentProcess()->HasSwitch(
431 switches::kEnableMessageCenterAlwaysScrollUpUponNotificationRemoval))
432 AnimateNotificationsBelowTarget();
433 else
434 AnimateNotificationsAboveTarget();
436 adding_views_.clear();
437 deleting_views_.clear();
440 void MessageListView::AnimateNotificationsBelowTarget() {
441 int last_index = -1;
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
448 // target).
449 last_index = i;
450 break;
453 if (last_index > 0) {
454 int between_items =
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() {
468 int last_index = -1;
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
475 // target).
476 last_index = i;
477 break;
480 if (last_index >= 0) {
481 int between_items =
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);
506 return false;
507 } else {
508 gfx::Rect target(child_area.x(), top, child_area.width(), height);
509 if (child->bounds().origin() != target.origin())
510 animator_->AnimateViewTo(child, target);
511 else
512 child->SetBoundsRect(target);
514 return true;
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(
533 FROM_HERE,
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,
545 int max_height,
546 bool initially_settings_visible,
547 bool top_down,
548 const base::string16& title)
549 : message_center_(message_center),
550 tray_(tray),
551 scroller_(NULL),
552 settings_view_(NULL),
553 button_bar_(NULL),
554 top_down_(top_down),
555 settings_visible_(initially_settings_visible),
556 source_view_(NULL),
557 source_height_(0),
558 target_view_(NULL),
559 target_height_(0),
560 is_closing_(false),
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,
570 message_center,
571 notifier_settings_provider,
572 initially_settings_visible,
573 title);
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
597 // out.
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);
607 else
608 settings_view_->SetVisible(false);
610 AddChildView(scroller_);
611 AddChildView(settings_view_);
612 AddChildView(button_bar_);
615 MessageCenterView::~MessageCenterView() {
616 if (!is_closing_)
617 message_center_->RemoveObserver(this);
620 void MessageCenterView::SetNotifications(
621 const NotificationList::Notifications& notifications) {
622 if (is_closing_)
623 return;
625 notification_views_.clear();
627 int index = 0;
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)
635 break;
638 NotificationsChanged();
639 scroller_->RequestFocus();
642 void MessageCenterView::SetSettingsVisible(bool visible) {
643 if (is_closing_)
644 return;
646 if (visible == settings_visible_)
647 return;
649 settings_visible_ = visible;
651 if (visible) {
652 source_view_ = scroller_;
653 target_view_ = settings_view_;
654 } else {
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));
670 } else {
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);
679 } else {
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() {
692 if (is_closing_)
693 return;
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();
714 Layout();
717 void MessageCenterView::SetIsClosing(bool is_closing) {
718 is_closing_ = is_closing;
719 if (is_closing)
720 message_center_->RemoveObserver(this);
721 else
722 message_center_->AddObserver(this);
725 void MessageCenterView::Layout() {
726 if (is_closing_)
727 return;
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) {
735 if (!top_down_) {
736 button_bar_->SetBounds(
737 0, height() - button_height, width(), button_height);
739 return;
742 scroller_->SetBounds(0,
743 top_down_ ? button_height : 0,
744 width(),
745 height() - button_height);
746 settings_view_->SetBounds(0,
747 top_down_ ? button_height : 0,
748 width(),
749 height() - button_height);
751 bool is_scrollable = false;
752 if (scroller_->visible())
753 is_scrollable = scroller_->height() < message_list_view_->height();
754 else
755 is_scrollable = settings_view_->IsScrollable();
757 if (!animating) {
758 if (is_scrollable) {
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));
763 } else {
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,
771 width(),
772 button_height);
773 if (GetWidget())
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));
787 int width = 0;
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);
810 else
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
820 // the buttons.
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) {
827 if (is_closing_)
828 return;
830 message_list_view_->ResetRepositionSession();
831 NotificationsChanged();
834 void MessageCenterView::OnNotificationAdded(const std::string& id) {
835 int index = 0;
836 const NotificationList::Notifications& notifications =
837 message_center_->GetVisibleNotifications();
838 for (NotificationList::Notifications::const_iterator iter =
839 notifications.begin(); iter != notifications.end();
840 ++iter, ++index) {
841 if ((*iter)->id() == id) {
842 AddNotificationAt(*(*iter), index);
843 break;
845 if (notification_views_.size() >= kMaxVisibleMessageCenterNotifications)
846 break;
848 NotificationsChanged();
851 void MessageCenterView::OnNotificationRemoved(const std::string& id,
852 bool by_user) {
853 NotificationViewsMap::iterator view_iter = notification_views_.find(id);
854 if (view_iter == notification_views_.end())
855 return;
856 NotificationView* view = view_iter->second;
857 int index = message_list_view_->GetIndexOf(view);
858 DCHECK_LE(0, index);
859 if (by_user) {
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);
869 else if (index > 0)
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();
877 } else {
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())
891 return;
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();
904 break;
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,
915 bool by_user) {
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,
931 int button_index) {
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();
951 Layout();
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());
961 SchedulePaint();
962 } else if (settings_transition_animation_->current_part_index() == 2 &&
963 target_view_->layer()) {
964 target_view_->layer()->SetOpacity(
965 settings_transition_animation_->GetCurrentValue());
966 SchedulePaint();
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,
976 int index) {
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.
994 if (focus_manager)
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();
1010 Layout();
1013 void MessageCenterView::SetNotificationViewForTest(MessageView* view) {
1014 message_list_view_->AddNotificationAt(view, 0);
1017 } // namespace message_center