Correct blacklist entry message
[chromium-blink-merge.git] / ui / message_center / views / message_center_view.cc
blob7225ea45d9270baa53771cdc7b4b73c107e1838f
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_strings.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_types.h"
24 #include "ui/message_center/views/message_center_button_bar.h"
25 #include "ui/message_center/views/message_view.h"
26 #include "ui/message_center/views/notification_view.h"
27 #include "ui/message_center/views/notifier_settings_view.h"
28 #include "ui/views/animation/bounds_animator.h"
29 #include "ui/views/animation/bounds_animator_observer.h"
30 #include "ui/views/background.h"
31 #include "ui/views/border.h"
32 #include "ui/views/controls/button/button.h"
33 #include "ui/views/controls/label.h"
34 #include "ui/views/controls/scroll_view.h"
35 #include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
36 #include "ui/views/layout/box_layout.h"
37 #include "ui/views/widget/widget.h"
39 namespace message_center {
41 namespace {
43 const SkColor kNoNotificationsTextColor = SkColorSetRGB(0xb4, 0xb4, 0xb4);
44 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
45 const SkColor kTransparentColor = SkColorSetARGB(0, 0, 0, 0);
46 #endif
47 const int kAnimateClearingNextNotificationDelayMS = 40;
48 const int kMinScrollViewHeight = 100;
50 const int kDefaultAnimationDurationMs = 120;
51 const int kDefaultFrameRateHz = 60;
53 } // namespace
55 // BoundedScrollView ///////////////////////////////////////////////////////////
57 // A custom scroll view whose height has a minimum and maximum value and whose
58 // scroll bar disappears when not needed.
59 class BoundedScrollView : public views::ScrollView {
60 public:
61 BoundedScrollView(int min_height, int max_height);
63 // Overridden from views::View:
64 virtual gfx::Size GetPreferredSize() OVERRIDE;
65 virtual int GetHeightForWidth(int width) OVERRIDE;
66 virtual void Layout() OVERRIDE;
68 private:
69 int min_height_;
70 int max_height_;
72 DISALLOW_COPY_AND_ASSIGN(BoundedScrollView);
75 BoundedScrollView::BoundedScrollView(int min_height, int max_height)
76 : min_height_(min_height),
77 max_height_(max_height) {
78 set_notify_enter_exit_on_child(true);
79 // Cancels the default dashed focus border.
80 set_focus_border(NULL);
81 set_background(
82 views::Background::CreateSolidBackground(kMessageCenterBackgroundColor));
83 SetVerticalScrollBar(new views::OverlayScrollBar(false));
86 gfx::Size BoundedScrollView::GetPreferredSize() {
87 gfx::Size size = contents()->GetPreferredSize();
88 size.SetToMax(gfx::Size(size.width(), min_height_));
89 size.SetToMin(gfx::Size(size.width(), max_height_));
90 gfx::Insets insets = GetInsets();
91 size.Enlarge(insets.width(), insets.height());
92 return size;
95 int BoundedScrollView::GetHeightForWidth(int width) {
96 gfx::Insets insets = GetInsets();
97 width = std::max(0, width - insets.width());
98 int height = contents()->GetHeightForWidth(width) + insets.height();
99 return std::min(std::max(height, min_height_), max_height_);
102 void BoundedScrollView::Layout() {
103 int content_width = width();
104 int content_height = contents()->GetHeightForWidth(content_width);
105 if (content_height > height()) {
106 content_width = std::max(content_width - GetScrollBarWidth(), 0);
107 content_height = contents()->GetHeightForWidth(content_width);
109 if (contents()->bounds().size() != gfx::Size(content_width, content_height))
110 contents()->SetBounds(0, 0, content_width, content_height);
111 views::ScrollView::Layout();
114 class NoNotificationMessageView : public views::View {
115 public:
116 NoNotificationMessageView();
117 virtual ~NoNotificationMessageView();
119 // Overridden from views::View.
120 virtual gfx::Size GetPreferredSize() OVERRIDE;
121 virtual int GetHeightForWidth(int width) OVERRIDE;
122 virtual void Layout() OVERRIDE;
124 private:
125 views::Label* label_;
127 DISALLOW_COPY_AND_ASSIGN(NoNotificationMessageView);
130 NoNotificationMessageView::NoNotificationMessageView() {
131 label_ = new views::Label(l10n_util::GetStringUTF16(
132 IDS_MESSAGE_CENTER_NO_MESSAGES));
133 label_->SetAutoColorReadabilityEnabled(false);
134 label_->SetEnabledColor(kNoNotificationsTextColor);
135 // Set transparent background to ensure that subpixel rendering
136 // is disabled. See crbug.com/169056
137 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
138 label_->SetBackgroundColor(kTransparentColor);
139 #endif
140 AddChildView(label_);
143 NoNotificationMessageView::~NoNotificationMessageView() {
146 gfx::Size NoNotificationMessageView::GetPreferredSize() {
147 return gfx::Size(kMinScrollViewHeight, label_->GetPreferredSize().width());
150 int NoNotificationMessageView::GetHeightForWidth(int width) {
151 return kMinScrollViewHeight;
154 void NoNotificationMessageView::Layout() {
155 int text_height = label_->GetHeightForWidth(width());
156 int margin = (height() - text_height) / 2;
157 label_->SetBounds(0, margin, width(), text_height);
160 // Displays a list of messages for rich notifications. It also supports
161 // repositioning.
162 class MessageListView : public views::View,
163 public views::BoundsAnimatorObserver {
164 public:
165 explicit MessageListView(MessageCenterView* message_center_view,
166 bool top_down);
167 virtual ~MessageListView();
169 void AddNotificationAt(views::View* view, int i);
170 void RemoveNotificationAt(int i);
171 void UpdateNotificationAt(views::View* view, int i);
172 void SetRepositionTarget(const gfx::Rect& target_rect);
173 void ResetRepositionSession();
174 void ClearAllNotifications(const gfx::Rect& visible_scroll_rect);
176 protected:
177 // Overridden from views::View.
178 virtual void Layout() OVERRIDE;
179 virtual gfx::Size GetPreferredSize() OVERRIDE;
180 virtual int GetHeightForWidth(int width) OVERRIDE;
181 virtual void PaintChildren(gfx::Canvas* canvas) OVERRIDE;
182 virtual void ReorderChildLayers(ui::Layer* parent_layer) OVERRIDE;
184 // Overridden from views::BoundsAnimatorObserver.
185 virtual void OnBoundsAnimatorProgressed(
186 views::BoundsAnimator* animator) OVERRIDE;
187 virtual void OnBoundsAnimatorDone(views::BoundsAnimator* animator) OVERRIDE;
189 private:
190 // Returns the actual index for child of |index|.
191 // MessageListView allows to slide down upper notifications, which means
192 // that the upper ones should come above the lower ones if top_down is not
193 // enabled. To achieve this, inversed order is adopted. The top most
194 // notification is the last child, and the bottom most notification is the
195 // first child.
196 int GetActualIndex(int index);
197 bool IsValidChild(views::View* child);
198 void DoUpdateIfPossible();
200 // Animates all notifications below target upwards to align with the top of
201 // the last closed notification.
202 void AnimateNotificationsBelowTarget();
203 // Animates all notifications above target downwards to align with the top of
204 // the last closed notification.
205 void AnimateNotificationsAboveTarget();
207 // Schedules animation for a child to the specified position. Returns false
208 // if |child| will disappear after the animation.
209 bool AnimateChild(views::View* child, int top, int height);
211 // Animate clearing one notification.
212 void AnimateClearingOneNotification();
213 MessageCenterView* message_center_view() const {
214 return message_center_view_;
217 MessageCenterView* message_center_view_; // Weak reference.
218 // The top position of the reposition target rectangle.
219 int reposition_top_;
220 int fixed_height_;
221 bool has_deferred_task_;
222 bool clear_all_started_;
223 bool top_down_;
224 std::set<views::View*> adding_views_;
225 std::set<views::View*> deleting_views_;
226 std::set<views::View*> deleted_when_done_;
227 std::list<views::View*> clearing_all_views_;
228 scoped_ptr<views::BoundsAnimator> animator_;
229 base::WeakPtrFactory<MessageListView> weak_ptr_factory_;
231 DISALLOW_COPY_AND_ASSIGN(MessageListView);
234 MessageListView::MessageListView(MessageCenterView* message_center_view,
235 bool top_down)
236 : message_center_view_(message_center_view),
237 reposition_top_(-1),
238 fixed_height_(0),
239 has_deferred_task_(false),
240 clear_all_started_(false),
241 top_down_(top_down),
242 weak_ptr_factory_(this) {
243 views::BoxLayout* layout =
244 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1);
245 layout->set_spread_blank_space(true);
246 SetLayoutManager(layout);
248 // Set the margin to 0 for the layout. BoxLayout assumes the same margin
249 // for top and bottom, but the bottom margin here should be smaller
250 // because of the shadow of message view. Use an empty border instead
251 // to provide this margin.
252 gfx::Insets shadow_insets = MessageView::GetShadowInsets();
253 set_background(views::Background::CreateSolidBackground(
254 kMessageCenterBackgroundColor));
255 set_border(views::Border::CreateEmptyBorder(
256 top_down ? 0 : kMarginBetweenItems - shadow_insets.top(), /* top */
257 kMarginBetweenItems - shadow_insets.left(), /* left */
258 top_down ? kMarginBetweenItems - shadow_insets.bottom() : 0, /* bottom */
259 kMarginBetweenItems - shadow_insets.right() /* right */));
262 MessageListView::~MessageListView() {
263 if (animator_.get())
264 animator_->RemoveObserver(this);
267 void MessageListView::Layout() {
268 if (animator_.get())
269 return;
271 gfx::Rect child_area = GetContentsBounds();
272 int top = child_area.y();
273 int between_items =
274 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
276 for (int i = 0; i < child_count(); ++i) {
277 views::View* child = child_at(i);
278 if (!child->visible())
279 continue;
280 int height = child->GetHeightForWidth(child_area.width());
281 child->SetBounds(child_area.x(), top, child_area.width(), height);
282 top += height + between_items;
286 void MessageListView::AddNotificationAt(views::View* view, int i) {
287 AddChildViewAt(view, GetActualIndex(i));
288 if (GetContentsBounds().IsEmpty())
289 return;
291 adding_views_.insert(view);
292 DoUpdateIfPossible();
295 void MessageListView::RemoveNotificationAt(int i) {
296 views::View* child = child_at(GetActualIndex(i));
297 if (GetContentsBounds().IsEmpty()) {
298 delete child;
299 } else {
300 if (child->layer()) {
301 deleting_views_.insert(child);
302 } else {
303 if (animator_.get())
304 animator_->StopAnimatingView(child);
305 delete child;
307 DoUpdateIfPossible();
311 void MessageListView::UpdateNotificationAt(views::View* view, int i) {
312 int actual_index = GetActualIndex(i);
313 views::View* child = child_at(actual_index);
314 if (animator_.get())
315 animator_->StopAnimatingView(child);
316 gfx::Rect old_bounds = child->bounds();
317 if (deleting_views_.find(child) != deleting_views_.end())
318 deleting_views_.erase(child);
319 if (deleted_when_done_.find(child) != deleted_when_done_.end())
320 deleted_when_done_.erase(child);
321 delete child;
322 AddChildViewAt(view, actual_index);
323 view->SetBounds(old_bounds.x(), old_bounds.y(), old_bounds.width(),
324 view->GetHeightForWidth(old_bounds.width()));
325 DoUpdateIfPossible();
328 gfx::Size MessageListView::GetPreferredSize() {
329 int width = 0;
330 for (int i = 0; i < child_count(); i++) {
331 views::View* child = child_at(i);
332 if (IsValidChild(child))
333 width = std::max(width, child->GetPreferredSize().width());
336 return gfx::Size(width + GetInsets().width(),
337 GetHeightForWidth(width + GetInsets().width()));
340 int MessageListView::GetHeightForWidth(int width) {
341 if (fixed_height_ > 0)
342 return fixed_height_;
344 width -= GetInsets().width();
345 int height = 0;
346 int padding = 0;
347 for (int i = 0; i < child_count(); ++i) {
348 views::View* child = child_at(i);
349 if (!IsValidChild(child))
350 continue;
351 height += child->GetHeightForWidth(width) + padding;
352 padding = kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
355 return height + GetInsets().height();
358 void MessageListView::PaintChildren(gfx::Canvas* canvas) {
359 // Paint in the inversed order. Otherwise upper notification may be
360 // hidden by the lower one.
361 for (int i = child_count() - 1; i >= 0; --i) {
362 if (!child_at(i)->layer())
363 child_at(i)->Paint(canvas);
367 void MessageListView::ReorderChildLayers(ui::Layer* parent_layer) {
368 // Reorder children to stack the last child layer at the top. Otherwise
369 // upper notification may be hidden by the lower one.
370 for (int i = 0; i < child_count(); ++i) {
371 if (child_at(i)->layer())
372 parent_layer->StackAtBottom(child_at(i)->layer());
376 void MessageListView::SetRepositionTarget(const gfx::Rect& target) {
377 reposition_top_ = target.y();
378 fixed_height_ = GetHeightForWidth(width());
381 void MessageListView::ResetRepositionSession() {
382 // Don't call DoUpdateIfPossible(), but let Layout() do the task without
383 // animation. Reset will cause the change of the bubble size itself, and
384 // animation from the old location will look weird.
385 if (reposition_top_ >= 0 && animator_.get()) {
386 has_deferred_task_ = false;
387 // cancel cause OnBoundsAnimatorDone which deletes |deleted_when_done_|.
388 animator_->Cancel();
389 STLDeleteContainerPointers(deleting_views_.begin(), deleting_views_.end());
390 deleting_views_.clear();
391 adding_views_.clear();
392 animator_.reset();
395 reposition_top_ = -1;
396 fixed_height_ = 0;
399 void MessageListView::ClearAllNotifications(
400 const gfx::Rect& visible_scroll_rect) {
401 for (int i = 0; i < child_count(); ++i) {
402 views::View* child = child_at(i);
403 if (!child->visible())
404 continue;
405 if (gfx::IntersectRects(child->bounds(), visible_scroll_rect).IsEmpty())
406 continue;
407 clearing_all_views_.push_back(child);
409 DoUpdateIfPossible();
412 void MessageListView::OnBoundsAnimatorProgressed(
413 views::BoundsAnimator* animator) {
414 DCHECK_EQ(animator_.get(), animator);
415 for (std::set<views::View*>::iterator iter = deleted_when_done_.begin();
416 iter != deleted_when_done_.end(); ++iter) {
417 const gfx::SlideAnimation* animation = animator->GetAnimationForView(*iter);
418 if (animation)
419 (*iter)->layer()->SetOpacity(animation->CurrentValueBetween(1.0, 0.0));
423 void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
424 STLDeleteContainerPointers(
425 deleted_when_done_.begin(), deleted_when_done_.end());
426 deleted_when_done_.clear();
428 if (clear_all_started_) {
429 clear_all_started_ = false;
430 message_center_view()->OnAllNotificationsCleared();
433 if (has_deferred_task_) {
434 has_deferred_task_ = false;
435 DoUpdateIfPossible();
438 if (GetWidget())
439 GetWidget()->SynthesizeMouseMoveEvent();
442 int MessageListView::GetActualIndex(int index) {
443 for (int i = 0; i < child_count() && i <= index; ++i)
444 index += IsValidChild(child_at(i)) ? 0 : 1;
445 return std::min(index, child_count());
448 bool MessageListView::IsValidChild(views::View* child) {
449 return child->visible() &&
450 deleting_views_.find(child) == deleting_views_.end() &&
451 deleted_when_done_.find(child) == deleted_when_done_.end();
454 void MessageListView::DoUpdateIfPossible() {
455 gfx::Rect child_area = GetContentsBounds();
456 if (child_area.IsEmpty())
457 return;
459 if (animator_.get() && animator_->IsAnimating()) {
460 has_deferred_task_ = true;
461 return;
464 if (!animator_.get()) {
465 animator_.reset(new views::BoundsAnimator(this));
466 animator_->AddObserver(this);
469 if (!clearing_all_views_.empty()) {
470 AnimateClearingOneNotification();
471 return;
474 if (top_down_)
475 AnimateNotificationsBelowTarget();
476 else
477 AnimateNotificationsAboveTarget();
479 adding_views_.clear();
480 deleting_views_.clear();
483 void MessageListView::AnimateNotificationsBelowTarget() {
484 int last_index = -1;
485 for (int i = 0; i < child_count(); ++i) {
486 views::View* child = child_at(i);
487 if (!IsValidChild(child)) {
488 AnimateChild(child, child->y(), child->height());
489 } else if (reposition_top_ < 0 || child->y() > reposition_top_) {
490 // Find first notification below target (or all notifications if no
491 // target).
492 last_index = i;
493 break;
496 if (last_index > 0) {
497 int between_items =
498 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
499 int top = (reposition_top_ > 0) ? reposition_top_ : GetInsets().top();
501 for (int i = last_index; i < child_count(); ++i) {
502 // Animate notifications below target upwards.
503 views::View* child = child_at(i);
504 if (AnimateChild(child, top, child->height()))
505 top += child->height() + between_items;
510 void MessageListView::AnimateNotificationsAboveTarget() {
511 int last_index = -1;
512 for (int i = child_count() - 1; i >= 0; --i) {
513 views::View* child = child_at(i);
514 if (!IsValidChild(child)) {
515 AnimateChild(child, child->y(), child->height());
516 } else if (reposition_top_ < 0 || child->y() < reposition_top_) {
517 // Find first notification above target (or all notifications if no
518 // target).
519 last_index = i;
520 break;
523 if (last_index > 0) {
524 int between_items =
525 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
526 int bottom = (reposition_top_ > 0)
527 ? reposition_top_ + child_at(last_index)->height()
528 : GetHeightForWidth(width()) - GetInsets().bottom();
529 for (int i = last_index; i >= 0; --i) {
530 // Animate notifications above target downwards.
531 views::View* child = child_at(i);
532 if (AnimateChild(child, bottom - child->height(), child->height()))
533 bottom -= child->height() + between_items;
538 bool MessageListView::AnimateChild(views::View* child, int top, int height) {
539 gfx::Rect child_area = GetContentsBounds();
540 if (adding_views_.find(child) != adding_views_.end()) {
541 child->SetBounds(child_area.right(), top, child_area.width(), height);
542 animator_->AnimateViewTo(
543 child, gfx::Rect(child_area.x(), top, child_area.width(), height));
544 } else if (deleting_views_.find(child) != deleting_views_.end()) {
545 DCHECK(child->layer());
546 // No moves, but animate to fade-out.
547 animator_->AnimateViewTo(child, child->bounds());
548 deleted_when_done_.insert(child);
549 return false;
550 } else {
551 gfx::Rect target(child_area.x(), top, child_area.width(), height);
552 if (child->bounds().origin() != target.origin())
553 animator_->AnimateViewTo(child, target);
554 else
555 child->SetBoundsRect(target);
557 return true;
560 void MessageListView::AnimateClearingOneNotification() {
561 DCHECK(!clearing_all_views_.empty());
563 clear_all_started_ = true;
565 views::View* child = clearing_all_views_.front();
566 clearing_all_views_.pop_front();
568 // Slide from left to right.
569 gfx::Rect new_bounds = child->bounds();
570 new_bounds.set_x(new_bounds.right() + kMarginBetweenItems);
571 animator_->AnimateViewTo(child, new_bounds);
573 // Schedule to start sliding out next notification after a short delay.
574 if (!clearing_all_views_.empty()) {
575 base::MessageLoop::current()->PostDelayedTask(
576 FROM_HERE,
577 base::Bind(&MessageListView::AnimateClearingOneNotification,
578 weak_ptr_factory_.GetWeakPtr()),
579 base::TimeDelta::FromMilliseconds(
580 kAnimateClearingNextNotificationDelayMS));
584 // MessageCenterView ///////////////////////////////////////////////////////////
586 MessageCenterView::MessageCenterView(MessageCenter* message_center,
587 MessageCenterTray* tray,
588 int max_height,
589 bool initially_settings_visible,
590 bool top_down)
591 : message_center_(message_center),
592 tray_(tray),
593 top_down_(top_down),
594 settings_visible_(initially_settings_visible),
595 source_view_(NULL),
596 source_height_(0),
597 target_view_(NULL),
598 target_height_(0),
599 is_closing_(false) {
600 message_center_->AddObserver(this);
601 set_notify_enter_exit_on_child(true);
602 set_background(views::Background::CreateSolidBackground(
603 kMessageCenterBackgroundColor));
605 NotifierSettingsProvider* notifier_settings_provider =
606 message_center_->GetNotifierSettingsProvider();
607 button_bar_ = new MessageCenterButtonBar(this,
608 message_center,
609 notifier_settings_provider,
610 initially_settings_visible);
612 const int button_height = button_bar_->GetPreferredSize().height();
614 scroller_ =
615 new BoundedScrollView(kMinScrollViewHeight, max_height - button_height);
617 if (get_use_acceleration_when_possible()) {
618 scroller_->SetPaintToLayer(true);
619 scroller_->SetFillsBoundsOpaquely(false);
620 scroller_->layer()->SetMasksToBounds(true);
623 message_list_view_ = new MessageListView(this, top_down);
624 no_notifications_message_view_ = new NoNotificationMessageView();
625 // Set the default visibility to false, otherwise the notification has slide
626 // in animation when the center is shown.
627 no_notifications_message_view_->SetVisible(false);
628 message_list_view_->AddChildView(no_notifications_message_view_);
629 scroller_->SetContents(message_list_view_);
631 settings_view_ = new NotifierSettingsView(notifier_settings_provider);
633 if (initially_settings_visible)
634 scroller_->SetVisible(false);
635 else
636 settings_view_->SetVisible(false);
638 AddChildView(scroller_);
639 AddChildView(settings_view_);
640 AddChildView(button_bar_);
643 MessageCenterView::~MessageCenterView() {
644 if (!is_closing_)
645 message_center_->RemoveObserver(this);
648 void MessageCenterView::SetNotifications(
649 const NotificationList::Notifications& notifications) {
650 if (is_closing_)
651 return;
653 message_views_.clear();
654 int index = 0;
655 for (NotificationList::Notifications::const_iterator iter =
656 notifications.begin(); iter != notifications.end();
657 ++iter, ++index) {
658 AddNotificationAt(*(*iter), index);
659 if (message_views_.size() >= kMaxVisibleMessageCenterNotifications)
660 break;
662 NotificationsChanged();
663 scroller_->RequestFocus();
666 void MessageCenterView::SetSettingsVisible(bool visible) {
667 if (is_closing_)
668 return;
670 if (visible == settings_visible_)
671 return;
673 settings_visible_ = visible;
675 if (visible) {
676 source_view_ = scroller_;
677 target_view_ = settings_view_;
678 } else {
679 source_view_ = settings_view_;
680 target_view_ = scroller_;
682 source_height_ = source_view_->GetHeightForWidth(width());
683 target_height_ = target_view_->GetHeightForWidth(width());
685 gfx::MultiAnimation::Parts parts;
686 // First part: slide resize animation.
687 parts.push_back(gfx::MultiAnimation::Part(
688 (source_height_ == target_height_) ? 0 : kDefaultAnimationDurationMs,
689 gfx::Tween::EASE_OUT));
690 // Second part: fade-out the source_view.
691 if (source_view_->layer()) {
692 parts.push_back(gfx::MultiAnimation::Part(
693 kDefaultAnimationDurationMs, gfx::Tween::LINEAR));
694 } else {
695 parts.push_back(gfx::MultiAnimation::Part());
697 // Third part: fade-in the target_view.
698 if (target_view_->layer()) {
699 parts.push_back(gfx::MultiAnimation::Part(
700 kDefaultAnimationDurationMs, gfx::Tween::LINEAR));
701 target_view_->layer()->SetOpacity(0);
702 target_view_->SetVisible(true);
703 } else {
704 parts.push_back(gfx::MultiAnimation::Part());
706 settings_transition_animation_.reset(new gfx::MultiAnimation(
707 parts, base::TimeDelta::FromMicroseconds(1000000 / kDefaultFrameRateHz)));
708 settings_transition_animation_->set_delegate(this);
709 settings_transition_animation_->set_continuous(false);
710 settings_transition_animation_->Start();
712 button_bar_->SetBackArrowVisible(visible);
715 void MessageCenterView::ClearAllNotifications() {
716 if (is_closing_)
717 return;
719 scroller_->SetEnabled(false);
720 button_bar_->SetAllButtonsEnabled(false);
721 message_list_view_->ClearAllNotifications(scroller_->GetVisibleRect());
724 void MessageCenterView::OnAllNotificationsCleared() {
725 scroller_->SetEnabled(true);
726 button_bar_->SetAllButtonsEnabled(true);
727 button_bar_->SetCloseAllButtonEnabled(false);
728 message_center_->RemoveAllNotifications(true); // Action by user.
731 size_t MessageCenterView::NumMessageViewsForTest() const {
732 return message_list_view_->child_count();
735 void MessageCenterView::OnSettingsChanged() {
736 scroller_->InvalidateLayout();
737 PreferredSizeChanged();
738 Layout();
741 void MessageCenterView::SetIsClosing(bool is_closing) {
742 is_closing_ = is_closing;
743 if (is_closing)
744 message_center_->RemoveObserver(this);
745 else
746 message_center_->AddObserver(this);
749 void MessageCenterView::Layout() {
750 if (is_closing_)
751 return;
753 int button_height = button_bar_->GetHeightForWidth(width()) +
754 button_bar_->GetInsets().height();
755 // Skip unnecessary re-layout of contents during the resize animation.
756 bool animating = settings_transition_animation_ &&
757 settings_transition_animation_->is_animating();
758 if (animating && settings_transition_animation_->current_part_index() == 0) {
759 if (!top_down_) {
760 button_bar_->SetBounds(
761 0, height() - button_height, width(), button_height);
763 return;
766 scroller_->SetBounds(0,
767 top_down_ ? button_height : 0,
768 width(),
769 height() - button_height);
770 settings_view_->SetBounds(0,
771 top_down_ ? button_height : 0,
772 width(),
773 height() - button_height);
775 bool is_scrollable = false;
776 if (scroller_->visible())
777 is_scrollable = scroller_->height() < message_list_view_->height();
778 else
779 is_scrollable = settings_view_->IsScrollable();
781 if (!animating) {
782 if (is_scrollable) {
783 // Draw separator line on the top of the button bar if it is on the bottom
784 // or draw it at the bottom if the bar is on the top.
785 button_bar_->set_border(views::Border::CreateSolidSidedBorder(
786 top_down_ ? 0 : 1, 0, top_down_ ? 1 : 0, 0, kFooterDelimiterColor));
787 } else {
788 button_bar_->set_border(views::Border::CreateEmptyBorder(
789 top_down_ ? 0 : 1, 0, top_down_ ? 1 : 0, 0));
791 button_bar_->SchedulePaint();
793 button_bar_->SetBounds(0,
794 top_down_ ? 0 : height() - button_height,
795 width(),
796 button_height);
797 if (GetWidget())
798 GetWidget()->GetRootView()->SchedulePaint();
801 gfx::Size MessageCenterView::GetPreferredSize() {
802 if (settings_transition_animation_ &&
803 settings_transition_animation_->is_animating()) {
804 int content_width = std::max(source_view_->GetPreferredSize().width(),
805 target_view_->GetPreferredSize().width());
806 int width = std::max(content_width,
807 button_bar_->GetPreferredSize().width());
808 return gfx::Size(width, GetHeightForWidth(width));
811 int width = 0;
812 for (int i = 0; i < child_count(); ++i) {
813 views::View* child = child_at(0);
814 if (child->visible())
815 width = std::max(width, child->GetPreferredSize().width());
817 return gfx::Size(width, GetHeightForWidth(width));
820 int MessageCenterView::GetHeightForWidth(int width) {
821 if (settings_transition_animation_ &&
822 settings_transition_animation_->is_animating()) {
823 int content_height = target_height_;
824 if (settings_transition_animation_->current_part_index() == 0) {
825 content_height = settings_transition_animation_->CurrentValueBetween(
826 source_height_, target_height_);
828 return button_bar_->GetHeightForWidth(width) + content_height;
831 int content_height = 0;
832 if (scroller_->visible())
833 content_height += scroller_->GetHeightForWidth(width);
834 else
835 content_height += settings_view_->GetHeightForWidth(width);
836 return button_bar_->GetHeightForWidth(width) +
837 button_bar_->GetInsets().height() + content_height;
840 bool MessageCenterView::OnMouseWheel(const ui::MouseWheelEvent& event) {
841 // Do not rely on the default scroll event handler of ScrollView because
842 // the scroll happens only when the focus is on the ScrollView. The
843 // notification center will allow the scrolling even when the focus is on
844 // the buttons.
845 if (scroller_->bounds().Contains(event.location()))
846 return scroller_->OnMouseWheel(event);
847 return views::View::OnMouseWheel(event);
850 void MessageCenterView::OnMouseExited(const ui::MouseEvent& event) {
851 if (is_closing_)
852 return;
854 message_list_view_->ResetRepositionSession();
855 NotificationsChanged();
858 void MessageCenterView::OnNotificationAdded(const std::string& id) {
859 int index = 0;
860 const NotificationList::Notifications& notifications =
861 message_center_->GetVisibleNotifications();
862 for (NotificationList::Notifications::const_iterator iter =
863 notifications.begin(); iter != notifications.end();
864 ++iter, ++index) {
865 if ((*iter)->id() == id) {
866 AddNotificationAt(*(*iter), index);
867 break;
869 if (message_views_.size() >= kMaxVisibleMessageCenterNotifications)
870 break;
872 NotificationsChanged();
875 void MessageCenterView::OnNotificationRemoved(const std::string& id,
876 bool by_user) {
877 for (size_t i = 0; i < message_views_.size(); ++i) {
878 if (message_views_[i]->notification_id() == id) {
879 if (by_user) {
880 message_list_view_->SetRepositionTarget(message_views_[i]->bounds());
881 // Moves the keyboard focus to the next notification if the removed
882 // notification is focused so that the user can dismiss notifications
883 // without re-focusing by tab key.
884 if (message_views_.size() > 1) {
885 views::View* focused_view = GetFocusManager()->GetFocusedView();
886 if (message_views_[i]->IsCloseButtonFocused() ||
887 focused_view == message_views_[i]) {
888 size_t next_index = i + 1;
889 if (next_index >= message_views_.size())
890 next_index = message_views_.size() - 2;
891 if (focused_view == message_views_[i])
892 message_views_[next_index]->RequestFocus();
893 else
894 message_views_[next_index]->RequestFocusOnCloseButton();
898 message_list_view_->RemoveNotificationAt(i);
899 message_views_.erase(message_views_.begin() + i);
900 NotificationsChanged();
901 break;
906 void MessageCenterView::OnNotificationUpdated(const std::string& id) {
907 const NotificationList::Notifications& notifications =
908 message_center_->GetVisibleNotifications();
909 size_t index = 0;
910 for (NotificationList::Notifications::const_iterator iter =
911 notifications.begin();
912 iter != notifications.end() && index < message_views_.size();
913 ++iter, ++index) {
914 DCHECK((*iter)->id() == message_views_[index]->notification_id());
915 if ((*iter)->id() == id) {
916 MessageView* view =
917 NotificationView::Create(*(*iter),
918 message_center_,
919 tray_,
920 true, // Create expanded.
921 false); // Not creating a top-level
922 // notification.
923 view->set_scroller(scroller_);
924 message_list_view_->UpdateNotificationAt(view, index);
925 message_views_[index] = view;
926 NotificationsChanged();
927 break;
932 void MessageCenterView::AnimationEnded(const gfx::Animation* animation) {
933 DCHECK_EQ(animation, settings_transition_animation_.get());
935 Visibility visibility = target_view_ == settings_view_
936 ? VISIBILITY_SETTINGS
937 : VISIBILITY_MESSAGE_CENTER;
938 message_center_->SetVisibility(visibility);
940 source_view_->SetVisible(false);
941 target_view_->SetVisible(true);
942 if (source_view_->layer())
943 source_view_->layer()->SetOpacity(1.0);
944 if (target_view_->layer())
945 target_view_->layer()->SetOpacity(1.0);
946 settings_transition_animation_.reset();
947 PreferredSizeChanged();
948 Layout();
951 void MessageCenterView::AnimationProgressed(const gfx::Animation* animation) {
952 DCHECK_EQ(animation, settings_transition_animation_.get());
953 PreferredSizeChanged();
954 if (settings_transition_animation_->current_part_index() == 1 &&
955 source_view_->layer()) {
956 source_view_->layer()->SetOpacity(
957 1.0 - settings_transition_animation_->GetCurrentValue());
958 SchedulePaint();
959 } else if (settings_transition_animation_->current_part_index() == 2 &&
960 target_view_->layer()) {
961 target_view_->layer()->SetOpacity(
962 settings_transition_animation_->GetCurrentValue());
963 SchedulePaint();
967 void MessageCenterView::AnimationCanceled(const gfx::Animation* animation) {
968 DCHECK_EQ(animation, settings_transition_animation_.get());
969 AnimationEnded(animation);
972 void MessageCenterView::AddNotificationAt(const Notification& notification,
973 int index) {
974 // NotificationViews are expanded by default here until
975 // http://crbug.com/217902 is fixed. TODO(dharcourt): Fix.
976 MessageView* view =
977 NotificationView::Create(notification,
978 message_center_,
979 tray_,
980 true, // Create expanded.
981 false); // Not creating a top-level
982 // notification.
983 view->set_scroller(scroller_);
984 message_views_.insert(message_views_.begin() + index, view);
985 message_list_view_->AddNotificationAt(view, index);
986 message_center_->DisplayedNotification(notification.id());
989 void MessageCenterView::NotificationsChanged() {
990 bool no_message_views = message_views_.empty();
992 no_notifications_message_view_->SetVisible(no_message_views);
993 button_bar_->SetCloseAllButtonEnabled(!no_message_views);
994 scroller_->set_focusable(!no_message_views);
996 scroller_->InvalidateLayout();
997 PreferredSizeChanged();
998 Layout();
1001 void MessageCenterView::SetNotificationViewForTest(views::View* view) {
1002 message_list_view_->AddNotificationAt(view, 0);
1005 } // namespace message_center