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