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 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 87 NoNotificationMessageView::~NoNotificationMessageView() { 88 } 89 90 gfx::Size NoNotificationMessageView::GetPreferredSize() const { 91 return gfx::Size(kMinScrollViewHeight, label_->GetPreferredSize().width()); 92 } 93 94 int NoNotificationMessageView::GetHeightForWidth(int width) const { 95 return kMinScrollViewHeight; 96 } 97 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(); 152 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 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 201 MessageListView::~MessageListView() { 202 if (animator_.get()) 203 animator_->RemoveObserver(this); 204 } 205 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 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 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 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 279 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 291 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 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 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 328 void MessageListView::SetRepositionTarget(const gfx::Rect& target) { 329 reposition_top_ = target.y(); 330 fixed_height_ = GetHeightForWidth(width()); 331 } 332 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 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 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 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 394 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 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 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 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 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 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 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 606 MessageCenterView::~MessageCenterView() { 607 if (!is_closing_) 608 message_center_->RemoveObserver(this); 609 } 610 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 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 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 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 698 size_t MessageCenterView::NumMessageViewsForTest() const { 699 return message_list_view_->child_count(); 700 } 701 702 void MessageCenterView::OnSettingsChanged() { 703 scroller_->InvalidateLayout(); 704 PreferredSizeChanged(); 705 Layout(); 706 } 707 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 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 768 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 787 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 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 817 void MessageCenterView::OnMouseExited(const ui::MouseEvent& event) { 818 if (is_closing_) 819 return; 820 821 message_list_view_->ResetRepositionSession(); 822 NotificationsChanged(); 823 } 824 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 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 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 899 void MessageCenterView::ClickOnNotification( 900 const std::string& notification_id) { 901 message_center_->ClickOnNotification(notification_id); 902 } 903 904 void MessageCenterView::RemoveNotification(const std::string& notification_id, 905 bool by_user) { 906 message_center_->RemoveNotification(notification_id, by_user); 907 } 908 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 915 bool MessageCenterView::HasClickedListener(const std::string& notification_id) { 916 return message_center_->HasClickedListener(notification_id); 917 } 918 919 void MessageCenterView::ClickOnNotificationButton( 920 const std::string& notification_id, 921 int button_index) { 922 message_center_->ClickOnNotificationButton(notification_id, button_index); 923 } 924 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 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 960 void MessageCenterView::AnimationCanceled(const gfx::Animation* animation) { 961 DCHECK_EQ(animation, settings_transition_animation_.get()); 962 AnimationEnded(animation); 963 } 964 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 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 1003 void MessageCenterView::SetNotificationViewForTest(MessageView* view) { 1004 message_list_view_->AddNotificationAt(view, 0); 1005 } 1006 1007 } // namespace message_center 1008