This source file includes following definitions.
- weak_factory_
- ClickOnNotification
- RemoveNotification
- CreateMenuModel
- HasClickedListener
- ClickOnNotificationButton
- MarkAllPopupsShown
- UpdateWidgets
- OnMouseEntered
- OnMouseExited
- CloseAllWidgets
- ForgetToast
- RemoveToast
- GetToastOriginX
- RepositionWidgets
- RepositionWidgetsWithTarget
- ComputePopupAlignment
- GetBaseLine
- OnNotificationAdded
- OnNotificationRemoved
- OnDeferTimerExpired
- OnNotificationUpdated
- FindToast
- IncrementDeferCounter
- DecrementDeferCounter
- DoUpdateIfPossible
- SetDisplayInfo
- OnDisplayBoundsChanged
- OnDisplayAdded
- OnDisplayRemoved
- GetWidgetForTest
- CreateRunLoopForTest
- WaitForTest
- GetToastRectAt
#include "ui/message_center/views/message_popup_collection.h"
#include <set>
#include "base/bind.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "ui/accessibility/ax_enums.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/screen.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/message_center_style.h"
#include "ui/message_center/message_center_tray.h"
#include "ui/message_center/message_center_util.h"
#include "ui/message_center/notification.h"
#include "ui/message_center/notification_list.h"
#include "ui/message_center/views/message_view_context_menu_controller.h"
#include "ui/message_center/views/notification_view.h"
#include "ui/message_center/views/toast_contents_view.h"
#include "ui/views/background.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/view.h"
#include "ui/views/views_delegate.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
namespace message_center {
namespace {
const int kMouseExitedDeferTimeoutMs = 200;
const int kToastMarginY = kMarginBetweenItems;
#if defined(OS_CHROMEOS)
const int kToastMarginX = 3;
#else
const int kToastMarginX = kMarginBetweenItems;
#endif
const int kNoToastMarginBorderAndShadowOffset = 2;
}
MessagePopupCollection::MessagePopupCollection(gfx::NativeView parent,
MessageCenter* message_center,
MessageCenterTray* tray,
bool first_item_has_no_margin)
: parent_(parent),
message_center_(message_center),
tray_(tray),
display_id_(gfx::Display::kInvalidDisplayID),
screen_(NULL),
defer_counter_(0),
latest_toast_entered_(NULL),
user_is_closing_toasts_by_clicking_(false),
first_item_has_no_margin_(first_item_has_no_margin),
context_menu_controller_(new MessageViewContextMenuController(this)),
weak_factory_(this) {
DCHECK(message_center_);
defer_timer_.reset(new base::OneShotTimer<MessagePopupCollection>);
message_center_->AddObserver(this);
}
MessagePopupCollection::~MessagePopupCollection() {
weak_factory_.InvalidateWeakPtrs();
if (screen_)
screen_->RemoveObserver(this);
message_center_->RemoveObserver(this);
CloseAllWidgets();
}
void MessagePopupCollection::ClickOnNotification(
const std::string& notification_id) {
message_center_->ClickOnNotification(notification_id);
}
void MessagePopupCollection::RemoveNotification(
const std::string& notification_id,
bool by_user) {
message_center_->RemoveNotification(notification_id, by_user);
}
scoped_ptr<ui::MenuModel> MessagePopupCollection::CreateMenuModel(
const NotifierId& notifier_id,
const base::string16& display_source) {
return tray_->CreateNotificationMenuModel(notifier_id, display_source);
}
bool MessagePopupCollection::HasClickedListener(
const std::string& notification_id) {
return message_center_->HasClickedListener(notification_id);
}
void MessagePopupCollection::ClickOnNotificationButton(
const std::string& notification_id,
int button_index) {
message_center_->ClickOnNotificationButton(notification_id, button_index);
}
void MessagePopupCollection::MarkAllPopupsShown() {
std::set<std::string> closed_ids = CloseAllWidgets();
for (std::set<std::string>::iterator iter = closed_ids.begin();
iter != closed_ids.end(); iter++) {
message_center_->MarkSinglePopupAsShown(*iter, false);
}
}
void MessagePopupCollection::UpdateWidgets() {
NotificationList::PopupNotifications popups =
message_center_->GetPopupNotifications();
if (popups.empty()) {
CloseAllWidgets();
return;
}
bool top_down = alignment_ & POPUP_ALIGNMENT_TOP;
int base = GetBaseLine(toasts_.empty() ? NULL : toasts_.back());
for (NotificationList::PopupNotifications::const_reverse_iterator iter =
popups.rbegin(); iter != popups.rend(); ++iter) {
if (FindToast((*iter)->id()))
continue;
NotificationView* view =
NotificationView::Create(NULL,
*(*iter),
true);
view->set_context_menu_controller(context_menu_controller_.get());
int view_height = ToastContentsView::GetToastSizeForView(view).height();
int height_available = top_down ? work_area_.bottom() - base : base;
if (height_available - view_height - kToastMarginY < 0) {
delete view;
break;
}
ToastContentsView* toast =
new ToastContentsView((*iter)->id(), weak_factory_.GetWeakPtr());
toast->SetContents(view, false);
toasts_.push_back(toast);
view->set_controller(toast);
gfx::Size preferred_size = toast->GetPreferredSize();
gfx::Point origin(GetToastOriginX(gfx::Rect(preferred_size)), base);
if (alignment_ & POPUP_ALIGNMENT_LEFT)
origin.set_x(origin.x() - preferred_size.width());
else
origin.set_x(origin.x() + preferred_size.width());
if (top_down)
origin.set_y(origin.y() + view_height);
toast->RevealWithAnimation(origin);
if (top_down)
base += view_height + kToastMarginY;
else
base -= view_height + kToastMarginY;
if (views::ViewsDelegate::views_delegate) {
views::ViewsDelegate::views_delegate->NotifyAccessibilityEvent(
toast, ui::AX_EVENT_ALERT);
}
message_center_->DisplayedNotification((*iter)->id());
}
}
void MessagePopupCollection::OnMouseEntered(ToastContentsView* toast_entered) {
latest_toast_entered_ = toast_entered;
message_center_->PausePopupTimers();
if (user_is_closing_toasts_by_clicking_)
defer_timer_->Stop();
}
void MessagePopupCollection::OnMouseExited(ToastContentsView* toast_exited) {
if (toast_exited != latest_toast_entered_)
return;
latest_toast_entered_ = NULL;
if (user_is_closing_toasts_by_clicking_) {
defer_timer_->Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kMouseExitedDeferTimeoutMs),
this,
&MessagePopupCollection::OnDeferTimerExpired);
} else {
message_center_->RestartPopupTimers();
}
}
std::set<std::string> MessagePopupCollection::CloseAllWidgets() {
std::set<std::string> closed_toast_ids;
while (!toasts_.empty()) {
ToastContentsView* toast = toasts_.front();
toasts_.pop_front();
closed_toast_ids.insert(toast->id());
OnMouseExited(toast);
toast->CloseWithAnimation();
}
return closed_toast_ids;
}
void MessagePopupCollection::ForgetToast(ToastContentsView* toast) {
toasts_.remove(toast);
OnMouseExited(toast);
}
void MessagePopupCollection::RemoveToast(ToastContentsView* toast,
bool mark_as_shown) {
ForgetToast(toast);
toast->CloseWithAnimation();
if (mark_as_shown)
message_center_->MarkSinglePopupAsShown(toast->id(), false);
}
int MessagePopupCollection::GetToastOriginX(const gfx::Rect& toast_bounds)
const {
#if defined(OS_CHROMEOS)
if (base::i18n::IsRTL())
return work_area_.x() + kToastMarginX;
#endif
if (alignment_ & POPUP_ALIGNMENT_LEFT)
return work_area_.x() + kToastMarginX;
return work_area_.right() - kToastMarginX - toast_bounds.width();
}
void MessagePopupCollection::RepositionWidgets() {
bool top_down = alignment_ & POPUP_ALIGNMENT_TOP;
int base = GetBaseLine(NULL);
for (Toasts::const_iterator iter = toasts_.begin(); iter != toasts_.end();) {
Toasts::const_iterator curr = iter++;
gfx::Rect bounds((*curr)->bounds());
bounds.set_x(GetToastOriginX(bounds));
bounds.set_y(alignment_ & POPUP_ALIGNMENT_TOP ? base
: base - bounds.height());
if ((top_down ? work_area_.bottom() - bounds.bottom() : bounds.y()) >= 0)
(*curr)->SetBoundsWithAnimation(bounds);
else
RemoveToast(*curr, false);
if (top_down)
base += bounds.height() + kToastMarginY;
else
base -= bounds.height() + kToastMarginY;
}
}
void MessagePopupCollection::RepositionWidgetsWithTarget() {
if (toasts_.empty())
return;
bool top_down = alignment_ & POPUP_ALIGNMENT_TOP;
if (top_down ? toasts_.back()->origin().y() < target_top_edge_
: toasts_.back()->origin().y() > target_top_edge_)
return;
Toasts::reverse_iterator iter = toasts_.rbegin();
for (; iter != toasts_.rend(); ++iter) {
if (top_down ? (*iter)->origin().y() < target_top_edge_
: (*iter)->origin().y() > target_top_edge_)
break;
}
--iter;
int slide_length = std::abs(target_top_edge_ - (*iter)->origin().y());
for (;; --iter) {
gfx::Rect bounds((*iter)->bounds());
if (top_down)
bounds.set_y(bounds.y() - slide_length);
else
bounds.set_y(bounds.y() + slide_length);
(*iter)->SetBoundsWithAnimation(bounds);
if (iter == toasts_.rbegin())
break;
}
}
void MessagePopupCollection::ComputePopupAlignment(gfx::Rect work_area,
gfx::Rect screen_bounds) {
alignment_ = work_area.y() > screen_bounds.y() ? POPUP_ALIGNMENT_TOP
: POPUP_ALIGNMENT_BOTTOM;
alignment_ = static_cast<PopupAlignment>(
alignment_ |
((work_area.x() > screen_bounds.x() && work_area.y() == screen_bounds.y())
? POPUP_ALIGNMENT_LEFT
: POPUP_ALIGNMENT_RIGHT));
}
int MessagePopupCollection::GetBaseLine(ToastContentsView* last_toast) const {
bool top_down = alignment_ & POPUP_ALIGNMENT_TOP;
int base;
if (top_down) {
if (!last_toast) {
base = work_area_.y();
if (!first_item_has_no_margin_)
base += kToastMarginY;
else
base -= kNoToastMarginBorderAndShadowOffset;
} else {
base = toasts_.back()->bounds().bottom() + kToastMarginY;
}
} else {
if (!last_toast) {
base = work_area_.bottom();
if (!first_item_has_no_margin_)
base -= kToastMarginY;
else
base += kNoToastMarginBorderAndShadowOffset;
} else {
base = toasts_.back()->origin().y() - kToastMarginY;
}
}
return base;
}
void MessagePopupCollection::OnNotificationAdded(
const std::string& notification_id) {
DoUpdateIfPossible();
}
void MessagePopupCollection::OnNotificationRemoved(
const std::string& notification_id,
bool by_user) {
Toasts::const_iterator iter = toasts_.begin();
for (; iter != toasts_.end(); ++iter) {
if ((*iter)->id() == notification_id)
break;
}
if (iter == toasts_.end())
return;
target_top_edge_ = (*iter)->bounds().y();
if (by_user && !user_is_closing_toasts_by_clicking_) {
user_is_closing_toasts_by_clicking_ = true;
IncrementDeferCounter();
}
RemoveToast(*iter, true);
if (by_user)
RepositionWidgetsWithTarget();
}
void MessagePopupCollection::OnDeferTimerExpired() {
user_is_closing_toasts_by_clicking_ = false;
DecrementDeferCounter();
message_center_->RestartPopupTimers();
}
void MessagePopupCollection::OnNotificationUpdated(
const std::string& notification_id) {
Toasts::const_iterator toast_iter = toasts_.begin();
for (; toast_iter != toasts_.end(); ++toast_iter) {
if ((*toast_iter)->id() == notification_id)
break;
}
if (toast_iter == toasts_.end())
return;
NotificationList::PopupNotifications notifications =
message_center_->GetPopupNotifications();
bool updated = false;
for (NotificationList::PopupNotifications::iterator iter =
notifications.begin(); iter != notifications.end(); ++iter) {
if ((*iter)->id() != notification_id)
continue;
const RichNotificationData& optional_fields =
(*iter)->rich_notification_data();
bool a11y_feedback_for_updates =
optional_fields.should_make_spoken_feedback_for_popup_updates;
NotificationView* view =
NotificationView::Create(*toast_iter,
*(*iter),
true);
view->set_context_menu_controller(context_menu_controller_.get());
(*toast_iter)->SetContents(view, a11y_feedback_for_updates);
updated = true;
}
if (!updated)
RemoveToast(*toast_iter, true);
if (user_is_closing_toasts_by_clicking_)
RepositionWidgetsWithTarget();
else
DoUpdateIfPossible();
}
ToastContentsView* MessagePopupCollection::FindToast(
const std::string& notification_id) const {
for (Toasts::const_iterator iter = toasts_.begin(); iter != toasts_.end();
++iter) {
if ((*iter)->id() == notification_id)
return *iter;
}
return NULL;
}
void MessagePopupCollection::IncrementDeferCounter() {
defer_counter_++;
}
void MessagePopupCollection::DecrementDeferCounter() {
defer_counter_--;
DCHECK(defer_counter_ >= 0);
DoUpdateIfPossible();
}
void MessagePopupCollection::DoUpdateIfPossible() {
if (!screen_) {
gfx::Display display;
if (!parent_) {
screen_ = gfx::Screen::GetNativeScreen();
display = screen_->GetPrimaryDisplay();
} else {
screen_ = gfx::Screen::GetScreenFor(parent_);
display = screen_->GetDisplayNearestWindow(parent_);
}
screen_->AddObserver(this);
display_id_ = display.id();
if (work_area_.IsEmpty()) {
work_area_ = display.work_area();
ComputePopupAlignment(work_area_, display.bounds());
}
}
if (defer_counter_ > 0)
return;
RepositionWidgets();
if (defer_counter_ > 0)
return;
UpdateWidgets();
if (defer_counter_ > 0)
return;
if (run_loop_for_test_.get())
run_loop_for_test_->Quit();
}
void MessagePopupCollection::SetDisplayInfo(const gfx::Rect& work_area,
const gfx::Rect& screen_bounds) {
if (work_area_ == work_area)
return;
work_area_ = work_area;
ComputePopupAlignment(work_area, screen_bounds);
RepositionWidgets();
}
void MessagePopupCollection::OnDisplayBoundsChanged(
const gfx::Display& display) {
if (display.id() != display_id_)
return;
SetDisplayInfo(display.work_area(), display.bounds());
}
void MessagePopupCollection::OnDisplayAdded(const gfx::Display& new_display) {
}
void MessagePopupCollection::OnDisplayRemoved(const gfx::Display& old_display) {
if (display_id_ == old_display.id() && !parent_) {
gfx::Display display = gfx::Screen::GetNativeScreen()->GetPrimaryDisplay();
display_id_ = display.id();
SetDisplayInfo(display.work_area(), display.bounds());
}
}
views::Widget* MessagePopupCollection::GetWidgetForTest(const std::string& id)
const {
for (Toasts::const_iterator iter = toasts_.begin(); iter != toasts_.end();
++iter) {
if ((*iter)->id() == id)
return (*iter)->GetWidget();
}
return NULL;
}
void MessagePopupCollection::CreateRunLoopForTest() {
run_loop_for_test_.reset(new base::RunLoop());
}
void MessagePopupCollection::WaitForTest() {
run_loop_for_test_->Run();
run_loop_for_test_.reset();
}
gfx::Rect MessagePopupCollection::GetToastRectAt(size_t index) const {
DCHECK(defer_counter_ == 0) << "Fetching the bounds with animations active.";
size_t i = 0;
for (Toasts::const_iterator iter = toasts_.begin(); iter != toasts_.end();
++iter) {
if (i++ == index) {
views::Widget* widget = (*iter)->GetWidget();
if (widget)
return widget->GetWindowBoundsInScreen();
break;
}
}
return gfx::Rect();
}
}