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.
4
5 #include "ui/message_center/views/message_center_view.h"
6
7 #include <list>
8 #include <map>
9
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/base/resource/resource_bundle.h"
17 #include "ui/gfx/animation/multi_animation.h"
18 #include "ui/gfx/animation/slide_animation.h"
19 #include "ui/gfx/canvas.h"
20 #include "ui/gfx/insets.h"
21 #include "ui/gfx/rect.h"
22 #include "ui/gfx/size.h"
23 #include "ui/message_center/message_center.h"
24 #include "ui/message_center/message_center_style.h"
25 #include "ui/message_center/message_center_tray.h"
26 #include "ui/message_center/message_center_types.h"
27 #include "ui/message_center/views/message_center_button_bar.h"
28 #include "ui/message_center/views/message_view.h"
29 #include "ui/message_center/views/message_view_context_menu_controller.h"
30 #include "ui/message_center/views/notification_view.h"
31 #include "ui/message_center/views/notifier_settings_view.h"
32 #include "ui/views/animation/bounds_animator.h"
33 #include "ui/views/animation/bounds_animator_observer.h"
34 #include "ui/views/background.h"
35 #include "ui/views/border.h"
36 #include "ui/views/controls/button/button.h"
37 #include "ui/views/controls/label.h"
38 #include "ui/views/controls/scroll_view.h"
39 #include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
40 #include "ui/views/layout/box_layout.h"
41 #include "ui/views/layout/fill_layout.h"
42 #include "ui/views/widget/widget.h"
43
44 namespace message_center {
45
46 namespace {
47
48 const SkColor kNoNotificationsTextColor = SkColorSetRGB(0xb4, 0xb4, 0xb4);
49 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
50 const SkColor kTransparentColor = SkColorSetARGB(0, 0, 0, 0);
51 #endif
52 const int kAnimateClearingNextNotificationDelayMS = 40;
53
54 const int kDefaultAnimationDurationMs = 120;
55 const int kDefaultFrameRateHz = 60;
56 } // namespace
57
58 class NoNotificationMessageView : public views::View {
59 public:
60 NoNotificationMessageView();
61 virtual ~NoNotificationMessageView();
62
63 // Overridden from views::View.
64 virtual gfx::Size GetPreferredSize() const OVERRIDE;
65 virtual int GetHeightForWidth(int width) const OVERRIDE;
66 virtual void Layout() OVERRIDE;
67
68 private:
69 views::Label* label_;
70
71 DISALLOW_COPY_AND_ASSIGN(NoNotificationMessageView);
72 };
73
NoNotificationMessageView()74 NoNotificationMessageView::NoNotificationMessageView() {
75 label_ = new views::Label(l10n_util::GetStringUTF16(
76 IDS_MESSAGE_CENTER_NO_MESSAGES));
77 label_->SetAutoColorReadabilityEnabled(false);
78 label_->SetEnabledColor(kNoNotificationsTextColor);
79 // Set transparent background to ensure that subpixel rendering
80 // is disabled. See crbug.com/169056
81 #if defined(OS_LINUX) && defined(OS_CHROMEOS)
82 label_->SetBackgroundColor(kTransparentColor);
83 #endif
84 AddChildView(label_);
85 }
86
~NoNotificationMessageView()87 NoNotificationMessageView::~NoNotificationMessageView() {
88 }
89
GetPreferredSize() const90 gfx::Size NoNotificationMessageView::GetPreferredSize() const {
91 return gfx::Size(kMinScrollViewHeight, label_->GetPreferredSize().width());
92 }
93
GetHeightForWidth(int width) const94 int NoNotificationMessageView::GetHeightForWidth(int width) const {
95 return kMinScrollViewHeight;
96 }
97
Layout()98 void NoNotificationMessageView::Layout() {
99 int text_height = label_->GetHeightForWidth(width());
100 int margin = (height() - text_height) / 2;
101 label_->SetBounds(0, margin, width(), text_height);
102 }
103
104 // Displays a list of messages for rich notifications. Functions as an array of
105 // MessageViews and animates them on transitions. It also supports
106 // repositioning.
107 class MessageListView : public views::View,
108 public views::BoundsAnimatorObserver {
109 public:
110 explicit MessageListView(MessageCenterView* message_center_view,
111 bool top_down);
112 virtual ~MessageListView();
113
114 void AddNotificationAt(MessageView* view, int i);
115 void RemoveNotification(MessageView* view);
116 void UpdateNotification(MessageView* view, const Notification& notification);
117 void SetRepositionTarget(const gfx::Rect& target_rect);
118 void ResetRepositionSession();
119 void ClearAllNotifications(const gfx::Rect& visible_scroll_rect);
120
121 protected:
122 // Overridden from views::View.
123 virtual void Layout() OVERRIDE;
124 virtual gfx::Size GetPreferredSize() const OVERRIDE;
125 virtual int GetHeightForWidth(int width) const OVERRIDE;
126 virtual void PaintChildren(gfx::Canvas* canvas,
127 const views::CullSet& cull_set) OVERRIDE;
128 virtual void ReorderChildLayers(ui::Layer* parent_layer) OVERRIDE;
129
130 // Overridden from views::BoundsAnimatorObserver.
131 virtual void OnBoundsAnimatorProgressed(
132 views::BoundsAnimator* animator) OVERRIDE;
133 virtual void OnBoundsAnimatorDone(views::BoundsAnimator* animator) OVERRIDE;
134
135 private:
136 bool IsValidChild(const views::View* child) const;
137 void DoUpdateIfPossible();
138
139 // Animates all notifications below target upwards to align with the top of
140 // the last closed notification.
141 void AnimateNotificationsBelowTarget();
142 // Animates all notifications above target downwards to align with the top of
143 // the last closed notification.
144 void AnimateNotificationsAboveTarget();
145
146 // Schedules animation for a child to the specified position. Returns false
147 // if |child| will disappear after the animation.
148 bool AnimateChild(views::View* child, int top, int height);
149
150 // Animate clearing one notification.
151 void AnimateClearingOneNotification();
message_center_view() const152 MessageCenterView* message_center_view() const {
153 return message_center_view_;
154 }
155
156 MessageCenterView* message_center_view_; // Weak reference.
157 // The top position of the reposition target rectangle.
158 int reposition_top_;
159 int fixed_height_;
160 bool has_deferred_task_;
161 bool clear_all_started_;
162 bool top_down_;
163 std::set<views::View*> adding_views_;
164 std::set<views::View*> deleting_views_;
165 std::set<views::View*> deleted_when_done_;
166 std::list<views::View*> clearing_all_views_;
167 scoped_ptr<views::BoundsAnimator> animator_;
168 base::WeakPtrFactory<MessageListView> weak_ptr_factory_;
169
170 DISALLOW_COPY_AND_ASSIGN(MessageListView);
171 };
172
MessageListView(MessageCenterView * message_center_view,bool top_down)173 MessageListView::MessageListView(MessageCenterView* message_center_view,
174 bool top_down)
175 : message_center_view_(message_center_view),
176 reposition_top_(-1),
177 fixed_height_(0),
178 has_deferred_task_(false),
179 clear_all_started_(false),
180 top_down_(top_down),
181 weak_ptr_factory_(this) {
182 views::BoxLayout* layout =
183 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1);
184 layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_FILL);
185 SetLayoutManager(layout);
186
187 // Set the margin to 0 for the layout. BoxLayout assumes the same margin
188 // for top and bottom, but the bottom margin here should be smaller
189 // because of the shadow of message view. Use an empty border instead
190 // to provide this margin.
191 gfx::Insets shadow_insets = MessageView::GetShadowInsets();
192 set_background(views::Background::CreateSolidBackground(
193 kMessageCenterBackgroundColor));
194 SetBorder(views::Border::CreateEmptyBorder(
195 top_down ? 0 : kMarginBetweenItems - shadow_insets.top(), /* top */
196 kMarginBetweenItems - shadow_insets.left(), /* left */
197 top_down ? kMarginBetweenItems - shadow_insets.bottom() : 0, /* bottom */
198 kMarginBetweenItems - shadow_insets.right() /* right */));
199 }
200
~MessageListView()201 MessageListView::~MessageListView() {
202 if (animator_.get())
203 animator_->RemoveObserver(this);
204 }
205
Layout()206 void MessageListView::Layout() {
207 if (animator_.get())
208 return;
209
210 gfx::Rect child_area = GetContentsBounds();
211 int top = child_area.y();
212 int between_items =
213 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
214
215 for (int i = 0; i < child_count(); ++i) {
216 views::View* child = child_at(i);
217 if (!child->visible())
218 continue;
219 int height = child->GetHeightForWidth(child_area.width());
220 child->SetBounds(child_area.x(), top, child_area.width(), height);
221 top += height + between_items;
222 }
223 }
224
AddNotificationAt(MessageView * view,int index)225 void MessageListView::AddNotificationAt(MessageView* view, int index) {
226 // |index| refers to a position in a subset of valid children. |real_index|
227 // in a list includes the invalid children, so we compute the real index by
228 // walking the list until |index| number of valid children are encountered,
229 // or to the end of the list.
230 int real_index = 0;
231 while (real_index < child_count()) {
232 if (IsValidChild(child_at(real_index))) {
233 --index;
234 if (index < 0)
235 break;
236 }
237 ++real_index;
238 }
239
240 AddChildViewAt(view, real_index);
241 if (GetContentsBounds().IsEmpty())
242 return;
243
244 adding_views_.insert(view);
245 DoUpdateIfPossible();
246 }
247
RemoveNotification(MessageView * view)248 void MessageListView::RemoveNotification(MessageView* view) {
249 DCHECK_EQ(view->parent(), this);
250 if (GetContentsBounds().IsEmpty()) {
251 delete view;
252 } else {
253 if (view->layer()) {
254 deleting_views_.insert(view);
255 } else {
256 if (animator_.get())
257 animator_->StopAnimatingView(view);
258 delete view;
259 }
260 DoUpdateIfPossible();
261 }
262 }
263
UpdateNotification(MessageView * view,const Notification & notification)264 void MessageListView::UpdateNotification(MessageView* view,
265 const Notification& notification) {
266 int index = GetIndexOf(view);
267 DCHECK_LE(0, index); // GetIndexOf is negative if not a child.
268
269 if (animator_.get())
270 animator_->StopAnimatingView(view);
271 if (deleting_views_.find(view) != deleting_views_.end())
272 deleting_views_.erase(view);
273 if (deleted_when_done_.find(view) != deleted_when_done_.end())
274 deleted_when_done_.erase(view);
275 view->UpdateWithNotification(notification);
276 DoUpdateIfPossible();
277 }
278
GetPreferredSize() const279 gfx::Size MessageListView::GetPreferredSize() const {
280 int width = 0;
281 for (int i = 0; i < child_count(); i++) {
282 const views::View* child = child_at(i);
283 if (IsValidChild(child))
284 width = std::max(width, child->GetPreferredSize().width());
285 }
286
287 return gfx::Size(width + GetInsets().width(),
288 GetHeightForWidth(width + GetInsets().width()));
289 }
290
GetHeightForWidth(int width) const291 int MessageListView::GetHeightForWidth(int width) const {
292 if (fixed_height_ > 0)
293 return fixed_height_;
294
295 width -= GetInsets().width();
296 int height = 0;
297 int padding = 0;
298 for (int i = 0; i < child_count(); ++i) {
299 const views::View* child = child_at(i);
300 if (!IsValidChild(child))
301 continue;
302 height += child->GetHeightForWidth(width) + padding;
303 padding = kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
304 }
305
306 return height + GetInsets().height();
307 }
308
PaintChildren(gfx::Canvas * canvas,const views::CullSet & cull_set)309 void MessageListView::PaintChildren(gfx::Canvas* canvas,
310 const views::CullSet& cull_set) {
311 // Paint in the inversed order. Otherwise upper notification may be
312 // hidden by the lower one.
313 for (int i = child_count() - 1; i >= 0; --i) {
314 if (!child_at(i)->layer())
315 child_at(i)->Paint(canvas, cull_set);
316 }
317 }
318
ReorderChildLayers(ui::Layer * parent_layer)319 void MessageListView::ReorderChildLayers(ui::Layer* parent_layer) {
320 // Reorder children to stack the last child layer at the top. Otherwise
321 // upper notification may be hidden by the lower one.
322 for (int i = 0; i < child_count(); ++i) {
323 if (child_at(i)->layer())
324 parent_layer->StackAtBottom(child_at(i)->layer());
325 }
326 }
327
SetRepositionTarget(const gfx::Rect & target)328 void MessageListView::SetRepositionTarget(const gfx::Rect& target) {
329 reposition_top_ = target.y();
330 fixed_height_ = GetHeightForWidth(width());
331 }
332
ResetRepositionSession()333 void MessageListView::ResetRepositionSession() {
334 // Don't call DoUpdateIfPossible(), but let Layout() do the task without
335 // animation. Reset will cause the change of the bubble size itself, and
336 // animation from the old location will look weird.
337 if (reposition_top_ >= 0 && animator_.get()) {
338 has_deferred_task_ = false;
339 // cancel cause OnBoundsAnimatorDone which deletes |deleted_when_done_|.
340 animator_->Cancel();
341 STLDeleteContainerPointers(deleting_views_.begin(), deleting_views_.end());
342 deleting_views_.clear();
343 adding_views_.clear();
344 animator_.reset();
345 }
346
347 reposition_top_ = -1;
348 fixed_height_ = 0;
349 }
350
ClearAllNotifications(const gfx::Rect & visible_scroll_rect)351 void MessageListView::ClearAllNotifications(
352 const gfx::Rect& visible_scroll_rect) {
353 for (int i = 0; i < child_count(); ++i) {
354 views::View* child = child_at(i);
355 if (!child->visible())
356 continue;
357 if (gfx::IntersectRects(child->bounds(), visible_scroll_rect).IsEmpty())
358 continue;
359 clearing_all_views_.push_back(child);
360 }
361 DoUpdateIfPossible();
362 }
363
OnBoundsAnimatorProgressed(views::BoundsAnimator * animator)364 void MessageListView::OnBoundsAnimatorProgressed(
365 views::BoundsAnimator* animator) {
366 DCHECK_EQ(animator_.get(), animator);
367 for (std::set<views::View*>::iterator iter = deleted_when_done_.begin();
368 iter != deleted_when_done_.end(); ++iter) {
369 const gfx::SlideAnimation* animation = animator->GetAnimationForView(*iter);
370 if (animation)
371 (*iter)->layer()->SetOpacity(animation->CurrentValueBetween(1.0, 0.0));
372 }
373 }
374
OnBoundsAnimatorDone(views::BoundsAnimator * animator)375 void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
376 STLDeleteContainerPointers(
377 deleted_when_done_.begin(), deleted_when_done_.end());
378 deleted_when_done_.clear();
379
380 if (clear_all_started_) {
381 clear_all_started_ = false;
382 message_center_view()->OnAllNotificationsCleared();
383 }
384
385 if (has_deferred_task_) {
386 has_deferred_task_ = false;
387 DoUpdateIfPossible();
388 }
389
390 if (GetWidget())
391 GetWidget()->SynthesizeMouseMoveEvent();
392 }
393
IsValidChild(const views::View * child) const394 bool MessageListView::IsValidChild(const views::View* child) const {
395 return child->visible() &&
396 deleting_views_.find(const_cast<views::View*>(child)) ==
397 deleting_views_.end() &&
398 deleted_when_done_.find(const_cast<views::View*>(child)) ==
399 deleted_when_done_.end();
400 }
401
DoUpdateIfPossible()402 void MessageListView::DoUpdateIfPossible() {
403 gfx::Rect child_area = GetContentsBounds();
404 if (child_area.IsEmpty())
405 return;
406
407 if (animator_.get() && animator_->IsAnimating()) {
408 has_deferred_task_ = true;
409 return;
410 }
411
412 if (!animator_.get()) {
413 animator_.reset(new views::BoundsAnimator(this));
414 animator_->AddObserver(this);
415 }
416
417 if (!clearing_all_views_.empty()) {
418 AnimateClearingOneNotification();
419 return;
420 }
421
422 if (top_down_)
423 AnimateNotificationsBelowTarget();
424 else
425 AnimateNotificationsAboveTarget();
426
427 adding_views_.clear();
428 deleting_views_.clear();
429 }
430
AnimateNotificationsBelowTarget()431 void MessageListView::AnimateNotificationsBelowTarget() {
432 int last_index = -1;
433 for (int i = 0; i < child_count(); ++i) {
434 views::View* child = child_at(i);
435 if (!IsValidChild(child)) {
436 AnimateChild(child, child->y(), child->height());
437 } else if (reposition_top_ < 0 || child->y() > reposition_top_) {
438 // Find first notification below target (or all notifications if no
439 // target).
440 last_index = i;
441 break;
442 }
443 }
444 if (last_index > 0) {
445 int between_items =
446 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
447 int top = (reposition_top_ > 0) ? reposition_top_ : GetInsets().top();
448
449 for (int i = last_index; i < child_count(); ++i) {
450 // Animate notifications below target upwards.
451 views::View* child = child_at(i);
452 if (AnimateChild(child, top, child->height()))
453 top += child->height() + between_items;
454 }
455 }
456 }
457
AnimateNotificationsAboveTarget()458 void MessageListView::AnimateNotificationsAboveTarget() {
459 int last_index = -1;
460 for (int i = child_count() - 1; i >= 0; --i) {
461 views::View* child = child_at(i);
462 if (!IsValidChild(child)) {
463 AnimateChild(child, child->y(), child->height());
464 } else if (reposition_top_ < 0 || child->y() < reposition_top_) {
465 // Find first notification above target (or all notifications if no
466 // target).
467 last_index = i;
468 break;
469 }
470 }
471 if (last_index >= 0) {
472 int between_items =
473 kMarginBetweenItems - MessageView::GetShadowInsets().bottom();
474 int bottom = (reposition_top_ > 0)
475 ? reposition_top_ + child_at(last_index)->height()
476 : GetHeightForWidth(width()) - GetInsets().bottom();
477 for (int i = last_index; i >= 0; --i) {
478 // Animate notifications above target downwards.
479 views::View* child = child_at(i);
480 if (AnimateChild(child, bottom - child->height(), child->height()))
481 bottom -= child->height() + between_items;
482 }
483 }
484 }
485
AnimateChild(views::View * child,int top,int height)486 bool MessageListView::AnimateChild(views::View* child, int top, int height) {
487 gfx::Rect child_area = GetContentsBounds();
488 if (adding_views_.find(child) != adding_views_.end()) {
489 child->SetBounds(child_area.right(), top, child_area.width(), height);
490 animator_->AnimateViewTo(
491 child, gfx::Rect(child_area.x(), top, child_area.width(), height));
492 } else if (deleting_views_.find(child) != deleting_views_.end()) {
493 DCHECK(child->layer());
494 // No moves, but animate to fade-out.
495 animator_->AnimateViewTo(child, child->bounds());
496 deleted_when_done_.insert(child);
497 return false;
498 } else {
499 gfx::Rect target(child_area.x(), top, child_area.width(), height);
500 if (child->bounds().origin() != target.origin())
501 animator_->AnimateViewTo(child, target);
502 else
503 child->SetBoundsRect(target);
504 }
505 return true;
506 }
507
AnimateClearingOneNotification()508 void MessageListView::AnimateClearingOneNotification() {
509 DCHECK(!clearing_all_views_.empty());
510
511 clear_all_started_ = true;
512
513 views::View* child = clearing_all_views_.front();
514 clearing_all_views_.pop_front();
515
516 // Slide from left to right.
517 gfx::Rect new_bounds = child->bounds();
518 new_bounds.set_x(new_bounds.right() + kMarginBetweenItems);
519 animator_->AnimateViewTo(child, new_bounds);
520
521 // Schedule to start sliding out next notification after a short delay.
522 if (!clearing_all_views_.empty()) {
523 base::MessageLoop::current()->PostDelayedTask(
524 FROM_HERE,
525 base::Bind(&MessageListView::AnimateClearingOneNotification,
526 weak_ptr_factory_.GetWeakPtr()),
527 base::TimeDelta::FromMilliseconds(
528 kAnimateClearingNextNotificationDelayMS));
529 }
530 }
531
532 // MessageCenterView ///////////////////////////////////////////////////////////
533
MessageCenterView(MessageCenter * message_center,MessageCenterTray * tray,int max_height,bool initially_settings_visible,bool top_down,const base::string16 & title)534 MessageCenterView::MessageCenterView(MessageCenter* message_center,
535 MessageCenterTray* tray,
536 int max_height,
537 bool initially_settings_visible,
538 bool top_down,
539 const base::string16& title)
540 : message_center_(message_center),
541 tray_(tray),
542 scroller_(NULL),
543 settings_view_(NULL),
544 button_bar_(NULL),
545 top_down_(top_down),
546 settings_visible_(initially_settings_visible),
547 source_view_(NULL),
548 source_height_(0),
549 target_view_(NULL),
550 target_height_(0),
551 is_closing_(false),
552 context_menu_controller_(new MessageViewContextMenuController(this)) {
553 message_center_->AddObserver(this);
554 set_notify_enter_exit_on_child(true);
555 set_background(views::Background::CreateSolidBackground(
556 kMessageCenterBackgroundColor));
557
558 NotifierSettingsProvider* notifier_settings_provider =
559 message_center_->GetNotifierSettingsProvider();
560 button_bar_ = new MessageCenterButtonBar(this,
561 message_center,
562 notifier_settings_provider,
563 initially_settings_visible,
564 title);
565
566 const int button_height = button_bar_->GetPreferredSize().height();
567
568 scroller_ = new views::ScrollView();
569 scroller_->ClipHeightTo(kMinScrollViewHeight, max_height - button_height);
570 scroller_->SetVerticalScrollBar(new views::OverlayScrollBar(false));
571 scroller_->set_background(
572 views::Background::CreateSolidBackground(kMessageCenterBackgroundColor));
573
574 scroller_->SetPaintToLayer(true);
575 scroller_->SetFillsBoundsOpaquely(false);
576 scroller_->layer()->SetMasksToBounds(true);
577
578 empty_list_view_.reset(new NoNotificationMessageView);
579 empty_list_view_->set_owned_by_client();
580 message_list_view_.reset(new MessageListView(this, top_down));
581 message_list_view_->set_owned_by_client();
582
583 // We want to swap the contents of the scroll view between the empty list
584 // view and the message list view, without constructing them afresh each
585 // time. So, since the scroll view deletes old contents each time you
586 // set the contents (regardless of the |owned_by_client_| setting) we need
587 // an intermediate view for the contents whose children we can swap in and
588 // out.
589 views::View* scroller_contents = new views::View();
590 scroller_contents->SetLayoutManager(new views::FillLayout());
591 scroller_contents->AddChildView(empty_list_view_.get());
592 scroller_->SetContents(scroller_contents);
593
594 settings_view_ = new NotifierSettingsView(notifier_settings_provider);
595
596 if (initially_settings_visible)
597 scroller_->SetVisible(false);
598 else
599 settings_view_->SetVisible(false);
600
601 AddChildView(scroller_);
602 AddChildView(settings_view_);
603 AddChildView(button_bar_);
604 }
605
~MessageCenterView()606 MessageCenterView::~MessageCenterView() {
607 if (!is_closing_)
608 message_center_->RemoveObserver(this);
609 }
610
SetNotifications(const NotificationList::Notifications & notifications)611 void MessageCenterView::SetNotifications(
612 const NotificationList::Notifications& notifications) {
613 if (is_closing_)
614 return;
615
616 notification_views_.clear();
617
618 int index = 0;
619 for (NotificationList::Notifications::const_iterator iter =
620 notifications.begin(); iter != notifications.end(); ++iter) {
621 AddNotificationAt(*(*iter), index++);
622
623 message_center_->DisplayedNotification(
624 (*iter)->id(), message_center::DISPLAY_SOURCE_MESSAGE_CENTER);
625 if (notification_views_.size() >= kMaxVisibleMessageCenterNotifications)
626 break;
627 }
628
629 NotificationsChanged();
630 scroller_->RequestFocus();
631 }
632
SetSettingsVisible(bool visible)633 void MessageCenterView::SetSettingsVisible(bool visible) {
634 if (is_closing_)
635 return;
636
637 if (visible == settings_visible_)
638 return;
639
640 settings_visible_ = visible;
641
642 if (visible) {
643 source_view_ = scroller_;
644 target_view_ = settings_view_;
645 } else {
646 source_view_ = settings_view_;
647 target_view_ = scroller_;
648 }
649 source_height_ = source_view_->GetHeightForWidth(width());
650 target_height_ = target_view_->GetHeightForWidth(width());
651
652 gfx::MultiAnimation::Parts parts;
653 // First part: slide resize animation.
654 parts.push_back(gfx::MultiAnimation::Part(
655 (source_height_ == target_height_) ? 0 : kDefaultAnimationDurationMs,
656 gfx::Tween::EASE_OUT));
657 // Second part: fade-out the source_view.
658 if (source_view_->layer()) {
659 parts.push_back(gfx::MultiAnimation::Part(
660 kDefaultAnimationDurationMs, gfx::Tween::LINEAR));
661 } else {
662 parts.push_back(gfx::MultiAnimation::Part());
663 }
664 // Third part: fade-in the target_view.
665 if (target_view_->layer()) {
666 parts.push_back(gfx::MultiAnimation::Part(
667 kDefaultAnimationDurationMs, gfx::Tween::LINEAR));
668 target_view_->layer()->SetOpacity(0);
669 target_view_->SetVisible(true);
670 } else {
671 parts.push_back(gfx::MultiAnimation::Part());
672 }
673 settings_transition_animation_.reset(new gfx::MultiAnimation(
674 parts, base::TimeDelta::FromMicroseconds(1000000 / kDefaultFrameRateHz)));
675 settings_transition_animation_->set_delegate(this);
676 settings_transition_animation_->set_continuous(false);
677 settings_transition_animation_->Start();
678
679 button_bar_->SetBackArrowVisible(visible);
680 }
681
ClearAllNotifications()682 void MessageCenterView::ClearAllNotifications() {
683 if (is_closing_)
684 return;
685
686 scroller_->SetEnabled(false);
687 button_bar_->SetAllButtonsEnabled(false);
688 message_list_view_->ClearAllNotifications(scroller_->GetVisibleRect());
689 }
690
OnAllNotificationsCleared()691 void MessageCenterView::OnAllNotificationsCleared() {
692 scroller_->SetEnabled(true);
693 button_bar_->SetAllButtonsEnabled(true);
694 button_bar_->SetCloseAllButtonEnabled(false);
695 message_center_->RemoveAllVisibleNotifications(true); // Action by user.
696 }
697
NumMessageViewsForTest() const698 size_t MessageCenterView::NumMessageViewsForTest() const {
699 return message_list_view_->child_count();
700 }
701
OnSettingsChanged()702 void MessageCenterView::OnSettingsChanged() {
703 scroller_->InvalidateLayout();
704 PreferredSizeChanged();
705 Layout();
706 }
707
SetIsClosing(bool is_closing)708 void MessageCenterView::SetIsClosing(bool is_closing) {
709 is_closing_ = is_closing;
710 if (is_closing)
711 message_center_->RemoveObserver(this);
712 else
713 message_center_->AddObserver(this);
714 }
715
Layout()716 void MessageCenterView::Layout() {
717 if (is_closing_)
718 return;
719
720 int button_height = button_bar_->GetHeightForWidth(width()) +
721 button_bar_->GetInsets().height();
722 // Skip unnecessary re-layout of contents during the resize animation.
723 bool animating = settings_transition_animation_ &&
724 settings_transition_animation_->is_animating();
725 if (animating && settings_transition_animation_->current_part_index() == 0) {
726 if (!top_down_) {
727 button_bar_->SetBounds(
728 0, height() - button_height, width(), button_height);
729 }
730 return;
731 }
732
733 scroller_->SetBounds(0,
734 top_down_ ? button_height : 0,
735 width(),
736 height() - button_height);
737 settings_view_->SetBounds(0,
738 top_down_ ? button_height : 0,
739 width(),
740 height() - button_height);
741
742 bool is_scrollable = false;
743 if (scroller_->visible())
744 is_scrollable = scroller_->height() < message_list_view_->height();
745 else
746 is_scrollable = settings_view_->IsScrollable();
747
748 if (!animating) {
749 if (is_scrollable) {
750 // Draw separator line on the top of the button bar if it is on the bottom
751 // or draw it at the bottom if the bar is on the top.
752 button_bar_->SetBorder(views::Border::CreateSolidSidedBorder(
753 top_down_ ? 0 : 1, 0, top_down_ ? 1 : 0, 0, kFooterDelimiterColor));
754 } else {
755 button_bar_->SetBorder(views::Border::CreateEmptyBorder(
756 top_down_ ? 0 : 1, 0, top_down_ ? 1 : 0, 0));
757 }
758 button_bar_->SchedulePaint();
759 }
760 button_bar_->SetBounds(0,
761 top_down_ ? 0 : height() - button_height,
762 width(),
763 button_height);
764 if (GetWidget())
765 GetWidget()->GetRootView()->SchedulePaint();
766 }
767
GetPreferredSize() const768 gfx::Size MessageCenterView::GetPreferredSize() const {
769 if (settings_transition_animation_ &&
770 settings_transition_animation_->is_animating()) {
771 int content_width = std::max(source_view_->GetPreferredSize().width(),
772 target_view_->GetPreferredSize().width());
773 int width = std::max(content_width,
774 button_bar_->GetPreferredSize().width());
775 return gfx::Size(width, GetHeightForWidth(width));
776 }
777
778 int width = 0;
779 for (int i = 0; i < child_count(); ++i) {
780 const views::View* child = child_at(0);
781 if (child->visible())
782 width = std::max(width, child->GetPreferredSize().width());
783 }
784 return gfx::Size(width, GetHeightForWidth(width));
785 }
786
GetHeightForWidth(int width) const787 int MessageCenterView::GetHeightForWidth(int width) const {
788 if (settings_transition_animation_ &&
789 settings_transition_animation_->is_animating()) {
790 int content_height = target_height_;
791 if (settings_transition_animation_->current_part_index() == 0) {
792 content_height = settings_transition_animation_->CurrentValueBetween(
793 source_height_, target_height_);
794 }
795 return button_bar_->GetHeightForWidth(width) + content_height;
796 }
797
798 int content_height = 0;
799 if (scroller_->visible())
800 content_height += scroller_->GetHeightForWidth(width);
801 else
802 content_height += settings_view_->GetHeightForWidth(width);
803 return button_bar_->GetHeightForWidth(width) +
804 button_bar_->GetInsets().height() + content_height;
805 }
806
OnMouseWheel(const ui::MouseWheelEvent & event)807 bool MessageCenterView::OnMouseWheel(const ui::MouseWheelEvent& event) {
808 // Do not rely on the default scroll event handler of ScrollView because
809 // the scroll happens only when the focus is on the ScrollView. The
810 // notification center will allow the scrolling even when the focus is on
811 // the buttons.
812 if (scroller_->bounds().Contains(event.location()))
813 return scroller_->OnMouseWheel(event);
814 return views::View::OnMouseWheel(event);
815 }
816
OnMouseExited(const ui::MouseEvent & event)817 void MessageCenterView::OnMouseExited(const ui::MouseEvent& event) {
818 if (is_closing_)
819 return;
820
821 message_list_view_->ResetRepositionSession();
822 NotificationsChanged();
823 }
824
OnNotificationAdded(const std::string & id)825 void MessageCenterView::OnNotificationAdded(const std::string& id) {
826 int index = 0;
827 const NotificationList::Notifications& notifications =
828 message_center_->GetVisibleNotifications();
829 for (NotificationList::Notifications::const_iterator iter =
830 notifications.begin(); iter != notifications.end();
831 ++iter, ++index) {
832 if ((*iter)->id() == id) {
833 AddNotificationAt(*(*iter), index);
834 break;
835 }
836 if (notification_views_.size() >= kMaxVisibleMessageCenterNotifications)
837 break;
838 }
839 NotificationsChanged();
840 }
841
OnNotificationRemoved(const std::string & id,bool by_user)842 void MessageCenterView::OnNotificationRemoved(const std::string& id,
843 bool by_user) {
844 NotificationViewsMap::iterator view_iter = notification_views_.find(id);
845 if (view_iter == notification_views_.end())
846 return;
847 NotificationView* view = view_iter->second;
848 int index = message_list_view_->GetIndexOf(view);
849 DCHECK_LE(0, index);
850 if (by_user) {
851 message_list_view_->SetRepositionTarget(view->bounds());
852 // Moves the keyboard focus to the next notification if the removed
853 // notification is focused so that the user can dismiss notifications
854 // without re-focusing by tab key.
855 if (view->IsCloseButtonFocused() ||
856 view == GetFocusManager()->GetFocusedView()) {
857 views::View* next_focused_view = NULL;
858 if (message_list_view_->child_count() > index + 1)
859 next_focused_view = message_list_view_->child_at(index + 1);
860 else if (index > 0)
861 next_focused_view = message_list_view_->child_at(index - 1);
862
863 if (next_focused_view) {
864 if (view->IsCloseButtonFocused())
865 // Safe cast since all views in MessageListView are MessageViews.
866 static_cast<MessageView*>(
867 next_focused_view)->RequestFocusOnCloseButton();
868 else
869 next_focused_view->RequestFocus();
870 }
871 }
872 }
873 message_list_view_->RemoveNotification(view);
874 notification_views_.erase(view_iter);
875 NotificationsChanged();
876 }
877
OnNotificationUpdated(const std::string & id)878 void MessageCenterView::OnNotificationUpdated(const std::string& id) {
879 NotificationViewsMap::const_iterator view_iter = notification_views_.find(id);
880 if (view_iter == notification_views_.end())
881 return;
882 NotificationView* view = view_iter->second;
883 // TODO(dimich): add MessageCenter::GetVisibleNotificationById(id)
884 const NotificationList::Notifications& notifications =
885 message_center_->GetVisibleNotifications();
886 for (NotificationList::Notifications::const_iterator iter =
887 notifications.begin(); iter != notifications.end(); ++iter) {
888 if ((*iter)->id() == id) {
889 int old_width = view->width();
890 int old_height = view->GetHeightForWidth(old_width);
891 message_list_view_->UpdateNotification(view, **iter);
892 if (view->GetHeightForWidth(old_width) != old_height)
893 NotificationsChanged();
894 break;
895 }
896 }
897 }
898
ClickOnNotification(const std::string & notification_id)899 void MessageCenterView::ClickOnNotification(
900 const std::string& notification_id) {
901 message_center_->ClickOnNotification(notification_id);
902 }
903
RemoveNotification(const std::string & notification_id,bool by_user)904 void MessageCenterView::RemoveNotification(const std::string& notification_id,
905 bool by_user) {
906 message_center_->RemoveNotification(notification_id, by_user);
907 }
908
CreateMenuModel(const NotifierId & notifier_id,const base::string16 & display_source)909 scoped_ptr<ui::MenuModel> MessageCenterView::CreateMenuModel(
910 const NotifierId& notifier_id,
911 const base::string16& display_source) {
912 return tray_->CreateNotificationMenuModel(notifier_id, display_source);
913 }
914
HasClickedListener(const std::string & notification_id)915 bool MessageCenterView::HasClickedListener(const std::string& notification_id) {
916 return message_center_->HasClickedListener(notification_id);
917 }
918
ClickOnNotificationButton(const std::string & notification_id,int button_index)919 void MessageCenterView::ClickOnNotificationButton(
920 const std::string& notification_id,
921 int button_index) {
922 message_center_->ClickOnNotificationButton(notification_id, button_index);
923 }
924
AnimationEnded(const gfx::Animation * animation)925 void MessageCenterView::AnimationEnded(const gfx::Animation* animation) {
926 DCHECK_EQ(animation, settings_transition_animation_.get());
927
928 Visibility visibility = target_view_ == settings_view_
929 ? VISIBILITY_SETTINGS
930 : VISIBILITY_MESSAGE_CENTER;
931 message_center_->SetVisibility(visibility);
932
933 source_view_->SetVisible(false);
934 target_view_->SetVisible(true);
935 if (source_view_->layer())
936 source_view_->layer()->SetOpacity(1.0);
937 if (target_view_->layer())
938 target_view_->layer()->SetOpacity(1.0);
939 settings_transition_animation_.reset();
940 PreferredSizeChanged();
941 Layout();
942 }
943
AnimationProgressed(const gfx::Animation * animation)944 void MessageCenterView::AnimationProgressed(const gfx::Animation* animation) {
945 DCHECK_EQ(animation, settings_transition_animation_.get());
946 PreferredSizeChanged();
947 if (settings_transition_animation_->current_part_index() == 1 &&
948 source_view_->layer()) {
949 source_view_->layer()->SetOpacity(
950 1.0 - settings_transition_animation_->GetCurrentValue());
951 SchedulePaint();
952 } else if (settings_transition_animation_->current_part_index() == 2 &&
953 target_view_->layer()) {
954 target_view_->layer()->SetOpacity(
955 settings_transition_animation_->GetCurrentValue());
956 SchedulePaint();
957 }
958 }
959
AnimationCanceled(const gfx::Animation * animation)960 void MessageCenterView::AnimationCanceled(const gfx::Animation* animation) {
961 DCHECK_EQ(animation, settings_transition_animation_.get());
962 AnimationEnded(animation);
963 }
964
AddNotificationAt(const Notification & notification,int index)965 void MessageCenterView::AddNotificationAt(const Notification& notification,
966 int index) {
967 NotificationView* view =
968 NotificationView::Create(this, notification, false); // Not top-level.
969 view->set_context_menu_controller(context_menu_controller_.get());
970 notification_views_[notification.id()] = view;
971 view->set_scroller(scroller_);
972 message_list_view_->AddNotificationAt(view, index);
973 }
974
NotificationsChanged()975 void MessageCenterView::NotificationsChanged() {
976 bool no_message_views = notification_views_.empty();
977
978 // When the child view is removed from the hierarchy, its focus is cleared.
979 // In this case we want to save which view has focus so that the user can
980 // continue to interact with notifications in the order they were expecting.
981 views::FocusManager* focus_manager = scroller_->GetFocusManager();
982 View* focused_view = NULL;
983 // |focus_manager| can be NULL in tests.
984 if (focus_manager)
985 focused_view = focus_manager->GetFocusedView();
986
987 // All the children of this view are owned by |this|.
988 scroller_->contents()->RemoveAllChildViews(/*delete_children=*/false);
989 scroller_->contents()->AddChildView(
990 no_message_views ? empty_list_view_.get() : message_list_view_.get());
991
992 button_bar_->SetCloseAllButtonEnabled(!no_message_views);
993 scroller_->SetFocusable(!no_message_views);
994
995 if (focus_manager && focused_view)
996 focus_manager->SetFocusedView(focused_view);
997
998 scroller_->InvalidateLayout();
999 PreferredSizeChanged();
1000 Layout();
1001 }
1002
SetNotificationViewForTest(MessageView * view)1003 void MessageCenterView::SetNotificationViewForTest(MessageView* view) {
1004 message_list_view_->AddNotificationAt(view, 0);
1005 }
1006
1007 } // namespace message_center
1008