Chromium Code Reviews| Index: ui/message_center/views/message_popup_collection.cc |
| diff --git a/ui/message_center/views/message_popup_collection.cc b/ui/message_center/views/message_popup_collection.cc |
| index 4f13879f5400ca0cf0b77f09c09d6b86c2e36a50..d1a3918f60664a8c03a8b9e2e692f68cac0f6abf 100644 |
| --- a/ui/message_center/views/message_popup_collection.cc |
| +++ b/ui/message_center/views/message_popup_collection.cc |
| @@ -26,8 +26,11 @@ namespace message_center { |
| class ToastContentsView : public views::WidgetDelegateView { |
| public: |
| ToastContentsView(const Notification* notification, |
| - base::WeakPtr<MessagePopupCollection> collection) |
| - : collection_(collection) { |
| + base::WeakPtr<MessagePopupCollection> collection, |
| + MessageCenter* message_center) |
| + : id_(notification->id()), |
| + collection_(collection), |
| + message_center_(message_center) { |
| DCHECK(collection_); |
| set_notify_enter_exit_on_child(true); |
| @@ -43,7 +46,7 @@ class ToastContentsView : public views::WidgetDelegateView { |
| // Creates the timer only when it does the timeout (i.e. not never-timeout). |
| if (!notification->never_timeout()) |
| - timer_.reset(new base::OneShotTimer<views::Widget>); |
| + timer_.reset(new base::OneShotTimer<MessageCenter>); |
| } |
| views::Widget* CreateWidget(gfx::NativeView parent) { |
| @@ -98,8 +101,8 @@ class ToastContentsView : public views::WidgetDelegateView { |
| start_time_ = base::Time::Now(); |
| timer_->Start(FROM_HERE, |
| delay_, |
| - base::Bind(&views::Widget::Close, |
| - base::Unretained(GetWidget()))); |
| + base::Bind(&MessageCenter::RemoveNotification, |
| + base::Unretained(message_center_), id_, false)); |
|
dharcourt
2013/04/12 09:05:38
Won't this remove the notification from the notifi
Jun Mukai
2013/04/12 21:20:54
Ugha, you're right. I'm doing a bad thing.
reverti
|
| } |
| // Overridden from views::WidgetDelegate: |
| @@ -145,10 +148,12 @@ class ToastContentsView : public views::WidgetDelegateView { |
| } |
| private: |
| + std::string id_; |
| base::TimeDelta delay_; |
| base::Time start_time_; |
| - scoped_ptr<base::OneShotTimer<views::Widget> > timer_; |
| + scoped_ptr<base::OneShotTimer<MessageCenter> > timer_; |
| base::WeakPtr<MessagePopupCollection> collection_; |
| + MessageCenter* message_center_; |
| DISALLOW_COPY_AND_ASSIGN(ToastContentsView); |
| }; |
| @@ -158,14 +163,17 @@ MessagePopupCollection::MessagePopupCollection(gfx::NativeView parent, |
| : parent_(parent), |
| message_center_(message_center) { |
| DCHECK(message_center_); |
| - UpdatePopups(); |
| + |
| + UpdateWidgets(); |
| + message_center_->AddObserver(this); |
| } |
| MessagePopupCollection::~MessagePopupCollection() { |
| + message_center_->RemoveObserver(this); |
| CloseAllWidgets(); |
| } |
| -void MessagePopupCollection::UpdatePopups() { |
| +void MessagePopupCollection::UpdateWidgets() { |
| NotificationList::PopupNotifications popups = |
| message_center_->GetPopupNotifications(); |
| @@ -174,26 +182,11 @@ void MessagePopupCollection::UpdatePopups() { |
| return; |
| } |
| - gfx::Rect work_area; |
| - if (!parent_) { |
| - // On Win+Aura, we don't have a parent since the popups currently show up |
| - // on the Windows desktop, not in the Aura/Ash desktop. This code will |
| - // display the popups on the primary display. |
| - gfx::Screen* screen = gfx::Screen::GetNativeScreen(); |
| - work_area = screen->GetPrimaryDisplay().work_area(); |
| - } else { |
| - gfx::Screen* screen = gfx::Screen::GetScreenFor(parent_); |
| - work_area = screen->GetDisplayNearestWindow(parent_).work_area(); |
| - } |
| - |
| - std::set<std::string> old_toast_ids; |
| - for (ToastContainer::iterator iter = toasts_.begin(); iter != toasts_.end(); |
| - ++iter) { |
| - old_toast_ids.insert(iter->first); |
| - } |
| - |
| - int bottom = work_area.bottom() - kMarginBetweenItems; |
| - int left = work_area.right() - kNotificationWidth - kMarginBetweenItems; |
| + gfx::Point base_position = GetWorkAreaBottomRight(); |
| + int bottom = widgets_.empty() ? |
| + base_position.y() : widgets_.back()->GetWindowBoundsInScreen().y(); |
| + bottom -= kMarginBetweenItems; |
| + int left = base_position.x() - kNotificationWidth - kMarginBetweenItems; |
| // Iterate in the reverse order to keep the oldest toasts on screen. Newer |
| // items may be ignored if there are no room to place them. |
| for (NotificationList::PopupNotifications::const_reverse_iterator iter = |
| @@ -206,48 +199,30 @@ void MessagePopupCollection::UpdatePopups() { |
| break; |
| } |
| - ToastContainer::iterator toast_iter = toasts_.find((*iter)->id()); |
| - views::Widget* widget = NULL; |
| - if (toast_iter != toasts_.end()) { |
| - widget = toast_iter->second->GetWidget(); |
| - old_toast_ids.erase((*iter)->id()); |
| - // Need to replace the contents because |view| can be updated, like |
| - // image loads. |
| - toast_iter->second->SetContents(view); |
| - } else { |
| - ToastContentsView* toast = new ToastContentsView(*iter, AsWeakPtr()); |
| - widget = toast->CreateWidget(parent_); |
| - toast->SetContents(view); |
| - widget->AddObserver(this); |
| - toast->StartTimer(); |
| - toasts_[(*iter)->id()] = toast; |
| + if (toasts_.find((*iter)->id()) != toasts_.end()) { |
| + delete view; |
| + continue; |
| } |
| + ToastContentsView* toast = new ToastContentsView( |
| + *iter, AsWeakPtr(), message_center_); |
| + views::Widget* widget = toast->CreateWidget(parent_); |
| + toast->SetContents(view); |
| + toast->StartTimer(); |
| + toasts_[(*iter)->id()] = toast; |
| + widgets_.push_back(widget); |
| + |
| // Place/move the toast widgets. Currently it stacks the widgets from the |
| // right-bottom of the work area. |
| // TODO(mukai): allow to specify the placement policy from outside of this |
| // class. The policy should be specified from preference on Windows, or |
| // the launcher alignment on ChromeOS. |
| - if (widget) { |
| - gfx::Rect bounds(widget->GetWindowBoundsInScreen()); |
| - bounds.set_origin(gfx::Point(left, bottom - bounds.height())); |
| - widget->SetBounds(bounds); |
| - if (!widget->IsVisible()) |
| - widget->Show(); |
| - } |
| - |
| + gfx::Rect bounds(widget->GetWindowBoundsInScreen()); |
| + bounds.set_origin(gfx::Point(left, bottom - bounds.height())); |
| + widget->SetBounds(bounds); |
| + widget->Show(); |
| bottom -= view_height + kMarginBetweenItems; |
| } |
| - |
| - for (std::set<std::string>::const_iterator iter = old_toast_ids.begin(); |
| - iter != old_toast_ids.end(); ++iter) { |
| - ToastContainer::iterator toast_iter = toasts_.find(*iter); |
| - DCHECK(toast_iter != toasts_.end()); |
| - views::Widget* widget = toast_iter->second->GetWidget(); |
| - widget->RemoveObserver(this); |
| - widget->Close(); |
| - toasts_.erase(toast_iter); |
| - } |
| } |
| void MessagePopupCollection::OnMouseEntered() { |
| @@ -262,6 +237,9 @@ void MessagePopupCollection::OnMouseExited() { |
| iter != toasts_.end(); ++iter) { |
| iter->second->RestartTimer(); |
| } |
| + RepositionWidgets(); |
| + // Reposition could create extra space which allows additional widgets. |
| + UpdateWidgets(); |
| } |
| void MessagePopupCollection::CloseAllWidgets() { |
| @@ -269,23 +247,139 @@ void MessagePopupCollection::CloseAllWidgets() { |
| iter != toasts_.end(); ++iter) { |
| iter->second->SuspendTimer(); |
| views::Widget* widget = iter->second->GetWidget(); |
| - widget->RemoveObserver(this); |
| widget->Close(); |
| } |
| toasts_.clear(); |
| + widgets_.clear(); |
| } |
| -void MessagePopupCollection::OnWidgetDestroying(views::Widget* widget) { |
| - widget->RemoveObserver(this); |
| - for (ToastContainer::iterator iter = toasts_.begin(); |
| - iter != toasts_.end(); ++iter) { |
| - if (iter->second->GetWidget() == widget) { |
| - message_center_->MarkSinglePopupAsShown(iter->first, false); |
| - toasts_.erase(iter); |
| - break; |
| +gfx::Point MessagePopupCollection::GetWorkAreaBottomRight() { |
| + if (!work_area_.IsEmpty()) |
| + return work_area_.bottom_right(); |
| + |
| + if (!parent_) { |
| + // On Win+Aura, we don't have a parent since the popups currently show up |
| + // on the Windows desktop, not in the Aura/Ash desktop. This code will |
| + // display the popups on the primary display. |
| + gfx::Screen* screen = gfx::Screen::GetNativeScreen(); |
| + work_area_ = screen->GetPrimaryDisplay().work_area(); |
| + } else { |
| + gfx::Screen* screen = gfx::Screen::GetScreenFor(parent_); |
| + work_area_ = screen->GetDisplayNearestWindow(parent_).work_area(); |
| + } |
| + |
| + return work_area_.bottom_right(); |
| +} |
| + |
| +void MessagePopupCollection::RepositionWidgets() { |
| + int bottom = GetWorkAreaBottomRight().y() - kMarginBetweenItems; |
| + for (std::list<views::Widget*>::iterator iter = widgets_.begin(); |
| + iter != widgets_.end(); ++iter) { |
| + gfx::Rect bounds((*iter)->GetWindowBoundsInScreen()); |
| + bounds.set_y(bottom - bounds.height()); |
| + (*iter)->SetBounds(bounds); |
| + bottom -= bounds.height() + kMarginBetweenItems; |
| + } |
| +} |
| + |
| +void MessagePopupCollection::RepositionWidgetsWithTarget( |
| + const gfx::Rect& target_bounds) { |
| + if (widgets_.empty()) |
| + return; |
| + |
| + if (widgets_.back()->GetWindowBoundsInScreen().y() > target_bounds.y()) { |
| + // No widgets are above, thus slides up the widgets. |
| + int slide_length = |
| + widgets_.back()->GetWindowBoundsInScreen().y() - target_bounds.y(); |
| + for (std::list<views::Widget*>::iterator iter = widgets_.begin(); |
| + iter != widgets_.end(); ++iter) { |
| + gfx::Rect bounds((*iter)->GetWindowBoundsInScreen()); |
| + bounds.set_y(bounds.y() - slide_length); |
| + (*iter)->SetBounds(bounds); |
| + } |
| + } else { |
| + std::list<views::Widget*>::reverse_iterator iter = widgets_.rbegin(); |
| + for (; iter != widgets_.rend(); ++iter) { |
| + if ((*iter)->GetWindowBoundsInScreen().y() > target_bounds.y()) |
| + break; |
| + } |
| + --iter; |
| + int slide_length = |
| + target_bounds.y() - (*iter)->GetWindowBoundsInScreen().y(); |
| + for (; ; --iter) { |
| + gfx::Rect bounds((*iter)->GetWindowBoundsInScreen()); |
| + bounds.set_y(bounds.y() + slide_length); |
| + (*iter)->SetBounds(bounds); |
| + |
| + if (iter == widgets_.rbegin()) |
| + break; |
| } |
| } |
| - UpdatePopups(); |
| +} |
| + |
| +void MessagePopupCollection::OnNotificationAdded( |
| + const std::string& notification_id) { |
| + UpdateWidgets(); |
| +} |
| + |
| +void MessagePopupCollection::OnNotificationRemoved( |
| + const std::string& notification_id, |
| + bool by_user) { |
| + ToastContainer::iterator iter = toasts_.find(notification_id); |
| + if (iter == toasts_.end()) |
| + return; |
| + |
| + views::Widget* widget = iter->second->GetWidget(); |
| + gfx::Rect removed_bounds = widget->GetWindowBoundsInScreen(); |
| + widget->Close(); |
| + widgets_.erase(std::find(widgets_.begin(), widgets_.end(), widget)); |
| + toasts_.erase(iter); |
| + bool widgets_went_empty = widgets_.empty(); |
| + if (by_user) |
| + RepositionWidgetsWithTarget(removed_bounds); |
| + else |
| + RepositionWidgets(); |
| + |
| + // A notification removal may create extra space which allows appearing |
| + // other notifications. |
| + UpdateWidgets(); |
| + |
| + // Also, if the removed notification is the last one but removing that enables |
| + // other notifications appearing, the newly created widgets also have to be |
| + // repositioned. |
| + if (by_user && widgets_went_empty) |
| + RepositionWidgetsWithTarget(removed_bounds); |
| +} |
| + |
| +void MessagePopupCollection::OnNotificationUpdated( |
| + const std::string& notification_id) { |
| + ToastContainer::iterator toast_iter = toasts_.find(notification_id); |
| + if (toast_iter == toasts_.end()) |
| + return; |
| + |
| + NotificationList::Notifications notifications = |
| + message_center_->GetNotifications(); |
| + bool updated = false; |
| + for (NotificationList::Notifications::iterator iter = |
| + notifications.begin(); iter != notifications.end(); ++iter) { |
| + if ((*iter)->id() != notification_id) |
| + continue; |
| + |
| + MessageView* view = NotificationView::Create( |
| + *(*iter), message_center_, true); |
| + toast_iter->second->SetContents(view); |
| + updated = true; |
| + } |
| + |
| + if (updated) { |
| + RepositionWidgets(); |
| + // Reposition could create extra space which allows additional widgets. |
| + UpdateWidgets(); |
| + } |
| +} |
| + |
| +void MessagePopupCollection::SetWorkAreaForTest(const gfx::Rect& work_area) { |
| + work_area_ = work_area; |
| } |
| } // namespace message_center |