// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Draws the view for the balloons.
#include "chrome/browser/chromeos/notifications/notification_panel.h"
#include <algorithm>
#include "chrome/browser/chromeos/notifications/balloon_collection_impl.h"
#include "chrome/browser/chromeos/notifications/balloon_view.h"
#include "content/common/notification_details.h"
#include "content/common/notification_source.h"
#include "grit/generated_resources.h"
#include "third_party/cros/chromeos_wm_ipc_enums.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "views/background.h"
#include "views/controls/native/native_view_host.h"
#include "views/controls/scroll_view.h"
#include "views/widget/root_view.h"
#include "views/widget/widget_gtk.h"
#define SET_STATE(state) SetState(state, __PRETTY_FUNCTION__)
namespace {
// Minimum and maximum size of balloon content.
const int kBalloonMinWidth = 300;
const int kBalloonMaxWidth = 300;
const int kBalloonMinHeight = 24;
const int kBalloonMaxHeight = 120;
// Maximum height of the notification panel.
// TODO(oshima): Get this from system's metrics.
const int kMaxPanelHeight = 400;
// The duration for a new notification to become stale.
const int kStaleTimeoutInSeconds = 10;
using chromeos::BalloonViewImpl;
using chromeos::NotificationPanel;
#if !defined(NDEBUG)
// A utility function to convert State enum to string.
const char* ToStr(const NotificationPanel::State state) {
switch (state) {
case NotificationPanel::FULL:
return "full";
case NotificationPanel::KEEP_SIZE:
return "keep_size";
case NotificationPanel::STICKY_AND_NEW:
return "sticky_new";
case NotificationPanel::MINIMIZED:
return "minimized";
case NotificationPanel::CLOSED:
return "closed";
default:
return "unknown";
}
}
#endif
chromeos::BalloonViewImpl* GetBalloonViewOf(const Balloon* balloon) {
return static_cast<chromeos::BalloonViewImpl*>(balloon->view());
}
// A WidgetGtk that covers entire ScrollView's viewport. Without this,
// all renderer's native gtk widgets are moved one by one via
// View::VisibleBoundsInRootChanged() notification, which makes
// scrolling not smooth.
class ViewportWidget : public views::WidgetGtk {
public:
explicit ViewportWidget(chromeos::NotificationPanel* panel)
: WidgetGtk(views::WidgetGtk::TYPE_CHILD),
panel_(panel) {
}
void UpdateControl() {
if (last_point_.get())
panel_->OnMouseMotion(*last_point_.get());
}
// views::WidgetGtk overrides.
virtual gboolean OnMotionNotify(GtkWidget* widget, GdkEventMotion* event) {
gboolean result = WidgetGtk::OnMotionNotify(widget, event);
gdouble x = event->x;
gdouble y = event->y;
// The window_contents_' allocation has been moved off the top left
// corner, so we need to adjust it.
GtkAllocation alloc = widget->allocation;
x -= alloc.x;
y -= alloc.y;
if (!last_point_.get()) {
last_point_.reset(new gfx::Point(x, y));
} else {
last_point_->set_x(x);
last_point_->set_y(y);
}
panel_->OnMouseMotion(*last_point_.get());
return result;
}
virtual gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event) {
gboolean result = views::WidgetGtk::OnLeaveNotify(widget, event);
// Leave notify can happen if the mouse moves into the child gdk window.
// Make sure the mouse is outside of the panel.
gfx::Point p(event->x_root, event->y_root);
gfx::Rect bounds = GetWindowScreenBounds();
if (!bounds.Contains(p)) {
panel_->OnMouseLeave();
last_point_.reset();
}
return result;
}
private:
chromeos::NotificationPanel* panel_;
scoped_ptr<gfx::Point> last_point_;
DISALLOW_COPY_AND_ASSIGN(ViewportWidget);
};
class BalloonSubContainer : public views::View {
public:
explicit BalloonSubContainer(int margin)
: margin_(margin) {
}
virtual ~BalloonSubContainer() {}
// views::View overrides.
virtual gfx::Size GetPreferredSize() {
return preferred_size_;
}
virtual void Layout() {
// Layout bottom up
int height = 0;
for (int i = child_count() - 1; i >= 0; --i) {
views::View* child = GetChildViewAt(i);
child->SetBounds(0, height, child->width(), child->height());
height += child->height() + margin_;
}
SchedulePaint();
}
// Updates the bound so that it can show all balloons.
void UpdateBounds() {
int height = 0;
int max_width = 0;
for (int i = child_count() - 1; i >= 0; --i) {
views::View* child = GetChildViewAt(i);
height += child->height() + margin_;
max_width = std::max(max_width, child->width());
}
if (height > 0)
height -= margin_;
preferred_size_.set_width(max_width);
preferred_size_.set_height(height);
SizeToPreferredSize();
}
// Returns the bounds that covers new notifications.
gfx::Rect GetNewBounds() {
gfx::Rect rect;
for (int i = child_count() - 1; i >= 0; --i) {
BalloonViewImpl* view =
static_cast<BalloonViewImpl*>(GetChildViewAt(i));
if (!view->stale()) {
if (rect.IsEmpty()) {
rect = view->bounds();
} else {
rect = rect.Union(view->bounds());
}
}
}
return gfx::Rect(x(), y(), rect.width(), rect.height());
}
// Returns # of new notifications.
int GetNewCount() {
int count = 0;
for (int i = child_count() - 1; i >= 0; --i) {
BalloonViewImpl* view =
static_cast<BalloonViewImpl*>(GetChildViewAt(i));
if (!view->stale())
count++;
}
return count;
}
// Make all notifications stale.
void MakeAllStale() {
for (int i = child_count() - 1; i >= 0; --i) {
BalloonViewImpl* view =
static_cast<BalloonViewImpl*>(GetChildViewAt(i));
view->set_stale();
}
}
void DismissAll() {
for (int i = child_count() - 1; i >= 0; --i) {
BalloonViewImpl* view =
static_cast<BalloonViewImpl*>(GetChildViewAt(i));
view->Close(true);
}
}
BalloonViewImpl* FindBalloonView(const Notification& notification) {
for (int i = child_count() - 1; i >= 0; --i) {
BalloonViewImpl* view =
static_cast<BalloonViewImpl*>(GetChildViewAt(i));
if (view->IsFor(notification)) {
return view;
}
}
return NULL;
}
BalloonViewImpl* FindBalloonView(const gfx::Point point) {
gfx::Point copy(point);
ConvertPointFromWidget(this, ©);
for (int i = child_count() - 1; i >= 0; --i) {
views::View* view = GetChildViewAt(i);
if (view->bounds().Contains(copy))
return static_cast<BalloonViewImpl*>(view);
}
return NULL;
}
private:
gfx::Size preferred_size_;
int margin_;
DISALLOW_COPY_AND_ASSIGN(BalloonSubContainer);
};
} // namespace
namespace chromeos {
class BalloonContainer : public views::View {
public:
explicit BalloonContainer(int margin)
: margin_(margin),
sticky_container_(new BalloonSubContainer(margin)),
non_sticky_container_(new BalloonSubContainer(margin)) {
AddChildView(sticky_container_);
AddChildView(non_sticky_container_);
}
virtual ~BalloonContainer() {}
// views::View overrides.
virtual void Layout() {
int margin =
(sticky_container_->child_count() != 0 &&
non_sticky_container_->child_count() != 0) ?
margin_ : 0;
sticky_container_->SetBounds(
0, 0, width(), sticky_container_->height());
non_sticky_container_->SetBounds(
0, sticky_container_->bounds().bottom() + margin,
width(), non_sticky_container_->height());
}
virtual gfx::Size GetPreferredSize() {
return preferred_size_;
}
// Returns the size that covers sticky and new notifications.
gfx::Size GetStickyNewSize() {
gfx::Rect sticky = sticky_container_->bounds();
gfx::Rect new_non_sticky = non_sticky_container_->GetNewBounds();
if (sticky.IsEmpty())
return new_non_sticky.size();
if (new_non_sticky.IsEmpty())
return sticky.size();
return sticky.Union(new_non_sticky).size();
}
// Adds a ballon to the panel.
void Add(Balloon* balloon) {
BalloonViewImpl* view = GetBalloonViewOf(balloon);
GetContainerFor(balloon)->AddChildView(view);
}
// Updates the position of the |balloon|.
bool Update(Balloon* balloon) {
BalloonViewImpl* view = GetBalloonViewOf(balloon);
View* container = NULL;
if (view->parent() == sticky_container_) {
container = sticky_container_;
} else if (view->parent() == non_sticky_container_) {
container = non_sticky_container_;
}
if (container) {
container->RemoveChildView(view);
container->AddChildView(view);
return true;
} else {
return false;
}
}
// Removes a ballon from the panel.
BalloonViewImpl* Remove(Balloon* balloon) {
BalloonViewImpl* view = GetBalloonViewOf(balloon);
GetContainerFor(balloon)->RemoveChildView(view);
return view;
}
// Returns the number of notifications added to the panel.
int GetNotificationCount() {
return sticky_container_->child_count() +
non_sticky_container_->child_count();
}
// Returns the # of new notifications.
int GetNewNotificationCount() {
return sticky_container_->GetNewCount() +
non_sticky_container_->GetNewCount();
}
// Returns the # of sticky and new notifications.
int GetStickyNewNotificationCount() {
return sticky_container_->child_count() +
non_sticky_container_->GetNewCount();
}
// Returns the # of sticky notifications.
int GetStickyNotificationCount() {
return sticky_container_->child_count();
}
// Returns true if the |view| is contained in the panel.
bool HasBalloonView(View* view) {
return view->parent() == sticky_container_ ||
view->parent() == non_sticky_container_;
}
// Updates the bounds so that all notifications are visible.
void UpdateBounds() {
sticky_container_->UpdateBounds();
non_sticky_container_->UpdateBounds();
preferred_size_ = sticky_container_->GetPreferredSize();
gfx::Size non_sticky_size = non_sticky_container_->GetPreferredSize();
int margin =
(!preferred_size_.IsEmpty() && !non_sticky_size.IsEmpty()) ?
margin_ : 0;
preferred_size_.Enlarge(0, non_sticky_size.height() + margin);
preferred_size_.set_width(std::max(
preferred_size_.width(), non_sticky_size.width()));
SizeToPreferredSize();
}
void MakeAllStale() {
sticky_container_->MakeAllStale();
non_sticky_container_->MakeAllStale();
}
void DismissAllNonSticky() {
non_sticky_container_->DismissAll();
}
BalloonViewImpl* FindBalloonView(const Notification& notification) {
BalloonViewImpl* view = sticky_container_->FindBalloonView(notification);
return view ? view : non_sticky_container_->FindBalloonView(notification);
}
BalloonViewImpl* FindBalloonView(const gfx::Point& point) {
BalloonViewImpl* view = sticky_container_->FindBalloonView(point);
return view ? view : non_sticky_container_->FindBalloonView(point);
}
private:
BalloonSubContainer* GetContainerFor(Balloon* balloon) const {
BalloonViewImpl* view = GetBalloonViewOf(balloon);
return view->sticky() ?
sticky_container_ : non_sticky_container_;
}
int margin_;
// Sticky/non-sticky ballon containers. They're child views and
// deleted when this container is deleted.
BalloonSubContainer* sticky_container_;
BalloonSubContainer* non_sticky_container_;
gfx::Size preferred_size_;
DISALLOW_COPY_AND_ASSIGN(BalloonContainer);
};
NotificationPanel::NotificationPanel()
: balloon_container_(NULL),
panel_widget_(NULL),
container_host_(NULL),
state_(CLOSED),
task_factory_(this),
min_bounds_(0, 0, kBalloonMinWidth, kBalloonMinHeight),
stale_timeout_(1000 * kStaleTimeoutInSeconds),
active_(NULL),
scroll_to_(NULL) {
Init();
}
NotificationPanel::~NotificationPanel() {
Hide();
}
////////////////////////////////////////////////////////////////////////////////
// NottificationPanel public.
void NotificationPanel::Show() {
if (!panel_widget_) {
// TODO(oshima): Using window because Popup widget behaves weird
// when resizing. This needs to be investigated.
views::WidgetGtk* widget_gtk =
new views::WidgetGtk(views::WidgetGtk::TYPE_WINDOW);
// Enable double buffering because the panel has both pure views
// control and native controls (scroll bar).
widget_gtk->EnableDoubleBuffer(true);
panel_widget_ = widget_gtk;
gfx::Rect bounds = GetPreferredBounds();
bounds = bounds.Union(min_bounds_);
panel_widget_->Init(NULL, bounds);
// Set minimum bounds so that it can grow freely.
gtk_widget_set_size_request(GTK_WIDGET(panel_widget_->GetNativeView()),
min_bounds_.width(), min_bounds_.height());
views::NativeViewHost* native = new views::NativeViewHost();
scroll_view_->SetContents(native);
panel_widget_->SetContentsView(scroll_view_.get());
// Add the view port after scroll_view is attached to the panel widget.
ViewportWidget* widget = new ViewportWidget(this);
container_host_ = widget;
container_host_->Init(NULL, gfx::Rect());
container_host_->SetContentsView(balloon_container_.get());
// The window_contents_ is onwed by the WidgetGtk. Increase ref count
// so that window_contents does not get deleted when detached.
g_object_ref(widget->window_contents());
native->Attach(widget->window_contents());
UnregisterNotification();
panel_controller_.reset(
new PanelController(this, GTK_WINDOW(panel_widget_->GetNativeView())));
panel_controller_->Init(false /* don't focus when opened */,
gfx::Rect(0, 0, kBalloonMinWidth, 1), 0,
WM_IPC_PANEL_USER_RESIZE_VERTICALLY);
registrar_.Add(this, NotificationType::PANEL_STATE_CHANGED,
Source<PanelController>(panel_controller_.get()));
}
panel_widget_->Show();
}
void NotificationPanel::Hide() {
balloon_container_->DismissAllNonSticky();
if (panel_widget_) {
container_host_->GetRootView()->RemoveChildView(balloon_container_.get());
views::NativeViewHost* native =
static_cast<views::NativeViewHost*>(scroll_view_->GetContents());
native->Detach();
scroll_view_->SetContents(NULL);
container_host_->Hide();
container_host_->CloseNow();
container_host_ = NULL;
UnregisterNotification();
panel_controller_->Close();
MessageLoop::current()->DeleteSoon(FROM_HERE, panel_controller_.release());
// We need to remove & detach the scroll view from hierarchy to
// avoid GTK deleting child.
// TODO(oshima): handle this details in WidgetGtk.
panel_widget_->GetRootView()->RemoveChildView(scroll_view_.get());
panel_widget_->Close();
panel_widget_ = NULL;
}
}
////////////////////////////////////////////////////////////////////////////////
// BalloonCollectionImpl::NotificationUI overrides.
void NotificationPanel::Add(Balloon* balloon) {
balloon_container_->Add(balloon);
if (state_ == CLOSED || state_ == MINIMIZED)
SET_STATE(STICKY_AND_NEW);
Show();
// Don't resize the panel yet. The panel will be resized when WebKit tells
// the size in ResizeNotification.
UpdatePanel(false);
UpdateControl();
StartStaleTimer(balloon);
scroll_to_ = balloon;
}
bool NotificationPanel::Update(Balloon* balloon) {
return balloon_container_->Update(balloon);
}
void NotificationPanel::Remove(Balloon* balloon) {
BalloonViewImpl* view = balloon_container_->Remove(balloon);
if (view == active_)
active_ = NULL;
if (scroll_to_ == balloon)
scroll_to_ = NULL;
// TODO(oshima): May be we shouldn't close
// if the mouse pointer is still on the panel.
if (balloon_container_->GetNotificationCount() == 0)
SET_STATE(CLOSED);
// no change to the state
if (state_ == KEEP_SIZE) {
// Just update the content.
UpdateContainerBounds();
} else {
if (state_ != CLOSED &&
balloon_container_->GetStickyNewNotificationCount() == 0)
SET_STATE(MINIMIZED);
UpdatePanel(true);
}
UpdateControl();
}
void NotificationPanel::Show(Balloon* balloon) {
if (state_ == CLOSED || state_ == MINIMIZED)
SET_STATE(STICKY_AND_NEW);
Show();
UpdatePanel(true);
StartStaleTimer(balloon);
ScrollBalloonToVisible(balloon);
}
void NotificationPanel::ResizeNotification(
Balloon* balloon, const gfx::Size& size) {
// restrict to the min & max sizes
gfx::Size real_size(
std::max(kBalloonMinWidth,
std::min(kBalloonMaxWidth, size.width())),
std::max(kBalloonMinHeight,
std::min(kBalloonMaxHeight, size.height())));
// Don't allow balloons to shrink. This avoids flickering
// which sometimes rapidly reports alternating sizes. Special
// case for setting the minimum value.
gfx::Size old_size = balloon->content_size();
if (real_size.width() > old_size.width() ||
real_size.height() > old_size.height() ||
real_size == min_bounds_.size()) {
balloon->set_content_size(real_size);
GetBalloonViewOf(balloon)->Layout();
UpdatePanel(true);
if (scroll_to_ == balloon) {
ScrollBalloonToVisible(scroll_to_);
scroll_to_ = NULL;
}
}
}
void NotificationPanel::SetActiveView(BalloonViewImpl* view) {
// Don't change the active view if it's same notification,
// or the notification is being closed.
if (active_ == view || (view && view->closed()))
return;
if (active_)
active_->Deactivated();
active_ = view;
if (active_)
active_->Activated();
}
////////////////////////////////////////////////////////////////////////////////
// PanelController overrides.
string16 NotificationPanel::GetPanelTitle() {
return string16(l10n_util::GetStringUTF16(IDS_NOTIFICATION_PANEL_TITLE));
}
SkBitmap NotificationPanel::GetPanelIcon() {
return SkBitmap();
}
bool NotificationPanel::CanClosePanel() {
return true;
}
void NotificationPanel::ClosePanel() {
SET_STATE(CLOSED);
UpdatePanel(false);
}
void NotificationPanel::ActivatePanel() {
if (active_)
active_->Activated();
}
////////////////////////////////////////////////////////////////////////////////
// NotificationObserver overrides.
void NotificationPanel::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
DCHECK(type == NotificationType::PANEL_STATE_CHANGED);
PanelController::State* state =
reinterpret_cast<PanelController::State*>(details.map_key());
switch (*state) {
case PanelController::EXPANDED:
// Geting expanded in STICKY_AND_NEW or in KEEP_SIZE state means
// that a new notification is added, so just leave the
// state. Otherwise, expand to full.
if (state_ != STICKY_AND_NEW && state_ != KEEP_SIZE)
SET_STATE(FULL);
// When the panel is to be expanded, we either show all, or
// show only sticky/new, depending on the state.
UpdatePanel(false);
break;
case PanelController::MINIMIZED:
SET_STATE(MINIMIZED);
// Make all notifications stale when a user minimize the panel.
balloon_container_->MakeAllStale();
break;
case PanelController::INITIAL:
NOTREACHED() << "Transition to Initial state should not happen";
}
}
////////////////////////////////////////////////////////////////////////////////
// PanelController public.
void NotificationPanel::OnMouseLeave() {
SetActiveView(NULL);
if (balloon_container_->GetNotificationCount() == 0)
SET_STATE(CLOSED);
UpdatePanel(true);
}
void NotificationPanel::OnMouseMotion(const gfx::Point& point) {
SetActiveView(balloon_container_->FindBalloonView(point));
SET_STATE(KEEP_SIZE);
}
NotificationPanelTester* NotificationPanel::GetTester() {
if (!tester_.get())
tester_.reset(new NotificationPanelTester(this));
return tester_.get();
}
////////////////////////////////////////////////////////////////////////////////
// NotificationPanel private.
void NotificationPanel::Init() {
DCHECK(!panel_widget_);
balloon_container_.reset(new BalloonContainer(1));
balloon_container_->set_parent_owned(false);
balloon_container_->set_background(
views::Background::CreateSolidBackground(ResourceBundle::frame_color));
scroll_view_.reset(new views::ScrollView());
scroll_view_->set_parent_owned(false);
scroll_view_->set_background(
views::Background::CreateSolidBackground(SK_ColorWHITE));
}
void NotificationPanel::UnregisterNotification() {
if (panel_controller_.get())
registrar_.Remove(this, NotificationType::PANEL_STATE_CHANGED,
Source<PanelController>(panel_controller_.get()));
}
void NotificationPanel::ScrollBalloonToVisible(Balloon* balloon) {
BalloonViewImpl* view = GetBalloonViewOf(balloon);
if (!view->closed()) {
// We can't use View::ScrollRectToVisible because the viewport is not
// ancestor of the BalloonViewImpl.
// Use Widget's coordinate which is same as viewport's coordinates.
gfx::Point p(0, 0);
views::View::ConvertPointToWidget(view, &p);
gfx::Rect visible_rect(p.x(), p.y(), view->width(), view->height());
scroll_view_->ScrollContentsRegionToBeVisible(visible_rect);
}
}
void NotificationPanel::UpdatePanel(bool update_container_size) {
if (update_container_size)
UpdateContainerBounds();
switch (state_) {
case KEEP_SIZE: {
gfx::Rect min_bounds = GetPreferredBounds();
gfx::Rect panel_bounds = panel_widget_->GetWindowScreenBounds();
if (min_bounds.height() < panel_bounds.height())
panel_widget_->SetBounds(min_bounds);
else if (min_bounds.height() > panel_bounds.height()) {
// need scroll bar
int width = balloon_container_->width() +
scroll_view_->GetScrollBarWidth();
panel_bounds.set_width(width);
panel_widget_->SetBounds(panel_bounds);
}
// no change.
break;
}
case CLOSED:
Hide();
break;
case MINIMIZED:
balloon_container_->MakeAllStale();
if (panel_controller_.get())
panel_controller_->SetState(PanelController::MINIMIZED);
break;
case FULL:
if (panel_widget_) {
panel_widget_->SetBounds(GetPreferredBounds());
panel_controller_->SetState(PanelController::EXPANDED);
}
break;
case STICKY_AND_NEW:
if (panel_widget_) {
panel_widget_->SetBounds(GetStickyNewBounds());
panel_controller_->SetState(PanelController::EXPANDED);
}
break;
}
}
void NotificationPanel::UpdateContainerBounds() {
balloon_container_->UpdateBounds();
views::NativeViewHost* native =
static_cast<views::NativeViewHost*>(scroll_view_->GetContents());
// Update from WebKit may arrive after the panel is closed/hidden
// and viewport widget is detached.
if (native) {
native->SetBoundsRect(balloon_container_->bounds());
scroll_view_->Layout();
}
}
void NotificationPanel::UpdateControl() {
if (container_host_)
static_cast<ViewportWidget*>(container_host_)->UpdateControl();
}
gfx::Rect NotificationPanel::GetPreferredBounds() {
gfx::Size pref_size = balloon_container_->GetPreferredSize();
int new_height = std::min(pref_size.height(), kMaxPanelHeight);
int new_width = pref_size.width();
// Adjust the width to avoid showing a horizontal scroll bar.
if (new_height != pref_size.height()) {
new_width += scroll_view_->GetScrollBarWidth();
}
return gfx::Rect(0, 0, new_width, new_height).Union(min_bounds_);
}
gfx::Rect NotificationPanel::GetStickyNewBounds() {
gfx::Size pref_size = balloon_container_->GetPreferredSize();
gfx::Size sticky_size = balloon_container_->GetStickyNewSize();
int new_height = std::min(sticky_size.height(), kMaxPanelHeight);
int new_width = pref_size.width();
// Adjust the width to avoid showing a horizontal scroll bar.
if (new_height != pref_size.height())
new_width += scroll_view_->GetScrollBarWidth();
return gfx::Rect(0, 0, new_width, new_height).Union(min_bounds_);
}
void NotificationPanel::StartStaleTimer(Balloon* balloon) {
BalloonViewImpl* view = GetBalloonViewOf(balloon);
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
task_factory_.NewRunnableMethod(
&NotificationPanel::OnStale, view),
stale_timeout_);
}
void NotificationPanel::OnStale(BalloonViewImpl* view) {
if (balloon_container_->HasBalloonView(view) && !view->stale()) {
view->set_stale();
// don't update panel on stale
if (state_ == KEEP_SIZE)
return;
if (balloon_container_->GetStickyNewNotificationCount() > 0) {
SET_STATE(STICKY_AND_NEW);
} else {
SET_STATE(MINIMIZED);
}
UpdatePanel(false);
}
}
void NotificationPanel::SetState(State new_state, const char* name) {
#if !defined(NDEBUG)
DVLOG(1) << "state transition " << ToStr(state_) << " >> " << ToStr(new_state)
<< " in " << name;
#endif
state_ = new_state;
}
void NotificationPanel::MarkStale(const Notification& notification) {
BalloonViewImpl* view = balloon_container_->FindBalloonView(notification);
DCHECK(view);
OnStale(view);
}
////////////////////////////////////////////////////////////////////////////////
// NotificationPanelTester public.
int NotificationPanelTester::GetNotificationCount() const {
return panel_->balloon_container_->GetNotificationCount();
}
int NotificationPanelTester::GetStickyNotificationCount() const {
return panel_->balloon_container_->GetStickyNotificationCount();
}
int NotificationPanelTester::GetNewNotificationCount() const {
return panel_->balloon_container_->GetNewNotificationCount();
}
void NotificationPanelTester::SetStaleTimeout(int timeout) {
panel_->stale_timeout_ = timeout;
}
void NotificationPanelTester::MarkStale(const Notification& notification) {
panel_->MarkStale(notification);
}
PanelController* NotificationPanelTester::GetPanelController() const {
return panel_->panel_controller_.get();
}
BalloonViewImpl* NotificationPanelTester::GetBalloonView(
BalloonCollectionImpl* collection,
const Notification& notification) {
Balloon* balloon = collection->FindBalloon(notification);
DCHECK(balloon);
return GetBalloonViewOf(balloon);
}
bool NotificationPanelTester::IsVisible(const BalloonViewImpl* view) const {
gfx::Rect rect = panel_->scroll_view_->GetVisibleRect();
gfx::Point origin(0, 0);
views::View::ConvertPointToView(view, panel_->balloon_container_.get(),
&origin);
return rect.Contains(gfx::Rect(origin, view->size()));
}
bool NotificationPanelTester::IsActive(const BalloonViewImpl* view) const {
return panel_->active_ == view;
}
} // namespace chromeos