+ Fix cloud sprite clipping on 2x
[chromium-blink-merge.git] / ui / message_center / views / message_center_view.cc
blob5b3ee28f030d8447a8e0d18fdd171fbc5723f120
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;
57 } // namespace
59 class NoNotificationMessageView : public views::View {
60 public:
61 NoNotificationMessageView();
62 virtual ~NoNotificationMessageView();
64 // Overridden from views::View.
65 virtual gfx::Size GetPreferredSize() const OVERRIDE;
66 virtual int GetHeightForWidth(int width) const OVERRIDE;
67 virtual void Layout() OVERRIDE;
69 private:
70 views::Label* label_;
72 DISALLOW_COPY_AND_ASSIGN(NoNotificationMessageView);
75 NoNotificationMessageView::NoNotificationMessageView() {
76 label_ = new views::Label(l10n_util::GetStringUTF16(
77 IDS_MESSAGE_CENTER_NO_MESSAGES));
78 label_->SetAutoColorReadabilityEnabled(false);
79 label_->SetEnabledColor(kNoNotificationsTextColor);
80 // Set transparent background to ensure that subpixel rendering
81 // is disabled. See crbug.com/169056
82 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
83 label_->SetBackgroundColor(kTransparentColor);
84 #endif
85 AddChildView(label_);
88 NoNotificationMessageView::~NoNotificationMessageView() {
91 gfx::Size NoNotificationMessageView::GetPreferredSize() const {
92 return gfx::Size(kMinScrollViewHeight, label_->GetPreferredSize().width());
95 int NoNotificationMessageView::GetHeightForWidth(int width) const {
96 return kMinScrollViewHeight;
99 void NoNotificationMessageView::Layout() {
100 int text_height = label_->GetHeightForWidth(width());
101 int margin = (height() - text_height) / 2;
102 label_->SetBounds(0, margin, width(), text_height);
105 // Displays a list of messages for rich notifications. Functions as an array of
106 // MessageViews and animates them on transitions. It also supports
107 // repositioning.
108 class MessageListView : public views::View,
109 public views::BoundsAnimatorObserver {
110 public:
111 explicit MessageListView(MessageCenterView* message_center_view,
112 bool top_down);
113 virtual ~MessageListView();
115 void AddNotificationAt(MessageView* view, int i);
116 void RemoveNotification(MessageView* view);
117 void UpdateNotification(MessageView* view, const Notification& notification);
118 void SetRepositionTarget(const gfx::Rect& target_rect);
119 void ResetRepositionSession();
120 void ClearAllNotifications(const gfx::Rect& visible_scroll_rect);
122 protected:
123 // Overridden from views::View.
124 virtual void Layout() OVERRIDE;
125 virtual gfx::Size GetPreferredSize() const OVERRIDE;
126 virtual int GetHeightForWidth(int width) const OVERRIDE;
127 virtual void PaintChildren(gfx::Canvas* canvas,
128 const views::CullSet& cull_set) OVERRIDE;
129 virtual void ReorderChildLayers(ui::Layer* parent_layer) OVERRIDE;
131 // Overridden from views::BoundsAnimatorObserver.
132 virtual void OnBoundsAnimatorProgressed(
133 views::BoundsAnimator* animator) OVERRIDE;
134 virtual void OnBoundsAnimatorDone(views::BoundsAnimator* animator) OVERRIDE;
136 private:
137 bool IsValidChild(const views::View* child) const;
138 void DoUpdateIfPossible();
140 // Animates all notifications below target upwards to align with the top of
141 // the last closed notification.
142 void AnimateNotificationsBelowTarget();
143 // Animates all notifications above target downwards to align with the top of
144 // the last closed notification.
145 void AnimateNotificationsAboveTarget();
147 // Schedules animation for a child to the specified position. Returns false
148 // if |child| will disappear after the animation.
149 bool AnimateChild(views::View* child, int top, int height);
151 // Animate clearing one notification.
152 void AnimateClearingOneNotification();
153 MessageCenterView* message_center_view() const {
154 return message_center_view_;
157 MessageCenterView* message_center_view_; // Weak reference.
158 // The top position of the reposition target rectangle.
159 int reposition_top_;
160 int fixed_height_;
161 bool has_deferred_task_;
162 bool clear_all_started_;
163 bool top_down_;
164 std::set<views::View*> adding_views_;
165 std::set<views::View*> deleting_views_;
166 std::set<views::View*> deleted_when_done_;
167 std::list<views::View*> clearing_all_views_;
168 scoped_ptr<views::BoundsAnimator> animator_;
169 base::WeakPtrFactory<MessageListView> weak_ptr_factory_;
171 DISALLOW_COPY_AND_ASSIGN(MessageListView);
174 MessageListView::MessageListView(MessageCenterView* message_center_view,
175 bool top_down)
176 : message_center_view_(message_center_view),
177 reposition_top_(-1),
178 fixed_height_(0),
179 has_deferred_task_(false),
180 clear_all_started_(false),
181 top_down_(top_down),
182 weak_ptr_factory_(this) {
183 views::BoxLayout* layout =
184 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1);
185 layout->SetDefaultFlex(1);
186 SetLayoutManager(layout);
188 // Set the margin to 0 for the layout. BoxLayout assumes the same margin
189 // for top and bottom, but the bottom margin here should be smaller
190 // because of the shadow of message view. Use an empty border instead
191 // to provide this margin.
192 gfx::Insets shadow_insets = MessageView::GetShadowInsets();
193 set_background(views::Background::CreateSolidBackground(
194 kMessageCenterBackgroundColor));
195 SetBorder(views::Border::CreateEmptyBorder(
196 top_down ? 0 : kMarginBetweenItems - shadow_insets.top(), /* top */
197 kMarginBetweenItems - shadow_insets.left(), /* left */
198 top_down ? kMarginBetweenItems - shadow_insets.bottom() : 0, /* bottom */
199 kMarginBetweenItems - shadow_insets.right() /* right */));
202 MessageListView::~MessageListView() {
203 if (animator_.get())
204 animator_->RemoveObserver(this);
207 void MessageListView::Layout() {
208 if (animator_.get())
209 return;
211 gfx::Rect child_area = GetContentsBounds();
212 int top = child_area.y();
213 int between_items =
214 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
216 for (int i = 0; i < child_count(); ++i) {
217 views::View* child = child_at(i);
218 if (!child->visible())
219 continue;
220 int height = child->GetHeightForWidth(child_area.width());
221 child->SetBounds(child_area.x(), top, child_area.width(), height);
222 top += height + between_items;
226 void MessageListView::AddNotificationAt(MessageView* view, int index) {
227 // |index| refers to a position in a subset of valid children. |real_index|
228 // in a list includes the invalid children, so we compute the real index by
229 // walking the list until |index| number of valid children are encountered,
230 // or to the end of the list.
231 int real_index = 0;
232 while (real_index < child_count()) {
233 if (IsValidChild(child_at(real_index))) {
234 --index;
235 if (index < 0)
236 break;
238 ++real_index;
241 AddChildViewAt(view, real_index);
242 if (GetContentsBounds().IsEmpty())
243 return;
245 adding_views_.insert(view);
246 DoUpdateIfPossible();
249 void MessageListView::RemoveNotification(MessageView* view) {
250 DCHECK_EQ(view->parent(), this);
251 if (GetContentsBounds().IsEmpty()) {
252 delete view;
253 } else {
254 if (view->layer()) {
255 deleting_views_.insert(view);
256 } else {
257 if (animator_.get())
258 animator_->StopAnimatingView(view);
259 delete view;
261 DoUpdateIfPossible();
265 void MessageListView::UpdateNotification(MessageView* view,
266 const Notification& notification) {
267 int index = GetIndexOf(view);
268 DCHECK_LE(0, index); // GetIndexOf is negative if not a child.
270 if (animator_.get())
271 animator_->StopAnimatingView(view);
272 if (deleting_views_.find(view) != deleting_views_.end())
273 deleting_views_.erase(view);
274 if (deleted_when_done_.find(view) != deleted_when_done_.end())
275 deleted_when_done_.erase(view);
276 view->UpdateWithNotification(notification);
277 DoUpdateIfPossible();
280 gfx::Size MessageListView::GetPreferredSize() const {
281 int width = 0;
282 for (int i = 0; i < child_count(); i++) {
283 const views::View* child = child_at(i);
284 if (IsValidChild(child))
285 width = std::max(width, child->GetPreferredSize().width());
288 return gfx::Size(width + GetInsets().width(),
289 GetHeightForWidth(width + GetInsets().width()));
292 int MessageListView::GetHeightForWidth(int width) const {
293 if (fixed_height_ > 0)
294 return fixed_height_;
296 width -= GetInsets().width();
297 int height = 0;
298 int padding = 0;
299 for (int i = 0; i < child_count(); ++i) {
300 const views::View* child = child_at(i);
301 if (!IsValidChild(child))
302 continue;
303 height += child->GetHeightForWidth(width) + padding;
304 padding = kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
307 return height + GetInsets().height();
310 void MessageListView::PaintChildren(gfx::Canvas* canvas,
311 const views::CullSet& cull_set) {
312 // Paint in the inversed order. Otherwise upper notification may be
313 // hidden by the lower one.
314 for (int i = child_count() - 1; i >= 0; --i) {
315 if (!child_at(i)->layer())
316 child_at(i)->Paint(canvas, cull_set);
320 void MessageListView::ReorderChildLayers(ui::Layer* parent_layer) {
321 // Reorder children to stack the last child layer at the top. Otherwise
322 // upper notification may be hidden by the lower one.
323 for (int i = 0; i < child_count(); ++i) {
324 if (child_at(i)->layer())
325 parent_layer->StackAtBottom(child_at(i)->layer());
329 void MessageListView::SetRepositionTarget(const gfx::Rect& target) {
330 reposition_top_ = target.y();
331 fixed_height_ = GetHeightForWidth(width());
334 void MessageListView::ResetRepositionSession() {
335 // Don't call DoUpdateIfPossible(), but let Layout() do the task without
336 // animation. Reset will cause the change of the bubble size itself, and
337 // animation from the old location will look weird.
338 if (reposition_top_ >= 0 && animator_.get()) {
339 has_deferred_task_ = false;
340 // cancel cause OnBoundsAnimatorDone which deletes |deleted_when_done_|.
341 animator_->Cancel();
342 STLDeleteContainerPointers(deleting_views_.begin(), deleting_views_.end());
343 deleting_views_.clear();
344 adding_views_.clear();
345 animator_.reset();
348 reposition_top_ = -1;
349 fixed_height_ = 0;
352 void MessageListView::ClearAllNotifications(
353 const gfx::Rect& visible_scroll_rect) {
354 for (int i = 0; i < child_count(); ++i) {
355 views::View* child = child_at(i);
356 if (!child->visible())
357 continue;
358 if (gfx::IntersectRects(child->bounds(), visible_scroll_rect).IsEmpty())
359 continue;
360 clearing_all_views_.push_back(child);
362 DoUpdateIfPossible();
365 void MessageListView::OnBoundsAnimatorProgressed(
366 views::BoundsAnimator* animator) {
367 DCHECK_EQ(animator_.get(), animator);
368 for (std::set<views::View*>::iterator iter = deleted_when_done_.begin();
369 iter != deleted_when_done_.end(); ++iter) {
370 const gfx::SlideAnimation* animation = animator->GetAnimationForView(*iter);
371 if (animation)
372 (*iter)->layer()->SetOpacity(animation->CurrentValueBetween(1.0, 0.0));
376 void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
377 STLDeleteContainerPointers(
378 deleted_when_done_.begin(), deleted_when_done_.end());
379 deleted_when_done_.clear();
381 if (clear_all_started_) {
382 clear_all_started_ = false;
383 message_center_view()->OnAllNotificationsCleared();
386 if (has_deferred_task_) {
387 has_deferred_task_ = false;
388 DoUpdateIfPossible();
391 if (GetWidget())
392 GetWidget()->SynthesizeMouseMoveEvent();
395 bool MessageListView::IsValidChild(const views::View* child) const {
396 return child->visible() &&
397 deleting_views_.find(const_cast<views::View*>(child)) ==
398 deleting_views_.end() &&
399 deleted_when_done_.find(const_cast<views::View*>(child)) ==
400 deleted_when_done_.end();
403 void MessageListView::DoUpdateIfPossible() {
404 gfx::Rect child_area = GetContentsBounds();
405 if (child_area.IsEmpty())
406 return;
408 if (animator_.get() && animator_->IsAnimating()) {
409 has_deferred_task_ = true;
410 return;
413 if (!animator_.get()) {
414 animator_.reset(new views::BoundsAnimator(this));
415 animator_->AddObserver(this);
418 if (!clearing_all_views_.empty()) {
419 AnimateClearingOneNotification();
420 return;
423 if (top_down_ ||
424 CommandLine::ForCurrentProcess()->HasSwitch(
425 switches::kEnableMessageCenterAlwaysScrollUpUponNotificationRemoval))
426 AnimateNotificationsBelowTarget();
427 else
428 AnimateNotificationsAboveTarget();
430 adding_views_.clear();
431 deleting_views_.clear();
434 void MessageListView::AnimateNotificationsBelowTarget() {
435 int last_index = -1;
436 for (int i = 0; i < child_count(); ++i) {
437 views::View* child = child_at(i);
438 if (!IsValidChild(child)) {
439 AnimateChild(child, child->y(), child->height());
440 } else if (reposition_top_ < 0 || child->y() > reposition_top_) {
441 // Find first notification below target (or all notifications if no
442 // target).
443 last_index = i;
444 break;
447 if (last_index > 0) {
448 int between_items =
449 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
450 int top = (reposition_top_ > 0) ? reposition_top_ : GetInsets().top();
452 for (int i = last_index; i < child_count(); ++i) {
453 // Animate notifications below target upwards.
454 views::View* child = child_at(i);
455 if (AnimateChild(child, top, child->height()))
456 top += child->height() + between_items;
461 void MessageListView::AnimateNotificationsAboveTarget() {
462 int last_index = -1;
463 for (int i = child_count() - 1; i >= 0; --i) {
464 views::View* child = child_at(i);
465 if (!IsValidChild(child)) {
466 AnimateChild(child, child->y(), child->height());
467 } else if (reposition_top_ < 0 || child->y() < reposition_top_) {
468 // Find first notification above target (or all notifications if no
469 // target).
470 last_index = i;
471 break;
474 if (last_index >= 0) {
475 int between_items =
476 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
477 int bottom = (reposition_top_ > 0)
478 ? reposition_top_ + child_at(last_index)->height()
479 : GetHeightForWidth(width()) - GetInsets().bottom();
480 for (int i = last_index; i >= 0; --i) {
481 // Animate notifications above target downwards.
482 views::View* child = child_at(i);
483 if (AnimateChild(child, bottom - child->height(), child->height()))
484 bottom -= child->height() + between_items;
489 bool MessageListView::AnimateChild(views::View* child, int top, int height) {
490 gfx::Rect child_area = GetContentsBounds();
491 if (adding_views_.find(child) != adding_views_.end()) {
492 child->SetBounds(child_area.right(), top, child_area.width(), height);
493 animator_->AnimateViewTo(
494 child, gfx::Rect(child_area.x(), top, child_area.width(), height));
495 } else if (deleting_views_.find(child) != deleting_views_.end()) {
496 DCHECK(child->layer());
497 // No moves, but animate to fade-out.
498 animator_->AnimateViewTo(child, child->bounds());
499 deleted_when_done_.insert(child);
500 return false;
501 } else {
502 gfx::Rect target(child_area.x(), top, child_area.width(), height);
503 if (child->bounds().origin() != target.origin())
504 animator_->AnimateViewTo(child, target);
505 else
506 child->SetBoundsRect(target);
508 return true;
511 void MessageListView::AnimateClearingOneNotification() {
512 DCHECK(!clearing_all_views_.empty());
514 clear_all_started_ = true;
516 views::View* child = clearing_all_views_.front();
517 clearing_all_views_.pop_front();
519 // Slide from left to right.
520 gfx::Rect new_bounds = child->bounds();
521 new_bounds.set_x(new_bounds.right() + kMarginBetweenItems);
522 animator_->AnimateViewTo(child, new_bounds);
524 // Schedule to start sliding out next notification after a short delay.
525 if (!clearing_all_views_.empty()) {
526 base::MessageLoop::current()->PostDelayedTask(
527 FROM_HERE,
528 base::Bind(&MessageListView::AnimateClearingOneNotification,
529 weak_ptr_factory_.GetWeakPtr()),
530 base::TimeDelta::FromMilliseconds(
531 kAnimateClearingNextNotificationDelayMS));
535 // MessageCenterView ///////////////////////////////////////////////////////////
537 MessageCenterView::MessageCenterView(MessageCenter* message_center,
538 MessageCenterTray* tray,
539 int max_height,
540 bool initially_settings_visible,
541 bool top_down,
542 const base::string16& title)
543 : message_center_(message_center),
544 tray_(tray),
545 scroller_(NULL),
546 settings_view_(NULL),
547 button_bar_(NULL),
548 top_down_(top_down),
549 settings_visible_(initially_settings_visible),
550 source_view_(NULL),
551 source_height_(0),
552 target_view_(NULL),
553 target_height_(0),
554 is_closing_(false),
555 context_menu_controller_(new MessageViewContextMenuController(this)) {
556 message_center_->AddObserver(this);
557 set_notify_enter_exit_on_child(true);
558 set_background(views::Background::CreateSolidBackground(
559 kMessageCenterBackgroundColor));
561 NotifierSettingsProvider* notifier_settings_provider =
562 message_center_->GetNotifierSettingsProvider();
563 button_bar_ = new MessageCenterButtonBar(this,
564 message_center,
565 notifier_settings_provider,
566 initially_settings_visible,
567 title);
569 const int button_height = button_bar_->GetPreferredSize().height();
571 scroller_ = new views::ScrollView();
572 scroller_->ClipHeightTo(kMinScrollViewHeight, max_height - button_height);
573 scroller_->SetVerticalScrollBar(new views::OverlayScrollBar(false));
574 scroller_->set_background(
575 views::Background::CreateSolidBackground(kMessageCenterBackgroundColor));
577 scroller_->SetPaintToLayer(true);
578 scroller_->SetFillsBoundsOpaquely(false);
579 scroller_->layer()->SetMasksToBounds(true);
581 empty_list_view_.reset(new NoNotificationMessageView);
582 empty_list_view_->set_owned_by_client();
583 message_list_view_.reset(new MessageListView(this, top_down));
584 message_list_view_->set_owned_by_client();
586 // We want to swap the contents of the scroll view between the empty list
587 // view and the message list view, without constructing them afresh each
588 // time. So, since the scroll view deletes old contents each time you
589 // set the contents (regardless of the |owned_by_client_| setting) we need
590 // an intermediate view for the contents whose children we can swap in and
591 // out.
592 views::View* scroller_contents = new views::View();
593 scroller_contents->SetLayoutManager(new views::FillLayout());
594 scroller_contents->AddChildView(empty_list_view_.get());
595 scroller_->SetContents(scroller_contents);
597 settings_view_ = new NotifierSettingsView(notifier_settings_provider);
599 if (initially_settings_visible)
600 scroller_->SetVisible(false);
601 else
602 settings_view_->SetVisible(false);
604 AddChildView(scroller_);
605 AddChildView(settings_view_);
606 AddChildView(button_bar_);
609 MessageCenterView::~MessageCenterView() {
610 if (!is_closing_)
611 message_center_->RemoveObserver(this);
614 void MessageCenterView::SetNotifications(
615 const NotificationList::Notifications& notifications) {
616 if (is_closing_)
617 return;
619 notification_views_.clear();
621 int index = 0;
622 for (NotificationList::Notifications::const_iterator iter =
623 notifications.begin(); iter != notifications.end(); ++iter) {
624 AddNotificationAt(*(*iter), index++);
626 message_center_->DisplayedNotification(
627 (*iter)->id(), message_center::DISPLAY_SOURCE_MESSAGE_CENTER);
628 if (notification_views_.size() >= kMaxVisibleMessageCenterNotifications)
629 break;
632 NotificationsChanged();
633 scroller_->RequestFocus();
636 void MessageCenterView::SetSettingsVisible(bool visible) {
637 if (is_closing_)
638 return;
640 if (visible == settings_visible_)
641 return;
643 settings_visible_ = visible;
645 if (visible) {
646 source_view_ = scroller_;
647 target_view_ = settings_view_;
648 } else {
649 source_view_ = settings_view_;
650 target_view_ = scroller_;
652 source_height_ = source_view_->GetHeightForWidth(width());
653 target_height_ = target_view_->GetHeightForWidth(width());
655 gfx::MultiAnimation::Parts parts;
656 // First part: slide resize animation.
657 parts.push_back(gfx::MultiAnimation::Part(
658 (source_height_ == target_height_) ? 0 : kDefaultAnimationDurationMs,
659 gfx::Tween::EASE_OUT));
660 // Second part: fade-out the source_view.
661 if (source_view_->layer()) {
662 parts.push_back(gfx::MultiAnimation::Part(
663 kDefaultAnimationDurationMs, gfx::Tween::LINEAR));
664 } else {
665 parts.push_back(gfx::MultiAnimation::Part());
667 // Third part: fade-in the target_view.
668 if (target_view_->layer()) {
669 parts.push_back(gfx::MultiAnimation::Part(
670 kDefaultAnimationDurationMs, gfx::Tween::LINEAR));
671 target_view_->layer()->SetOpacity(0);
672 target_view_->SetVisible(true);
673 } else {
674 parts.push_back(gfx::MultiAnimation::Part());
676 settings_transition_animation_.reset(new gfx::MultiAnimation(
677 parts, base::TimeDelta::FromMicroseconds(1000000 / kDefaultFrameRateHz)));
678 settings_transition_animation_->set_delegate(this);
679 settings_transition_animation_->set_continuous(false);
680 settings_transition_animation_->Start();
682 button_bar_->SetBackArrowVisible(visible);
685 void MessageCenterView::ClearAllNotifications() {
686 if (is_closing_)
687 return;
689 scroller_->SetEnabled(false);
690 button_bar_->SetAllButtonsEnabled(false);
691 message_list_view_->ClearAllNotifications(scroller_->GetVisibleRect());
694 void MessageCenterView::OnAllNotificationsCleared() {
695 scroller_->SetEnabled(true);
696 button_bar_->SetAllButtonsEnabled(true);
697 button_bar_->SetCloseAllButtonEnabled(false);
698 message_center_->RemoveAllVisibleNotifications(true); // Action by user.
701 size_t MessageCenterView::NumMessageViewsForTest() const {
702 return message_list_view_->child_count();
705 void MessageCenterView::OnSettingsChanged() {
706 scroller_->InvalidateLayout();
707 PreferredSizeChanged();
708 Layout();
711 void MessageCenterView::SetIsClosing(bool is_closing) {
712 is_closing_ = is_closing;
713 if (is_closing)
714 message_center_->RemoveObserver(this);
715 else
716 message_center_->AddObserver(this);
719 void MessageCenterView::Layout() {
720 if (is_closing_)
721 return;
723 int button_height = button_bar_->GetHeightForWidth(width()) +
724 button_bar_->GetInsets().height();
725 // Skip unnecessary re-layout of contents during the resize animation.
726 bool animating = settings_transition_animation_ &&
727 settings_transition_animation_->is_animating();
728 if (animating && settings_transition_animation_->current_part_index() == 0) {
729 if (!top_down_) {
730 button_bar_->SetBounds(
731 0, height() - button_height, width(), button_height);
733 return;
736 scroller_->SetBounds(0,
737 top_down_ ? button_height : 0,
738 width(),
739 height() - button_height);
740 settings_view_->SetBounds(0,
741 top_down_ ? button_height : 0,
742 width(),
743 height() - button_height);
745 bool is_scrollable = false;
746 if (scroller_->visible())
747 is_scrollable = scroller_->height() < message_list_view_->height();
748 else
749 is_scrollable = settings_view_->IsScrollable();
751 if (!animating) {
752 if (is_scrollable) {
753 // Draw separator line on the top of the button bar if it is on the bottom
754 // or draw it at the bottom if the bar is on the top.
755 button_bar_->SetBorder(views::Border::CreateSolidSidedBorder(
756 top_down_ ? 0 : 1, 0, top_down_ ? 1 : 0, 0, kFooterDelimiterColor));
757 } else {
758 button_bar_->SetBorder(views::Border::CreateEmptyBorder(
759 top_down_ ? 0 : 1, 0, top_down_ ? 1 : 0, 0));
761 button_bar_->SchedulePaint();
763 button_bar_->SetBounds(0,
764 top_down_ ? 0 : height() - button_height,
765 width(),
766 button_height);
767 if (GetWidget())
768 GetWidget()->GetRootView()->SchedulePaint();
771 gfx::Size MessageCenterView::GetPreferredSize() const {
772 if (settings_transition_animation_ &&
773 settings_transition_animation_->is_animating()) {
774 int content_width = std::max(source_view_->GetPreferredSize().width(),
775 target_view_->GetPreferredSize().width());
776 int width = std::max(content_width,
777 button_bar_->GetPreferredSize().width());
778 return gfx::Size(width, GetHeightForWidth(width));
781 int width = 0;
782 for (int i = 0; i < child_count(); ++i) {
783 const views::View* child = child_at(0);
784 if (child->visible())
785 width = std::max(width, child->GetPreferredSize().width());
787 return gfx::Size(width, GetHeightForWidth(width));
790 int MessageCenterView::GetHeightForWidth(int width) const {
791 if (settings_transition_animation_ &&
792 settings_transition_animation_->is_animating()) {
793 int content_height = target_height_;
794 if (settings_transition_animation_->current_part_index() == 0) {
795 content_height = settings_transition_animation_->CurrentValueBetween(
796 source_height_, target_height_);
798 return button_bar_->GetHeightForWidth(width) + content_height;
801 int content_height = 0;
802 if (scroller_->visible())
803 content_height += scroller_->GetHeightForWidth(width);
804 else
805 content_height += settings_view_->GetHeightForWidth(width);
806 return button_bar_->GetHeightForWidth(width) +
807 button_bar_->GetInsets().height() + content_height;
810 bool MessageCenterView::OnMouseWheel(const ui::MouseWheelEvent& event) {
811 // Do not rely on the default scroll event handler of ScrollView because
812 // the scroll happens only when the focus is on the ScrollView. The
813 // notification center will allow the scrolling even when the focus is on
814 // the buttons.
815 if (scroller_->bounds().Contains(event.location()))
816 return scroller_->OnMouseWheel(event);
817 return views::View::OnMouseWheel(event);
820 void MessageCenterView::OnMouseExited(const ui::MouseEvent& event) {
821 if (is_closing_)
822 return;
824 message_list_view_->ResetRepositionSession();
825 NotificationsChanged();
828 void MessageCenterView::OnNotificationAdded(const std::string& id) {
829 int index = 0;
830 const NotificationList::Notifications& notifications =
831 message_center_->GetVisibleNotifications();
832 for (NotificationList::Notifications::const_iterator iter =
833 notifications.begin(); iter != notifications.end();
834 ++iter, ++index) {
835 if ((*iter)->id() == id) {
836 AddNotificationAt(*(*iter), index);
837 break;
839 if (notification_views_.size() >= kMaxVisibleMessageCenterNotifications)
840 break;
842 NotificationsChanged();
845 void MessageCenterView::OnNotificationRemoved(const std::string& id,
846 bool by_user) {
847 NotificationViewsMap::iterator view_iter = notification_views_.find(id);
848 if (view_iter == notification_views_.end())
849 return;
850 NotificationView* view = view_iter->second;
851 int index = message_list_view_->GetIndexOf(view);
852 DCHECK_LE(0, index);
853 if (by_user) {
854 message_list_view_->SetRepositionTarget(view->bounds());
855 // Moves the keyboard focus to the next notification if the removed
856 // notification is focused so that the user can dismiss notifications
857 // without re-focusing by tab key.
858 if (view->IsCloseButtonFocused() ||
859 view == GetFocusManager()->GetFocusedView()) {
860 views::View* next_focused_view = NULL;
861 if (message_list_view_->child_count() > index + 1)
862 next_focused_view = message_list_view_->child_at(index + 1);
863 else if (index > 0)
864 next_focused_view = message_list_view_->child_at(index - 1);
866 if (next_focused_view) {
867 if (view->IsCloseButtonFocused())
868 // Safe cast since all views in MessageListView are MessageViews.
869 static_cast<MessageView*>(
870 next_focused_view)->RequestFocusOnCloseButton();
871 else
872 next_focused_view->RequestFocus();
876 message_list_view_->RemoveNotification(view);
877 notification_views_.erase(view_iter);
878 NotificationsChanged();
881 void MessageCenterView::OnNotificationUpdated(const std::string& id) {
882 NotificationViewsMap::const_iterator view_iter = notification_views_.find(id);
883 if (view_iter == notification_views_.end())
884 return;
885 NotificationView* view = view_iter->second;
886 // TODO(dimich): add MessageCenter::GetVisibleNotificationById(id)
887 const NotificationList::Notifications& notifications =
888 message_center_->GetVisibleNotifications();
889 for (NotificationList::Notifications::const_iterator iter =
890 notifications.begin(); iter != notifications.end(); ++iter) {
891 if ((*iter)->id() == id) {
892 int old_width = view->width();
893 int old_height = view->GetHeightForWidth(old_width);
894 message_list_view_->UpdateNotification(view, **iter);
895 if (view->GetHeightForWidth(old_width) != old_height)
896 NotificationsChanged();
897 break;
902 void MessageCenterView::ClickOnNotification(
903 const std::string& notification_id) {
904 message_center_->ClickOnNotification(notification_id);
907 void MessageCenterView::RemoveNotification(const std::string& notification_id,
908 bool by_user) {
909 message_center_->RemoveNotification(notification_id, by_user);
912 scoped_ptr<ui::MenuModel> MessageCenterView::CreateMenuModel(
913 const NotifierId& notifier_id,
914 const base::string16& display_source) {
915 return tray_->CreateNotificationMenuModel(notifier_id, display_source);
918 bool MessageCenterView::HasClickedListener(const std::string& notification_id) {
919 return message_center_->HasClickedListener(notification_id);
922 void MessageCenterView::ClickOnNotificationButton(
923 const std::string& notification_id,
924 int button_index) {
925 message_center_->ClickOnNotificationButton(notification_id, button_index);
928 void MessageCenterView::AnimationEnded(const gfx::Animation* animation) {
929 DCHECK_EQ(animation, settings_transition_animation_.get());
931 Visibility visibility = target_view_ == settings_view_
932 ? VISIBILITY_SETTINGS
933 : VISIBILITY_MESSAGE_CENTER;
934 message_center_->SetVisibility(visibility);
936 source_view_->SetVisible(false);
937 target_view_->SetVisible(true);
938 if (source_view_->layer())
939 source_view_->layer()->SetOpacity(1.0);
940 if (target_view_->layer())
941 target_view_->layer()->SetOpacity(1.0);
942 settings_transition_animation_.reset();
943 PreferredSizeChanged();
944 Layout();
947 void MessageCenterView::AnimationProgressed(const gfx::Animation* animation) {
948 DCHECK_EQ(animation, settings_transition_animation_.get());
949 PreferredSizeChanged();
950 if (settings_transition_animation_->current_part_index() == 1 &&
951 source_view_->layer()) {
952 source_view_->layer()->SetOpacity(
953 1.0 - settings_transition_animation_->GetCurrentValue());
954 SchedulePaint();
955 } else if (settings_transition_animation_->current_part_index() == 2 &&
956 target_view_->layer()) {
957 target_view_->layer()->SetOpacity(
958 settings_transition_animation_->GetCurrentValue());
959 SchedulePaint();
963 void MessageCenterView::AnimationCanceled(const gfx::Animation* animation) {
964 DCHECK_EQ(animation, settings_transition_animation_.get());
965 AnimationEnded(animation);
968 void MessageCenterView::AddNotificationAt(const Notification& notification,
969 int index) {
970 NotificationView* view =
971 NotificationView::Create(this, notification, false); // Not top-level.
972 view->set_context_menu_controller(context_menu_controller_.get());
973 notification_views_[notification.id()] = view;
974 view->set_scroller(scroller_);
975 message_list_view_->AddNotificationAt(view, index);
978 void MessageCenterView::NotificationsChanged() {
979 bool no_message_views = notification_views_.empty();
981 // When the child view is removed from the hierarchy, its focus is cleared.
982 // In this case we want to save which view has focus so that the user can
983 // continue to interact with notifications in the order they were expecting.
984 views::FocusManager* focus_manager = scroller_->GetFocusManager();
985 View* focused_view = NULL;
986 // |focus_manager| can be NULL in tests.
987 if (focus_manager)
988 focused_view = focus_manager->GetFocusedView();
990 // All the children of this view are owned by |this|.
991 scroller_->contents()->RemoveAllChildViews(/*delete_children=*/false);
992 scroller_->contents()->AddChildView(
993 no_message_views ? empty_list_view_.get() : message_list_view_.get());
995 button_bar_->SetCloseAllButtonEnabled(!no_message_views);
996 scroller_->SetFocusable(!no_message_views);
998 if (focus_manager && focused_view)
999 focus_manager->SetFocusedView(focused_view);
1001 scroller_->InvalidateLayout();
1002 PreferredSizeChanged();
1003 Layout();
1006 void MessageCenterView::SetNotificationViewForTest(MessageView* view) {
1007 message_list_view_->AddNotificationAt(view, 0);
1010 } // namespace message_center