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