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.
ToStr(const NotificationPanel::State state)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
GetBalloonViewOf(const Balloon * balloon)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:
ViewportWidget(chromeos::NotificationPanel * panel)75 explicit ViewportWidget(chromeos::NotificationPanel* panel)
76 : WidgetGtk(views::WidgetGtk::TYPE_CHILD),
77 panel_(panel) {
78 }
79
UpdateControl()80 void UpdateControl() {
81 if (last_point_.get())
82 panel_->OnMouseMotion(*last_point_.get());
83 }
84
85 // views::WidgetGtk overrides.
OnMotionNotify(GtkWidget * widget,GdkEventMotion * event)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
OnLeaveNotify(GtkWidget * widget,GdkEventCrossing * event)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:
BalloonSubContainer(int margin)128 explicit BalloonSubContainer(int margin)
129 : margin_(margin) {
130 }
131
~BalloonSubContainer()132 virtual ~BalloonSubContainer() {}
133
134 // views::View overrides.
GetPreferredSize()135 virtual gfx::Size GetPreferredSize() {
136 return preferred_size_;
137 }
138
Layout()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.
UpdateBounds()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.
GetNewBounds()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.
GetNewCount()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.
MakeAllStale()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
DismissAll()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
FindBalloonView(const Notification & notification)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
FindBalloonView(const gfx::Point point)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:
BalloonContainer(int margin)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 }
~BalloonContainer()254 virtual ~BalloonContainer() {}
255
256 // views::View overrides.
Layout()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
GetPreferredSize()269 virtual gfx::Size GetPreferredSize() {
270 return preferred_size_;
271 }
272
273 // Returns the size that covers sticky and new notifications.
GetStickyNewSize()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.
Add(Balloon * balloon)285 void Add(Balloon* balloon) {
286 BalloonViewImpl* view = GetBalloonViewOf(balloon);
287 GetContainerFor(balloon)->AddChildView(view);
288 }
289
290 // Updates the position of the |balloon|.
Update(Balloon * 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.
Remove(Balloon * balloon)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.
GetNotificationCount()316 int GetNotificationCount() {
317 return sticky_container_->child_count() +
318 non_sticky_container_->child_count();
319 }
320
321 // Returns the # of new notifications.
GetNewNotificationCount()322 int GetNewNotificationCount() {
323 return sticky_container_->GetNewCount() +
324 non_sticky_container_->GetNewCount();
325 }
326
327 // Returns the # of sticky and new notifications.
GetStickyNewNotificationCount()328 int GetStickyNewNotificationCount() {
329 return sticky_container_->child_count() +
330 non_sticky_container_->GetNewCount();
331 }
332
333 // Returns the # of sticky notifications.
GetStickyNotificationCount()334 int GetStickyNotificationCount() {
335 return sticky_container_->child_count();
336 }
337
338 // Returns true if the |view| is contained in the panel.
HasBalloonView(View * view)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.
UpdateBounds()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
MakeAllStale()360 void MakeAllStale() {
361 sticky_container_->MakeAllStale();
362 non_sticky_container_->MakeAllStale();
363 }
364
DismissAllNonSticky()365 void DismissAllNonSticky() {
366 non_sticky_container_->DismissAll();
367 }
368
FindBalloonView(const Notification & notification)369 BalloonViewImpl* FindBalloonView(const Notification& notification) {
370 BalloonViewImpl* view = sticky_container_->FindBalloonView(notification);
371 return view ? view : non_sticky_container_->FindBalloonView(notification);
372 }
373
FindBalloonView(const gfx::Point & point)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:
GetContainerFor(Balloon * balloon) const380 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
NotificationPanel()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
~NotificationPanel()409 NotificationPanel::~NotificationPanel() {
410 Hide();
411 }
412
413 ////////////////////////////////////////////////////////////////////////////////
414 // NottificationPanel public.
415
Show()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
Hide()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
Add(Balloon * balloon)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
Update(Balloon * balloon)502 bool NotificationPanel::Update(Balloon* balloon) {
503 return balloon_container_->Update(balloon);
504 }
505
Remove(Balloon * balloon)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
Show(Balloon * balloon)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
ResizeNotification(Balloon * balloon,const gfx::Size & size)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
SetActiveView(BalloonViewImpl * view)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
GetPanelTitle()580 string16 NotificationPanel::GetPanelTitle() {
581 return string16(l10n_util::GetStringUTF16(IDS_NOTIFICATION_PANEL_TITLE));
582 }
583
GetPanelIcon()584 SkBitmap NotificationPanel::GetPanelIcon() {
585 return SkBitmap();
586 }
587
CanClosePanel()588 bool NotificationPanel::CanClosePanel() {
589 return true;
590 }
591
ClosePanel()592 void NotificationPanel::ClosePanel() {
593 SET_STATE(CLOSED);
594 UpdatePanel(false);
595 }
596
ActivatePanel()597 void NotificationPanel::ActivatePanel() {
598 if (active_)
599 active_->Activated();
600 }
601
602 ////////////////////////////////////////////////////////////////////////////////
603 // NotificationObserver overrides.
604
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
OnMouseLeave()635 void NotificationPanel::OnMouseLeave() {
636 SetActiveView(NULL);
637 if (balloon_container_->GetNotificationCount() == 0)
638 SET_STATE(CLOSED);
639 UpdatePanel(true);
640 }
641
OnMouseMotion(const gfx::Point & point)642 void NotificationPanel::OnMouseMotion(const gfx::Point& point) {
643 SetActiveView(balloon_container_->FindBalloonView(point));
644 SET_STATE(KEEP_SIZE);
645 }
646
GetTester()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
Init()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
UnregisterNotification()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
ScrollBalloonToVisible(Balloon * balloon)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
UpdatePanel(bool update_container_size)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
UpdateContainerBounds()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
UpdateControl()743 void NotificationPanel::UpdateControl() {
744 if (container_host_)
745 static_cast<ViewportWidget*>(container_host_)->UpdateControl();
746 }
747
GetPreferredBounds()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
GetStickyNewBounds()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
StartStaleTimer(Balloon * balloon)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
OnStale(BalloonViewImpl * view)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
SetState(State new_state,const char * name)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
MarkStale(const Notification & notification)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
GetNotificationCount() const811 int NotificationPanelTester::GetNotificationCount() const {
812 return panel_->balloon_container_->GetNotificationCount();
813 }
814
GetStickyNotificationCount() const815 int NotificationPanelTester::GetStickyNotificationCount() const {
816 return panel_->balloon_container_->GetStickyNotificationCount();
817 }
818
GetNewNotificationCount() const819 int NotificationPanelTester::GetNewNotificationCount() const {
820 return panel_->balloon_container_->GetNewNotificationCount();
821 }
822
SetStaleTimeout(int timeout)823 void NotificationPanelTester::SetStaleTimeout(int timeout) {
824 panel_->stale_timeout_ = timeout;
825 }
826
MarkStale(const Notification & notification)827 void NotificationPanelTester::MarkStale(const Notification& notification) {
828 panel_->MarkStale(notification);
829 }
830
GetPanelController() const831 PanelController* NotificationPanelTester::GetPanelController() const {
832 return panel_->panel_controller_.get();
833 }
834
GetBalloonView(BalloonCollectionImpl * collection,const Notification & notification)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
IsVisible(const BalloonViewImpl * view) const843 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
IsActive(const BalloonViewImpl * view) const852 bool NotificationPanelTester::IsActive(const BalloonViewImpl* view) const {
853 return panel_->active_ == view;
854 }
855
856 } // namespace chromeos
857