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/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" 44 45 namespace message_center { 46 47 namespace { 48 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; 54 55 const int kDefaultAnimationDurationMs = 120; 56 const int kDefaultFrameRateHz = 60; 57 } // namespace 58 59 class NoNotificationMessageView : public views::View { 60 public: 61 NoNotificationMessageView(); 62 virtual ~NoNotificationMessageView(); 63 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; 68 69 private: 70 views::Label* label_; 71 72 DISALLOW_COPY_AND_ASSIGN(NoNotificationMessageView); 73 }; 74 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_); 86 } 87 88 NoNotificationMessageView::~NoNotificationMessageView() { 89 } 90 91 gfx::Size NoNotificationMessageView::GetPreferredSize() const { 92 return gfx::Size(kMinScrollViewHeight, label_->GetPreferredSize().width()); 93 } 94 95 int NoNotificationMessageView::GetHeightForWidth(int width) const { 96 return kMinScrollViewHeight; 97 } 98 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); 103 } 104 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(); 114 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); 121 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; 130 131 // Overridden from views::BoundsAnimatorObserver. 132 virtual void OnBoundsAnimatorProgressed( 133 views::BoundsAnimator* animator) OVERRIDE; 134 virtual void OnBoundsAnimatorDone(views::BoundsAnimator* animator) OVERRIDE; 135 136 private: 137 bool IsValidChild(const views::View* child) const; 138 void DoUpdateIfPossible(); 139 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(); 146 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); 150 151 // Animate clearing one notification. 152 void AnimateClearingOneNotification(); 153 MessageCenterView* message_center_view() const { 154 return message_center_view_; 155 } 156 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_; 170 171 DISALLOW_COPY_AND_ASSIGN(MessageListView); 172 }; 173 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); 187 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 */)); 200 } 201 202 MessageListView::~MessageListView() { 203 if (animator_.get()) 204 animator_->RemoveObserver(this); 205 } 206 207 void MessageListView::Layout() { 208 if (animator_.get()) 209 return; 210 211 gfx::Rect child_area = GetContentsBounds(); 212 int top = child_area.y(); 213 int between_items = 214 kMarginBetweenItems - MessageView::GetShadowInsets().bottom(); 215 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; 223 } 224 } 225 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; 237 } 238 ++real_index; 239 } 240 241 AddChildViewAt(view, real_index); 242 if (GetContentsBounds().IsEmpty()) 243 return; 244 245 adding_views_.insert(view); 246 DoUpdateIfPossible(); 247 } 248 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; 260 } 261 DoUpdateIfPossible(); 262 } 263 } 264 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. 269 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(); 278 } 279 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()); 286 } 287 288 return gfx::Size(width + GetInsets().width(), 289 GetHeightForWidth(width + GetInsets().width())); 290 } 291 292 int MessageListView::GetHeightForWidth(int width) const { 293 if (fixed_height_ > 0) 294 return fixed_height_; 295 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(); 305 } 306 307 return height + GetInsets().height(); 308 } 309 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); 317 } 318 } 319 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()); 326 } 327 } 328 329 void MessageListView::SetRepositionTarget(const gfx::Rect& target) { 330 reposition_top_ = target.y(); 331 fixed_height_ = GetHeightForWidth(width()); 332 } 333 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(); 346 } 347 348 reposition_top_ = -1; 349 fixed_height_ = 0; 350 } 351 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); 361 } 362 DoUpdateIfPossible(); 363 } 364 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)); 373 } 374 } 375 376 void MessageListView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) { 377 STLDeleteContainerPointers( 378 deleted_when_done_.begin(), deleted_when_done_.end()); 379 deleted_when_done_.clear(); 380 381 if (clear_all_started_) { 382 clear_all_started_ = false; 383 message_center_view()->OnAllNotificationsCleared(); 384 } 385 386 if (has_deferred_task_) { 387 has_deferred_task_ = false; 388 DoUpdateIfPossible(); 389 } 390 391 if (GetWidget()) 392 GetWidget()->SynthesizeMouseMoveEvent(); 393 } 394 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(); 401 } 402 403 void MessageListView::DoUpdateIfPossible() { 404 gfx::Rect child_area = GetContentsBounds(); 405 if (child_area.IsEmpty()) 406 return; 407 408 if (animator_.get() && animator_->IsAnimating()) { 409 has_deferred_task_ = true; 410 return; 411 } 412 413 if (!animator_.get()) { 414 animator_.reset(new views::BoundsAnimator(this)); 415 animator_->AddObserver(this); 416 } 417 418 if (!clearing_all_views_.empty()) { 419 AnimateClearingOneNotification(); 420 return; 421 } 422 423 if (top_down_ || 424 CommandLine::ForCurrentProcess()->HasSwitch( 425 switches::kEnableMessageCenterAlwaysScrollUpUponNotificationRemoval)) 426 AnimateNotificationsBelowTarget(); 427 else 428 AnimateNotificationsAboveTarget(); 429 430 adding_views_.clear(); 431 deleting_views_.clear(); 432 } 433 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; 445 } 446 } 447 if (last_index > 0) { 448 int between_items = 449 kMarginBetweenItems - MessageView::GetShadowInsets().bottom(); 450 int top = (reposition_top_ > 0) ? reposition_top_ : GetInsets().top(); 451 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; 457 } 458 } 459 } 460 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; 472 } 473 } 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; 485 } 486 } 487 } 488 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); 507 } 508 return true; 509 } 510 511 void MessageListView::AnimateClearingOneNotification() { 512 DCHECK(!clearing_all_views_.empty()); 513 514 clear_all_started_ = true; 515 516 views::View* child = clearing_all_views_.front(); 517 clearing_all_views_.pop_front(); 518 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); 523 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)); 532 } 533 } 534 535 // MessageCenterView /////////////////////////////////////////////////////////// 536 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)); 560 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); 568 569 const int button_height = button_bar_->GetPreferredSize().height(); 570 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)); 576 577 scroller_->SetPaintToLayer(true); 578 scroller_->SetFillsBoundsOpaquely(false); 579 scroller_->layer()->SetMasksToBounds(true); 580 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(); 585 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); 596 597 settings_view_ = new NotifierSettingsView(notifier_settings_provider); 598 599 if (initially_settings_visible) 600 scroller_->SetVisible(false); 601 else 602 settings_view_->SetVisible(false); 603 604 AddChildView(scroller_); 605 AddChildView(settings_view_); 606 AddChildView(button_bar_); 607 } 608 609 MessageCenterView::~MessageCenterView() { 610 if (!is_closing_) 611 message_center_->RemoveObserver(this); 612 } 613 614 void MessageCenterView::SetNotifications( 615 const NotificationList::Notifications& notifications) { 616 if (is_closing_) 617 return; 618 619 notification_views_.clear(); 620 621 int index = 0; 622 for (NotificationList::Notifications::const_iterator iter = 623 notifications.begin(); iter != notifications.end(); ++iter) { 624 AddNotificationAt(*(*iter), index++); 625 626 message_center_->DisplayedNotification( 627 (*iter)->id(), message_center::DISPLAY_SOURCE_MESSAGE_CENTER); 628 if (notification_views_.size() >= kMaxVisibleMessageCenterNotifications) 629 break; 630 } 631 632 NotificationsChanged(); 633 scroller_->RequestFocus(); 634 } 635 636 void MessageCenterView::SetSettingsVisible(bool visible) { 637 if (is_closing_) 638 return; 639 640 if (visible == settings_visible_) 641 return; 642 643 settings_visible_ = visible; 644 645 if (visible) { 646 source_view_ = scroller_; 647 target_view_ = settings_view_; 648 } else { 649 source_view_ = settings_view_; 650 target_view_ = scroller_; 651 } 652 source_height_ = source_view_->GetHeightForWidth(width()); 653 target_height_ = target_view_->GetHeightForWidth(width()); 654 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()); 666 } 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()); 675 } 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(); 681 682 button_bar_->SetBackArrowVisible(visible); 683 } 684 685 void MessageCenterView::ClearAllNotifications() { 686 if (is_closing_) 687 return; 688 689 scroller_->SetEnabled(false); 690 button_bar_->SetAllButtonsEnabled(false); 691 message_list_view_->ClearAllNotifications(scroller_->GetVisibleRect()); 692 } 693 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. 699 } 700 701 size_t MessageCenterView::NumMessageViewsForTest() const { 702 return message_list_view_->child_count(); 703 } 704 705 void MessageCenterView::OnSettingsChanged() { 706 scroller_->InvalidateLayout(); 707 PreferredSizeChanged(); 708 Layout(); 709 } 710 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); 717 } 718 719 void MessageCenterView::Layout() { 720 if (is_closing_) 721 return; 722 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); 732 } 733 return; 734 } 735 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); 744 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(); 750 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)); 760 } 761 button_bar_->SchedulePaint(); 762 } 763 button_bar_->SetBounds(0, 764 top_down_ ? 0 : height() - button_height, 765 width(), 766 button_height); 767 if (GetWidget()) 768 GetWidget()->GetRootView()->SchedulePaint(); 769 } 770 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)); 779 } 780 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()); 786 } 787 return gfx::Size(width, GetHeightForWidth(width)); 788 } 789 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_); 797 } 798 return button_bar_->GetHeightForWidth(width) + content_height; 799 } 800 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; 808 } 809 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); 818 } 819 820 void MessageCenterView::OnMouseExited(const ui::MouseEvent& event) { 821 if (is_closing_) 822 return; 823 824 message_list_view_->ResetRepositionSession(); 825 NotificationsChanged(); 826 } 827 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; 838 } 839 if (notification_views_.size() >= kMaxVisibleMessageCenterNotifications) 840 break; 841 } 842 NotificationsChanged(); 843 } 844 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); 865 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(); 873 } 874 } 875 } 876 message_list_view_->RemoveNotification(view); 877 notification_views_.erase(view_iter); 878 NotificationsChanged(); 879 } 880 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; 898 } 899 } 900 } 901 902 void MessageCenterView::ClickOnNotification( 903 const std::string& notification_id) { 904 message_center_->ClickOnNotification(notification_id); 905 } 906 907 void MessageCenterView::RemoveNotification(const std::string& notification_id, 908 bool by_user) { 909 message_center_->RemoveNotification(notification_id, by_user); 910 } 911 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); 916 } 917 918 bool MessageCenterView::HasClickedListener(const std::string& notification_id) { 919 return message_center_->HasClickedListener(notification_id); 920 } 921 922 void MessageCenterView::ClickOnNotificationButton( 923 const std::string& notification_id, 924 int button_index) { 925 message_center_->ClickOnNotificationButton(notification_id, button_index); 926 } 927 928 void MessageCenterView::AnimationEnded(const gfx::Animation* animation) { 929 DCHECK_EQ(animation, settings_transition_animation_.get()); 930 931 Visibility visibility = target_view_ == settings_view_ 932 ? VISIBILITY_SETTINGS 933 : VISIBILITY_MESSAGE_CENTER; 934 message_center_->SetVisibility(visibility); 935 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(); 945 } 946 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(); 960 } 961 } 962 963 void MessageCenterView::AnimationCanceled(const gfx::Animation* animation) { 964 DCHECK_EQ(animation, settings_transition_animation_.get()); 965 AnimationEnded(animation); 966 } 967 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); 976 } 977 978 void MessageCenterView::NotificationsChanged() { 979 bool no_message_views = notification_views_.empty(); 980 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(); 989 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()); 994 995 button_bar_->SetCloseAllButtonEnabled(!no_message_views); 996 scroller_->SetFocusable(!no_message_views); 997 998 if (focus_manager && focused_view) 999 focus_manager->SetFocusedView(focused_view); 1000 1001 scroller_->InvalidateLayout(); 1002 PreferredSizeChanged(); 1003 Layout(); 1004 } 1005 1006 void MessageCenterView::SetNotificationViewForTest(MessageView* view) { 1007 message_list_view_->AddNotificationAt(view, 0); 1008 } 1009 1010 } // namespace message_center 1011