cc: Add more eviction categories to picture layer impl.
[chromium-blink-merge.git] / ui / message_center / views / message_center_view.cc
blob6a7e815cce9cfc4d67b090bf4f29226c3b9445af
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 "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 {
45 namespace {
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);
50 #endif
51 const int kAnimateClearingNextNotificationDelayMS = 40;
53 const int kDefaultAnimationDurationMs = 120;
54 const int kDefaultFrameRateHz = 60;
55 } // namespace
57 class NoNotificationMessageView : public views::View {
58 public:
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;
67 private:
68 views::Label* label_;
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);
82 #endif
83 AddChildView(label_);
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
105 // repositioning.
106 class MessageListView : public views::View,
107 public views::BoundsAnimatorObserver {
108 public:
109 explicit MessageListView(MessageCenterView* message_center_view,
110 bool top_down);
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);
120 protected:
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;
134 private:
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.
157 int reposition_top_;
158 int fixed_height_;
159 bool has_deferred_task_;
160 bool clear_all_started_;
161 bool top_down_;
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,
173 bool top_down)
174 : message_center_view_(message_center_view),
175 reposition_top_(-1),
176 fixed_height_(0),
177 has_deferred_task_(false),
178 clear_all_started_(false),
179 top_down_(top_down),
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() {
201 if (animator_.get())
202 animator_->RemoveObserver(this);
205 void MessageListView::Layout() {
206 if (animator_.get())
207 return;
209 gfx::Rect child_area = GetContentsBounds();
210 int top = child_area.y();
211 int between_items =
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())
217 continue;
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.
229 int real_index = 0;
230 while (real_index < child_count()) {
231 if (IsValidChild(child_at(real_index))) {
232 --index;
233 if (index < 0)
234 break;
236 ++real_index;
239 AddChildViewAt(view, real_index);
240 if (GetContentsBounds().IsEmpty())
241 return;
243 adding_views_.insert(view);
244 DoUpdateIfPossible();
247 void MessageListView::RemoveNotification(MessageView* view) {
248 DCHECK_EQ(view->parent(), this);
249 if (GetContentsBounds().IsEmpty()) {
250 delete view;
251 } else {
252 if (view->layer()) {
253 deleting_views_.insert(view);
254 } else {
255 if (animator_.get())
256 animator_->StopAnimatingView(view);
257 delete 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.
268 if (animator_.get())
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 {
279 int width = 0;
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();
295 int height = 0;
296 int padding = 0;
297 for (int i = 0; i < child_count(); ++i) {
298 const views::View* child = child_at(i);
299 if (!IsValidChild(child))
300 continue;
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_|.
339 animator_->Cancel();
340 STLDeleteContainerPointers(deleting_views_.begin(), deleting_views_.end());
341 deleting_views_.clear();
342 adding_views_.clear();
343 animator_.reset();
346 reposition_top_ = -1;
347 fixed_height_ = 0;
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())
355 continue;
356 if (gfx::IntersectRects(child->bounds(), visible_scroll_rect).IsEmpty())
357 continue;
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);
369 if (animation)
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();
389 if (GetWidget())
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())
404 return;
406 if (animator_.get() && animator_->IsAnimating()) {
407 has_deferred_task_ = true;
408 return;
411 if (!animator_.get()) {
412 animator_.reset(new views::BoundsAnimator(this));
413 animator_->AddObserver(this);
416 if (!clearing_all_views_.empty()) {
417 AnimateClearingOneNotification();
418 return;
421 if (top_down_)
422 AnimateNotificationsBelowTarget();
423 else
424 AnimateNotificationsAboveTarget();
426 adding_views_.clear();
427 deleting_views_.clear();
430 void MessageListView::AnimateNotificationsBelowTarget() {
431 int last_index = -1;
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
438 // target).
439 last_index = i;
440 break;
443 if (last_index > 0) {
444 int between_items =
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() {
458 int last_index = -1;
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
465 // target).
466 last_index = i;
467 break;
470 if (last_index >= 0) {
471 int between_items =
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);
496 return false;
497 } else {
498 gfx::Rect target(child_area.x(), top, child_area.width(), height);
499 if (child->bounds().origin() != target.origin())
500 animator_->AnimateViewTo(child, target);
501 else
502 child->SetBoundsRect(target);
504 return true;
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(
523 FROM_HERE,
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,
535 int max_height,
536 bool initially_settings_visible,
537 bool top_down,
538 const base::string16& title)
539 : message_center_(message_center),
540 tray_(tray),
541 scroller_(NULL),
542 settings_view_(NULL),
543 button_bar_(NULL),
544 top_down_(top_down),
545 settings_visible_(initially_settings_visible),
546 source_view_(NULL),
547 source_height_(0),
548 target_view_(NULL),
549 target_height_(0),
550 is_closing_(false),
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,
560 message_center,
561 notifier_settings_provider,
562 initially_settings_visible,
563 title);
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
587 // out.
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);
597 else
598 settings_view_->SetVisible(false);
600 AddChildView(scroller_);
601 AddChildView(settings_view_);
602 AddChildView(button_bar_);
605 MessageCenterView::~MessageCenterView() {
606 if (!is_closing_)
607 message_center_->RemoveObserver(this);
610 void MessageCenterView::SetNotifications(
611 const NotificationList::Notifications& notifications) {
612 if (is_closing_)
613 return;
615 notification_views_.clear();
617 int index = 0;
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)
625 break;
628 NotificationsChanged();
629 scroller_->RequestFocus();
632 void MessageCenterView::SetSettingsVisible(bool visible) {
633 if (is_closing_)
634 return;
636 if (visible == settings_visible_)
637 return;
639 settings_visible_ = visible;
641 if (visible) {
642 source_view_ = scroller_;
643 target_view_ = settings_view_;
644 } else {
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));
660 } else {
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);
669 } else {
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() {
682 if (is_closing_)
683 return;
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();
704 Layout();
707 void MessageCenterView::SetIsClosing(bool is_closing) {
708 is_closing_ = is_closing;
709 if (is_closing)
710 message_center_->RemoveObserver(this);
711 else
712 message_center_->AddObserver(this);
715 void MessageCenterView::Layout() {
716 if (is_closing_)
717 return;
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) {
725 if (!top_down_) {
726 button_bar_->SetBounds(
727 0, height() - button_height, width(), button_height);
729 return;
732 scroller_->SetBounds(0,
733 top_down_ ? button_height : 0,
734 width(),
735 height() - button_height);
736 settings_view_->SetBounds(0,
737 top_down_ ? button_height : 0,
738 width(),
739 height() - button_height);
741 bool is_scrollable = false;
742 if (scroller_->visible())
743 is_scrollable = scroller_->height() < message_list_view_->height();
744 else
745 is_scrollable = settings_view_->IsScrollable();
747 if (!animating) {
748 if (is_scrollable) {
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));
753 } else {
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,
761 width(),
762 button_height);
763 if (GetWidget())
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));
777 int width = 0;
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);
800 else
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
810 // the buttons.
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) {
817 if (is_closing_)
818 return;
820 message_list_view_->ResetRepositionSession();
821 NotificationsChanged();
824 void MessageCenterView::OnNotificationAdded(const std::string& id) {
825 int index = 0;
826 const NotificationList::Notifications& notifications =
827 message_center_->GetVisibleNotifications();
828 for (NotificationList::Notifications::const_iterator iter =
829 notifications.begin(); iter != notifications.end();
830 ++iter, ++index) {
831 if ((*iter)->id() == id) {
832 AddNotificationAt(*(*iter), index);
833 break;
835 if (notification_views_.size() >= kMaxVisibleMessageCenterNotifications)
836 break;
838 NotificationsChanged();
841 void MessageCenterView::OnNotificationRemoved(const std::string& id,
842 bool by_user) {
843 NotificationViewsMap::iterator view_iter = notification_views_.find(id);
844 if (view_iter == notification_views_.end())
845 return;
846 NotificationView* view = view_iter->second;
847 int index = message_list_view_->GetIndexOf(view);
848 DCHECK_LE(0, index);
849 if (by_user) {
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);
859 else if (index > 0)
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();
867 else
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())
880 return;
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();
893 break;
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,
904 bool by_user) {
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,
920 int button_index) {
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();
940 Layout();
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());
950 SchedulePaint();
951 } else if (settings_transition_animation_->current_part_index() == 2 &&
952 target_view_->layer()) {
953 target_view_->layer()->SetOpacity(
954 settings_transition_animation_->GetCurrentValue());
955 SchedulePaint();
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,
965 int index) {
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.
983 if (focus_manager)
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();
999 Layout();
1002 void MessageCenterView::SetNotificationViewForTest(MessageView* view) {
1003 message_list_view_->AddNotificationAt(view, 0);
1006 } // namespace message_center