• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, &copy);
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