1 // Copyright (c) 2011 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 // Draws the view for the balloons. 6 7 #include "chrome/browser/chromeos/notifications/notification_panel.h" 8 9 #include <algorithm> 10 11 #include "chrome/browser/chromeos/notifications/balloon_collection_impl.h" 12 #include "chrome/browser/chromeos/notifications/balloon_view.h" 13 #include "content/common/notification_details.h" 14 #include "content/common/notification_source.h" 15 #include "grit/generated_resources.h" 16 #include "third_party/cros/chromeos_wm_ipc_enums.h" 17 #include "ui/base/l10n/l10n_util.h" 18 #include "ui/base/resource/resource_bundle.h" 19 #include "ui/gfx/canvas.h" 20 #include "views/background.h" 21 #include "views/controls/native/native_view_host.h" 22 #include "views/controls/scroll_view.h" 23 #include "views/widget/root_view.h" 24 #include "views/widget/widget_gtk.h" 25 26 #define SET_STATE(state) SetState(state, __PRETTY_FUNCTION__) 27 28 namespace { 29 // Minimum and maximum size of balloon content. 30 const int kBalloonMinWidth = 300; 31 const int kBalloonMaxWidth = 300; 32 const int kBalloonMinHeight = 24; 33 const int kBalloonMaxHeight = 120; 34 35 // Maximum height of the notification panel. 36 // TODO(oshima): Get this from system's metrics. 37 const int kMaxPanelHeight = 400; 38 39 // The duration for a new notification to become stale. 40 const int kStaleTimeoutInSeconds = 10; 41 42 using chromeos::BalloonViewImpl; 43 using chromeos::NotificationPanel; 44 45 #if !defined(NDEBUG) 46 // A utility function to convert State enum to string. 47 const char* ToStr(const NotificationPanel::State state) { 48 switch (state) { 49 case NotificationPanel::FULL: 50 return "full"; 51 case NotificationPanel::KEEP_SIZE: 52 return "keep_size"; 53 case NotificationPanel::STICKY_AND_NEW: 54 return "sticky_new"; 55 case NotificationPanel::MINIMIZED: 56 return "minimized"; 57 case NotificationPanel::CLOSED: 58 return "closed"; 59 default: 60 return "unknown"; 61 } 62 } 63 #endif 64 65 chromeos::BalloonViewImpl* GetBalloonViewOf(const Balloon* balloon) { 66 return static_cast<chromeos::BalloonViewImpl*>(balloon->view()); 67 } 68 69 // A WidgetGtk that covers entire ScrollView's viewport. Without this, 70 // all renderer's native gtk widgets are moved one by one via 71 // View::VisibleBoundsInRootChanged() notification, which makes 72 // scrolling not smooth. 73 class ViewportWidget : public views::WidgetGtk { 74 public: 75 explicit ViewportWidget(chromeos::NotificationPanel* panel) 76 : WidgetGtk(views::WidgetGtk::TYPE_CHILD), 77 panel_(panel) { 78 } 79 80 void UpdateControl() { 81 if (last_point_.get()) 82 panel_->OnMouseMotion(*last_point_.get()); 83 } 84 85 // views::WidgetGtk overrides. 86 virtual gboolean OnMotionNotify(GtkWidget* widget, GdkEventMotion* event) { 87 gboolean result = WidgetGtk::OnMotionNotify(widget, event); 88 gdouble x = event->x; 89 gdouble y = event->y; 90 91 // The window_contents_' allocation has been moved off the top left 92 // corner, so we need to adjust it. 93 GtkAllocation alloc = widget->allocation; 94 x -= alloc.x; 95 y -= alloc.y; 96 97 if (!last_point_.get()) { 98 last_point_.reset(new gfx::Point(x, y)); 99 } else { 100 last_point_->set_x(x); 101 last_point_->set_y(y); 102 } 103 panel_->OnMouseMotion(*last_point_.get()); 104 return result; 105 } 106 107 virtual gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event) { 108 gboolean result = views::WidgetGtk::OnLeaveNotify(widget, event); 109 // Leave notify can happen if the mouse moves into the child gdk window. 110 // Make sure the mouse is outside of the panel. 111 gfx::Point p(event->x_root, event->y_root); 112 gfx::Rect bounds = GetWindowScreenBounds(); 113 if (!bounds.Contains(p)) { 114 panel_->OnMouseLeave(); 115 last_point_.reset(); 116 } 117 return result; 118 } 119 120 private: 121 chromeos::NotificationPanel* panel_; 122 scoped_ptr<gfx::Point> last_point_; 123 DISALLOW_COPY_AND_ASSIGN(ViewportWidget); 124 }; 125 126 class BalloonSubContainer : public views::View { 127 public: 128 explicit BalloonSubContainer(int margin) 129 : margin_(margin) { 130 } 131 132 virtual ~BalloonSubContainer() {} 133 134 // views::View overrides. 135 virtual gfx::Size GetPreferredSize() { 136 return preferred_size_; 137 } 138 139 virtual void Layout() { 140 // Layout bottom up 141 int height = 0; 142 for (int i = child_count() - 1; i >= 0; --i) { 143 views::View* child = GetChildViewAt(i); 144 child->SetBounds(0, height, child->width(), child->height()); 145 height += child->height() + margin_; 146 } 147 SchedulePaint(); 148 } 149 150 // Updates the bound so that it can show all balloons. 151 void UpdateBounds() { 152 int height = 0; 153 int max_width = 0; 154 for (int i = child_count() - 1; i >= 0; --i) { 155 views::View* child = GetChildViewAt(i); 156 height += child->height() + margin_; 157 max_width = std::max(max_width, child->width()); 158 } 159 if (height > 0) 160 height -= margin_; 161 preferred_size_.set_width(max_width); 162 preferred_size_.set_height(height); 163 SizeToPreferredSize(); 164 } 165 166 // Returns the bounds that covers new notifications. 167 gfx::Rect GetNewBounds() { 168 gfx::Rect rect; 169 for (int i = child_count() - 1; i >= 0; --i) { 170 BalloonViewImpl* view = 171 static_cast<BalloonViewImpl*>(GetChildViewAt(i)); 172 if (!view->stale()) { 173 if (rect.IsEmpty()) { 174 rect = view->bounds(); 175 } else { 176 rect = rect.Union(view->bounds()); 177 } 178 } 179 } 180 return gfx::Rect(x(), y(), rect.width(), rect.height()); 181 } 182 183 // Returns # of new notifications. 184 int GetNewCount() { 185 int count = 0; 186 for (int i = child_count() - 1; i >= 0; --i) { 187 BalloonViewImpl* view = 188 static_cast<BalloonViewImpl*>(GetChildViewAt(i)); 189 if (!view->stale()) 190 count++; 191 } 192 return count; 193 } 194 195 // Make all notifications stale. 196 void MakeAllStale() { 197 for (int i = child_count() - 1; i >= 0; --i) { 198 BalloonViewImpl* view = 199 static_cast<BalloonViewImpl*>(GetChildViewAt(i)); 200 view->set_stale(); 201 } 202 } 203 204 void DismissAll() { 205 for (int i = child_count() - 1; i >= 0; --i) { 206 BalloonViewImpl* view = 207 static_cast<BalloonViewImpl*>(GetChildViewAt(i)); 208 view->Close(true); 209 } 210 } 211 212 BalloonViewImpl* FindBalloonView(const Notification& notification) { 213 for (int i = child_count() - 1; i >= 0; --i) { 214 BalloonViewImpl* view = 215 static_cast<BalloonViewImpl*>(GetChildViewAt(i)); 216 if (view->IsFor(notification)) { 217 return view; 218 } 219 } 220 return NULL; 221 } 222 223 BalloonViewImpl* FindBalloonView(const gfx::Point point) { 224 gfx::Point copy(point); 225 ConvertPointFromWidget(this, ©); 226 for (int i = child_count() - 1; i >= 0; --i) { 227 views::View* view = GetChildViewAt(i); 228 if (view->bounds().Contains(copy)) 229 return static_cast<BalloonViewImpl*>(view); 230 } 231 return NULL; 232 } 233 234 private: 235 gfx::Size preferred_size_; 236 int margin_; 237 238 DISALLOW_COPY_AND_ASSIGN(BalloonSubContainer); 239 }; 240 241 } // namespace 242 243 namespace chromeos { 244 245 class BalloonContainer : public views::View { 246 public: 247 explicit BalloonContainer(int margin) 248 : margin_(margin), 249 sticky_container_(new BalloonSubContainer(margin)), 250 non_sticky_container_(new BalloonSubContainer(margin)) { 251 AddChildView(sticky_container_); 252 AddChildView(non_sticky_container_); 253 } 254 virtual ~BalloonContainer() {} 255 256 // views::View overrides. 257 virtual void Layout() { 258 int margin = 259 (sticky_container_->child_count() != 0 && 260 non_sticky_container_->child_count() != 0) ? 261 margin_ : 0; 262 sticky_container_->SetBounds( 263 0, 0, width(), sticky_container_->height()); 264 non_sticky_container_->SetBounds( 265 0, sticky_container_->bounds().bottom() + margin, 266 width(), non_sticky_container_->height()); 267 } 268 269 virtual gfx::Size GetPreferredSize() { 270 return preferred_size_; 271 } 272 273 // Returns the size that covers sticky and new notifications. 274 gfx::Size GetStickyNewSize() { 275 gfx::Rect sticky = sticky_container_->bounds(); 276 gfx::Rect new_non_sticky = non_sticky_container_->GetNewBounds(); 277 if (sticky.IsEmpty()) 278 return new_non_sticky.size(); 279 if (new_non_sticky.IsEmpty()) 280 return sticky.size(); 281 return sticky.Union(new_non_sticky).size(); 282 } 283 284 // Adds a ballon to the panel. 285 void Add(Balloon* balloon) { 286 BalloonViewImpl* view = GetBalloonViewOf(balloon); 287 GetContainerFor(balloon)->AddChildView(view); 288 } 289 290 // Updates the position of the |balloon|. 291 bool Update(Balloon* balloon) { 292 BalloonViewImpl* view = GetBalloonViewOf(balloon); 293 View* container = NULL; 294 if (view->parent() == sticky_container_) { 295 container = sticky_container_; 296 } else if (view->parent() == non_sticky_container_) { 297 container = non_sticky_container_; 298 } 299 if (container) { 300 container->RemoveChildView(view); 301 container->AddChildView(view); 302 return true; 303 } else { 304 return false; 305 } 306 } 307 308 // Removes a ballon from the panel. 309 BalloonViewImpl* Remove(Balloon* balloon) { 310 BalloonViewImpl* view = GetBalloonViewOf(balloon); 311 GetContainerFor(balloon)->RemoveChildView(view); 312 return view; 313 } 314 315 // Returns the number of notifications added to the panel. 316 int GetNotificationCount() { 317 return sticky_container_->child_count() + 318 non_sticky_container_->child_count(); 319 } 320 321 // Returns the # of new notifications. 322 int GetNewNotificationCount() { 323 return sticky_container_->GetNewCount() + 324 non_sticky_container_->GetNewCount(); 325 } 326 327 // Returns the # of sticky and new notifications. 328 int GetStickyNewNotificationCount() { 329 return sticky_container_->child_count() + 330 non_sticky_container_->GetNewCount(); 331 } 332 333 // Returns the # of sticky notifications. 334 int GetStickyNotificationCount() { 335 return sticky_container_->child_count(); 336 } 337 338 // Returns true if the |view| is contained in the panel. 339 bool HasBalloonView(View* view) { 340 return view->parent() == sticky_container_ || 341 view->parent() == non_sticky_container_; 342 } 343 344 // Updates the bounds so that all notifications are visible. 345 void UpdateBounds() { 346 sticky_container_->UpdateBounds(); 347 non_sticky_container_->UpdateBounds(); 348 preferred_size_ = sticky_container_->GetPreferredSize(); 349 350 gfx::Size non_sticky_size = non_sticky_container_->GetPreferredSize(); 351 int margin = 352 (!preferred_size_.IsEmpty() && !non_sticky_size.IsEmpty()) ? 353 margin_ : 0; 354 preferred_size_.Enlarge(0, non_sticky_size.height() + margin); 355 preferred_size_.set_width(std::max( 356 preferred_size_.width(), non_sticky_size.width())); 357 SizeToPreferredSize(); 358 } 359 360 void MakeAllStale() { 361 sticky_container_->MakeAllStale(); 362 non_sticky_container_->MakeAllStale(); 363 } 364 365 void DismissAllNonSticky() { 366 non_sticky_container_->DismissAll(); 367 } 368 369 BalloonViewImpl* FindBalloonView(const Notification& notification) { 370 BalloonViewImpl* view = sticky_container_->FindBalloonView(notification); 371 return view ? view : non_sticky_container_->FindBalloonView(notification); 372 } 373 374 BalloonViewImpl* FindBalloonView(const gfx::Point& point) { 375 BalloonViewImpl* view = sticky_container_->FindBalloonView(point); 376 return view ? view : non_sticky_container_->FindBalloonView(point); 377 } 378 379 private: 380 BalloonSubContainer* GetContainerFor(Balloon* balloon) const { 381 BalloonViewImpl* view = GetBalloonViewOf(balloon); 382 return view->sticky() ? 383 sticky_container_ : non_sticky_container_; 384 } 385 386 int margin_; 387 // Sticky/non-sticky ballon containers. They're child views and 388 // deleted when this container is deleted. 389 BalloonSubContainer* sticky_container_; 390 BalloonSubContainer* non_sticky_container_; 391 gfx::Size preferred_size_; 392 393 DISALLOW_COPY_AND_ASSIGN(BalloonContainer); 394 }; 395 396 NotificationPanel::NotificationPanel() 397 : balloon_container_(NULL), 398 panel_widget_(NULL), 399 container_host_(NULL), 400 state_(CLOSED), 401 task_factory_(this), 402 min_bounds_(0, 0, kBalloonMinWidth, kBalloonMinHeight), 403 stale_timeout_(1000 * kStaleTimeoutInSeconds), 404 active_(NULL), 405 scroll_to_(NULL) { 406 Init(); 407 } 408 409 NotificationPanel::~NotificationPanel() { 410 Hide(); 411 } 412 413 //////////////////////////////////////////////////////////////////////////////// 414 // NottificationPanel public. 415 416 void NotificationPanel::Show() { 417 if (!panel_widget_) { 418 // TODO(oshima): Using window because Popup widget behaves weird 419 // when resizing. This needs to be investigated. 420 views::WidgetGtk* widget_gtk = 421 new views::WidgetGtk(views::WidgetGtk::TYPE_WINDOW); 422 // Enable double buffering because the panel has both pure views 423 // control and native controls (scroll bar). 424 widget_gtk->EnableDoubleBuffer(true); 425 panel_widget_ = widget_gtk; 426 427 gfx::Rect bounds = GetPreferredBounds(); 428 bounds = bounds.Union(min_bounds_); 429 panel_widget_->Init(NULL, bounds); 430 // Set minimum bounds so that it can grow freely. 431 gtk_widget_set_size_request(GTK_WIDGET(panel_widget_->GetNativeView()), 432 min_bounds_.width(), min_bounds_.height()); 433 434 views::NativeViewHost* native = new views::NativeViewHost(); 435 scroll_view_->SetContents(native); 436 437 panel_widget_->SetContentsView(scroll_view_.get()); 438 439 // Add the view port after scroll_view is attached to the panel widget. 440 ViewportWidget* widget = new ViewportWidget(this); 441 container_host_ = widget; 442 container_host_->Init(NULL, gfx::Rect()); 443 container_host_->SetContentsView(balloon_container_.get()); 444 // The window_contents_ is onwed by the WidgetGtk. Increase ref count 445 // so that window_contents does not get deleted when detached. 446 g_object_ref(widget->window_contents()); 447 native->Attach(widget->window_contents()); 448 449 UnregisterNotification(); 450 panel_controller_.reset( 451 new PanelController(this, GTK_WINDOW(panel_widget_->GetNativeView()))); 452 panel_controller_->Init(false /* don't focus when opened */, 453 gfx::Rect(0, 0, kBalloonMinWidth, 1), 0, 454 WM_IPC_PANEL_USER_RESIZE_VERTICALLY); 455 registrar_.Add(this, NotificationType::PANEL_STATE_CHANGED, 456 Source<PanelController>(panel_controller_.get())); 457 } 458 panel_widget_->Show(); 459 } 460 461 void NotificationPanel::Hide() { 462 balloon_container_->DismissAllNonSticky(); 463 if (panel_widget_) { 464 container_host_->GetRootView()->RemoveChildView(balloon_container_.get()); 465 466 views::NativeViewHost* native = 467 static_cast<views::NativeViewHost*>(scroll_view_->GetContents()); 468 native->Detach(); 469 scroll_view_->SetContents(NULL); 470 container_host_->Hide(); 471 container_host_->CloseNow(); 472 container_host_ = NULL; 473 474 UnregisterNotification(); 475 panel_controller_->Close(); 476 MessageLoop::current()->DeleteSoon(FROM_HERE, panel_controller_.release()); 477 // We need to remove & detach the scroll view from hierarchy to 478 // avoid GTK deleting child. 479 // TODO(oshima): handle this details in WidgetGtk. 480 panel_widget_->GetRootView()->RemoveChildView(scroll_view_.get()); 481 panel_widget_->Close(); 482 panel_widget_ = NULL; 483 } 484 } 485 486 //////////////////////////////////////////////////////////////////////////////// 487 // BalloonCollectionImpl::NotificationUI overrides. 488 489 void NotificationPanel::Add(Balloon* balloon) { 490 balloon_container_->Add(balloon); 491 if (state_ == CLOSED || state_ == MINIMIZED) 492 SET_STATE(STICKY_AND_NEW); 493 Show(); 494 // Don't resize the panel yet. The panel will be resized when WebKit tells 495 // the size in ResizeNotification. 496 UpdatePanel(false); 497 UpdateControl(); 498 StartStaleTimer(balloon); 499 scroll_to_ = balloon; 500 } 501 502 bool NotificationPanel::Update(Balloon* balloon) { 503 return balloon_container_->Update(balloon); 504 } 505 506 void NotificationPanel::Remove(Balloon* balloon) { 507 BalloonViewImpl* view = balloon_container_->Remove(balloon); 508 if (view == active_) 509 active_ = NULL; 510 if (scroll_to_ == balloon) 511 scroll_to_ = NULL; 512 513 // TODO(oshima): May be we shouldn't close 514 // if the mouse pointer is still on the panel. 515 if (balloon_container_->GetNotificationCount() == 0) 516 SET_STATE(CLOSED); 517 // no change to the state 518 if (state_ == KEEP_SIZE) { 519 // Just update the content. 520 UpdateContainerBounds(); 521 } else { 522 if (state_ != CLOSED && 523 balloon_container_->GetStickyNewNotificationCount() == 0) 524 SET_STATE(MINIMIZED); 525 UpdatePanel(true); 526 } 527 UpdateControl(); 528 } 529 530 void NotificationPanel::Show(Balloon* balloon) { 531 if (state_ == CLOSED || state_ == MINIMIZED) 532 SET_STATE(STICKY_AND_NEW); 533 Show(); 534 UpdatePanel(true); 535 StartStaleTimer(balloon); 536 ScrollBalloonToVisible(balloon); 537 } 538 539 void NotificationPanel::ResizeNotification( 540 Balloon* balloon, const gfx::Size& size) { 541 // restrict to the min & max sizes 542 gfx::Size real_size( 543 std::max(kBalloonMinWidth, 544 std::min(kBalloonMaxWidth, size.width())), 545 std::max(kBalloonMinHeight, 546 std::min(kBalloonMaxHeight, size.height()))); 547 548 // Don't allow balloons to shrink. This avoids flickering 549 // which sometimes rapidly reports alternating sizes. Special 550 // case for setting the minimum value. 551 gfx::Size old_size = balloon->content_size(); 552 if (real_size.width() > old_size.width() || 553 real_size.height() > old_size.height() || 554 real_size == min_bounds_.size()) { 555 balloon->set_content_size(real_size); 556 GetBalloonViewOf(balloon)->Layout(); 557 UpdatePanel(true); 558 if (scroll_to_ == balloon) { 559 ScrollBalloonToVisible(scroll_to_); 560 scroll_to_ = NULL; 561 } 562 } 563 } 564 565 void NotificationPanel::SetActiveView(BalloonViewImpl* view) { 566 // Don't change the active view if it's same notification, 567 // or the notification is being closed. 568 if (active_ == view || (view && view->closed())) 569 return; 570 if (active_) 571 active_->Deactivated(); 572 active_ = view; 573 if (active_) 574 active_->Activated(); 575 } 576 577 //////////////////////////////////////////////////////////////////////////////// 578 // PanelController overrides. 579 580 string16 NotificationPanel::GetPanelTitle() { 581 return string16(l10n_util::GetStringUTF16(IDS_NOTIFICATION_PANEL_TITLE)); 582 } 583 584 SkBitmap NotificationPanel::GetPanelIcon() { 585 return SkBitmap(); 586 } 587 588 bool NotificationPanel::CanClosePanel() { 589 return true; 590 } 591 592 void NotificationPanel::ClosePanel() { 593 SET_STATE(CLOSED); 594 UpdatePanel(false); 595 } 596 597 void NotificationPanel::ActivatePanel() { 598 if (active_) 599 active_->Activated(); 600 } 601 602 //////////////////////////////////////////////////////////////////////////////// 603 // NotificationObserver overrides. 604 605 void NotificationPanel::Observe(NotificationType type, 606 const NotificationSource& source, 607 const NotificationDetails& details) { 608 DCHECK(type == NotificationType::PANEL_STATE_CHANGED); 609 PanelController::State* state = 610 reinterpret_cast<PanelController::State*>(details.map_key()); 611 switch (*state) { 612 case PanelController::EXPANDED: 613 // Geting expanded in STICKY_AND_NEW or in KEEP_SIZE state means 614 // that a new notification is added, so just leave the 615 // state. Otherwise, expand to full. 616 if (state_ != STICKY_AND_NEW && state_ != KEEP_SIZE) 617 SET_STATE(FULL); 618 // When the panel is to be expanded, we either show all, or 619 // show only sticky/new, depending on the state. 620 UpdatePanel(false); 621 break; 622 case PanelController::MINIMIZED: 623 SET_STATE(MINIMIZED); 624 // Make all notifications stale when a user minimize the panel. 625 balloon_container_->MakeAllStale(); 626 break; 627 case PanelController::INITIAL: 628 NOTREACHED() << "Transition to Initial state should not happen"; 629 } 630 } 631 632 //////////////////////////////////////////////////////////////////////////////// 633 // PanelController public. 634 635 void NotificationPanel::OnMouseLeave() { 636 SetActiveView(NULL); 637 if (balloon_container_->GetNotificationCount() == 0) 638 SET_STATE(CLOSED); 639 UpdatePanel(true); 640 } 641 642 void NotificationPanel::OnMouseMotion(const gfx::Point& point) { 643 SetActiveView(balloon_container_->FindBalloonView(point)); 644 SET_STATE(KEEP_SIZE); 645 } 646 647 NotificationPanelTester* NotificationPanel::GetTester() { 648 if (!tester_.get()) 649 tester_.reset(new NotificationPanelTester(this)); 650 return tester_.get(); 651 } 652 653 //////////////////////////////////////////////////////////////////////////////// 654 // NotificationPanel private. 655 656 void NotificationPanel::Init() { 657 DCHECK(!panel_widget_); 658 balloon_container_.reset(new BalloonContainer(1)); 659 balloon_container_->set_parent_owned(false); 660 balloon_container_->set_background( 661 views::Background::CreateSolidBackground(ResourceBundle::frame_color)); 662 663 scroll_view_.reset(new views::ScrollView()); 664 scroll_view_->set_parent_owned(false); 665 scroll_view_->set_background( 666 views::Background::CreateSolidBackground(SK_ColorWHITE)); 667 } 668 669 void NotificationPanel::UnregisterNotification() { 670 if (panel_controller_.get()) 671 registrar_.Remove(this, NotificationType::PANEL_STATE_CHANGED, 672 Source<PanelController>(panel_controller_.get())); 673 } 674 675 void NotificationPanel::ScrollBalloonToVisible(Balloon* balloon) { 676 BalloonViewImpl* view = GetBalloonViewOf(balloon); 677 if (!view->closed()) { 678 // We can't use View::ScrollRectToVisible because the viewport is not 679 // ancestor of the BalloonViewImpl. 680 // Use Widget's coordinate which is same as viewport's coordinates. 681 gfx::Point p(0, 0); 682 views::View::ConvertPointToWidget(view, &p); 683 gfx::Rect visible_rect(p.x(), p.y(), view->width(), view->height()); 684 scroll_view_->ScrollContentsRegionToBeVisible(visible_rect); 685 } 686 } 687 688 void NotificationPanel::UpdatePanel(bool update_container_size) { 689 if (update_container_size) 690 UpdateContainerBounds(); 691 switch (state_) { 692 case KEEP_SIZE: { 693 gfx::Rect min_bounds = GetPreferredBounds(); 694 gfx::Rect panel_bounds = panel_widget_->GetWindowScreenBounds(); 695 if (min_bounds.height() < panel_bounds.height()) 696 panel_widget_->SetBounds(min_bounds); 697 else if (min_bounds.height() > panel_bounds.height()) { 698 // need scroll bar 699 int width = balloon_container_->width() + 700 scroll_view_->GetScrollBarWidth(); 701 panel_bounds.set_width(width); 702 panel_widget_->SetBounds(panel_bounds); 703 } 704 705 // no change. 706 break; 707 } 708 case CLOSED: 709 Hide(); 710 break; 711 case MINIMIZED: 712 balloon_container_->MakeAllStale(); 713 if (panel_controller_.get()) 714 panel_controller_->SetState(PanelController::MINIMIZED); 715 break; 716 case FULL: 717 if (panel_widget_) { 718 panel_widget_->SetBounds(GetPreferredBounds()); 719 panel_controller_->SetState(PanelController::EXPANDED); 720 } 721 break; 722 case STICKY_AND_NEW: 723 if (panel_widget_) { 724 panel_widget_->SetBounds(GetStickyNewBounds()); 725 panel_controller_->SetState(PanelController::EXPANDED); 726 } 727 break; 728 } 729 } 730 731 void NotificationPanel::UpdateContainerBounds() { 732 balloon_container_->UpdateBounds(); 733 views::NativeViewHost* native = 734 static_cast<views::NativeViewHost*>(scroll_view_->GetContents()); 735 // Update from WebKit may arrive after the panel is closed/hidden 736 // and viewport widget is detached. 737 if (native) { 738 native->SetBoundsRect(balloon_container_->bounds()); 739 scroll_view_->Layout(); 740 } 741 } 742 743 void NotificationPanel::UpdateControl() { 744 if (container_host_) 745 static_cast<ViewportWidget*>(container_host_)->UpdateControl(); 746 } 747 748 gfx::Rect NotificationPanel::GetPreferredBounds() { 749 gfx::Size pref_size = balloon_container_->GetPreferredSize(); 750 int new_height = std::min(pref_size.height(), kMaxPanelHeight); 751 int new_width = pref_size.width(); 752 // Adjust the width to avoid showing a horizontal scroll bar. 753 if (new_height != pref_size.height()) { 754 new_width += scroll_view_->GetScrollBarWidth(); 755 } 756 return gfx::Rect(0, 0, new_width, new_height).Union(min_bounds_); 757 } 758 759 gfx::Rect NotificationPanel::GetStickyNewBounds() { 760 gfx::Size pref_size = balloon_container_->GetPreferredSize(); 761 gfx::Size sticky_size = balloon_container_->GetStickyNewSize(); 762 int new_height = std::min(sticky_size.height(), kMaxPanelHeight); 763 int new_width = pref_size.width(); 764 // Adjust the width to avoid showing a horizontal scroll bar. 765 if (new_height != pref_size.height()) 766 new_width += scroll_view_->GetScrollBarWidth(); 767 return gfx::Rect(0, 0, new_width, new_height).Union(min_bounds_); 768 } 769 770 void NotificationPanel::StartStaleTimer(Balloon* balloon) { 771 BalloonViewImpl* view = GetBalloonViewOf(balloon); 772 MessageLoop::current()->PostDelayedTask( 773 FROM_HERE, 774 task_factory_.NewRunnableMethod( 775 &NotificationPanel::OnStale, view), 776 stale_timeout_); 777 } 778 779 void NotificationPanel::OnStale(BalloonViewImpl* view) { 780 if (balloon_container_->HasBalloonView(view) && !view->stale()) { 781 view->set_stale(); 782 // don't update panel on stale 783 if (state_ == KEEP_SIZE) 784 return; 785 if (balloon_container_->GetStickyNewNotificationCount() > 0) { 786 SET_STATE(STICKY_AND_NEW); 787 } else { 788 SET_STATE(MINIMIZED); 789 } 790 UpdatePanel(false); 791 } 792 } 793 794 void NotificationPanel::SetState(State new_state, const char* name) { 795 #if !defined(NDEBUG) 796 DVLOG(1) << "state transition " << ToStr(state_) << " >> " << ToStr(new_state) 797 << " in " << name; 798 #endif 799 state_ = new_state; 800 } 801 802 void NotificationPanel::MarkStale(const Notification& notification) { 803 BalloonViewImpl* view = balloon_container_->FindBalloonView(notification); 804 DCHECK(view); 805 OnStale(view); 806 } 807 808 //////////////////////////////////////////////////////////////////////////////// 809 // NotificationPanelTester public. 810 811 int NotificationPanelTester::GetNotificationCount() const { 812 return panel_->balloon_container_->GetNotificationCount(); 813 } 814 815 int NotificationPanelTester::GetStickyNotificationCount() const { 816 return panel_->balloon_container_->GetStickyNotificationCount(); 817 } 818 819 int NotificationPanelTester::GetNewNotificationCount() const { 820 return panel_->balloon_container_->GetNewNotificationCount(); 821 } 822 823 void NotificationPanelTester::SetStaleTimeout(int timeout) { 824 panel_->stale_timeout_ = timeout; 825 } 826 827 void NotificationPanelTester::MarkStale(const Notification& notification) { 828 panel_->MarkStale(notification); 829 } 830 831 PanelController* NotificationPanelTester::GetPanelController() const { 832 return panel_->panel_controller_.get(); 833 } 834 835 BalloonViewImpl* NotificationPanelTester::GetBalloonView( 836 BalloonCollectionImpl* collection, 837 const Notification& notification) { 838 Balloon* balloon = collection->FindBalloon(notification); 839 DCHECK(balloon); 840 return GetBalloonViewOf(balloon); 841 } 842 843 bool NotificationPanelTester::IsVisible(const BalloonViewImpl* view) const { 844 gfx::Rect rect = panel_->scroll_view_->GetVisibleRect(); 845 gfx::Point origin(0, 0); 846 views::View::ConvertPointToView(view, panel_->balloon_container_.get(), 847 &origin); 848 return rect.Contains(gfx::Rect(origin, view->size())); 849 } 850 851 852 bool NotificationPanelTester::IsActive(const BalloonViewImpl* view) const { 853 return panel_->active_ == view; 854 } 855 856 } // namespace chromeos 857