OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ui/message_center/views/message_popup_collection.h" | 5 #include "ui/message_center/views/message_popup_collection.h" |
6 | 6 |
7 #include <set> | 7 #include <set> |
8 | 8 |
9 #include "base/bind.h" | 9 #include "base/bind.h" |
10 #include "base/memory/weak_ptr.h" | 10 #include "base/memory/weak_ptr.h" |
11 #include "base/timer.h" | 11 #include "base/timer.h" |
12 #include "ui/gfx/screen.h" | 12 #include "ui/gfx/screen.h" |
13 #include "ui/message_center/message_center.h" | 13 #include "ui/message_center/message_center.h" |
14 #include "ui/message_center/message_center_constants.h" | 14 #include "ui/message_center/message_center_constants.h" |
15 #include "ui/message_center/notification.h" | 15 #include "ui/message_center/notification.h" |
16 #include "ui/message_center/notification_list.h" | 16 #include "ui/message_center/notification_list.h" |
17 #include "ui/message_center/views/notification_view.h" | 17 #include "ui/message_center/views/notification_view.h" |
18 #include "ui/views/background.h" | 18 #include "ui/views/background.h" |
19 #include "ui/views/layout/fill_layout.h" | 19 #include "ui/views/layout/fill_layout.h" |
20 #include "ui/views/view.h" | 20 #include "ui/views/view.h" |
21 #include "ui/views/widget/widget.h" | 21 #include "ui/views/widget/widget.h" |
22 #include "ui/views/widget/widget_delegate.h" | 22 #include "ui/views/widget/widget_delegate.h" |
23 | 23 |
24 namespace message_center { | 24 namespace message_center { |
25 | 25 |
26 class ToastContentsView : public views::WidgetDelegateView { | 26 class ToastContentsView : public views::WidgetDelegateView { |
27 public: | 27 public: |
28 ToastContentsView(const Notification* notification, | 28 ToastContentsView(const Notification* notification, |
29 base::WeakPtr<MessagePopupCollection> collection) | 29 base::WeakPtr<MessagePopupCollection> collection, |
30 : collection_(collection) { | 30 MessageCenter* message_center) |
| 31 : id_(notification->id()), |
| 32 collection_(collection), |
| 33 message_center_(message_center) { |
31 DCHECK(collection_); | 34 DCHECK(collection_); |
32 | 35 |
33 set_notify_enter_exit_on_child(true); | 36 set_notify_enter_exit_on_child(true); |
34 // Sets the transparent background. Then, when the message view is slid out, | 37 // Sets the transparent background. Then, when the message view is slid out, |
35 // the whole toast seems to slide although the actual bound of the widget | 38 // the whole toast seems to slide although the actual bound of the widget |
36 // remains. This is hacky but easier to keep the consistency. | 39 // remains. This is hacky but easier to keep the consistency. |
37 set_background(views::Background::CreateSolidBackground(0, 0, 0, 0)); | 40 set_background(views::Background::CreateSolidBackground(0, 0, 0, 0)); |
38 | 41 |
39 int seconds = kAutocloseDefaultDelaySeconds; | 42 int seconds = kAutocloseDefaultDelaySeconds; |
40 if (notification->priority() > DEFAULT_PRIORITY) | 43 if (notification->priority() > DEFAULT_PRIORITY) |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
138 | 141 |
139 virtual gfx::Size GetPreferredSize() OVERRIDE { | 142 virtual gfx::Size GetPreferredSize() OVERRIDE { |
140 if (child_count() == 0) | 143 if (child_count() == 0) |
141 return gfx::Size(); | 144 return gfx::Size(); |
142 | 145 |
143 return gfx::Size(kNotificationWidth, | 146 return gfx::Size(kNotificationWidth, |
144 child_at(0)->GetHeightForWidth(kNotificationWidth)); | 147 child_at(0)->GetHeightForWidth(kNotificationWidth)); |
145 } | 148 } |
146 | 149 |
147 private: | 150 private: |
| 151 std::string id_; |
148 base::TimeDelta delay_; | 152 base::TimeDelta delay_; |
149 base::Time start_time_; | 153 base::Time start_time_; |
150 scoped_ptr<base::OneShotTimer<views::Widget> > timer_; | 154 scoped_ptr<base::OneShotTimer<views::Widget> > timer_; |
151 base::WeakPtr<MessagePopupCollection> collection_; | 155 base::WeakPtr<MessagePopupCollection> collection_; |
| 156 MessageCenter* message_center_; |
152 | 157 |
153 DISALLOW_COPY_AND_ASSIGN(ToastContentsView); | 158 DISALLOW_COPY_AND_ASSIGN(ToastContentsView); |
154 }; | 159 }; |
155 | 160 |
156 MessagePopupCollection::MessagePopupCollection(gfx::NativeView parent, | 161 MessagePopupCollection::MessagePopupCollection(gfx::NativeView parent, |
157 MessageCenter* message_center) | 162 MessageCenter* message_center) |
158 : parent_(parent), | 163 : parent_(parent), |
159 message_center_(message_center) { | 164 message_center_(message_center) { |
160 DCHECK(message_center_); | 165 DCHECK(message_center_); |
161 UpdatePopups(); | 166 |
| 167 UpdateWidgets(); |
| 168 message_center_->AddObserver(this); |
162 } | 169 } |
163 | 170 |
164 MessagePopupCollection::~MessagePopupCollection() { | 171 MessagePopupCollection::~MessagePopupCollection() { |
| 172 message_center_->RemoveObserver(this); |
165 CloseAllWidgets(); | 173 CloseAllWidgets(); |
166 } | 174 } |
167 | 175 |
168 void MessagePopupCollection::UpdatePopups() { | 176 void MessagePopupCollection::UpdateWidgets() { |
169 NotificationList::PopupNotifications popups = | 177 NotificationList::PopupNotifications popups = |
170 message_center_->GetPopupNotifications(); | 178 message_center_->GetPopupNotifications(); |
171 | 179 |
172 if (popups.empty()) { | 180 if (popups.empty()) { |
173 CloseAllWidgets(); | 181 CloseAllWidgets(); |
174 return; | 182 return; |
175 } | 183 } |
176 | 184 |
177 gfx::Rect work_area; | 185 gfx::Point base_position = GetWorkAreaBottomRight(); |
178 if (!parent_) { | 186 int bottom = widgets_.empty() ? |
179 // On Win+Aura, we don't have a parent since the popups currently show up | 187 base_position.y() : widgets_.back()->GetWindowBoundsInScreen().y(); |
180 // on the Windows desktop, not in the Aura/Ash desktop. This code will | 188 bottom -= kMarginBetweenItems; |
181 // display the popups on the primary display. | 189 int left = base_position.x() - kNotificationWidth - kMarginBetweenItems; |
182 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); | |
183 work_area = screen->GetPrimaryDisplay().work_area(); | |
184 } else { | |
185 gfx::Screen* screen = gfx::Screen::GetScreenFor(parent_); | |
186 work_area = screen->GetDisplayNearestWindow(parent_).work_area(); | |
187 } | |
188 | |
189 std::set<std::string> old_toast_ids; | |
190 for (ToastContainer::iterator iter = toasts_.begin(); iter != toasts_.end(); | |
191 ++iter) { | |
192 old_toast_ids.insert(iter->first); | |
193 } | |
194 | |
195 int bottom = work_area.bottom() - kMarginBetweenItems; | |
196 int left = work_area.right() - kNotificationWidth - kMarginBetweenItems; | |
197 // Iterate in the reverse order to keep the oldest toasts on screen. Newer | 190 // Iterate in the reverse order to keep the oldest toasts on screen. Newer |
198 // items may be ignored if there are no room to place them. | 191 // items may be ignored if there are no room to place them. |
199 for (NotificationList::PopupNotifications::const_reverse_iterator iter = | 192 for (NotificationList::PopupNotifications::const_reverse_iterator iter = |
200 popups.rbegin(); iter != popups.rend(); ++iter) { | 193 popups.rbegin(); iter != popups.rend(); ++iter) { |
201 MessageView* view = | 194 MessageView* view = |
202 NotificationView::Create(*(*iter), message_center_, true); | 195 NotificationView::Create(*(*iter), message_center_, true); |
203 int view_height = view->GetHeightForWidth(kNotificationWidth); | 196 int view_height = view->GetHeightForWidth(kNotificationWidth); |
204 if (bottom - view_height - kMarginBetweenItems < 0) { | 197 if (bottom - view_height - kMarginBetweenItems < 0) { |
205 delete view; | 198 delete view; |
206 break; | 199 break; |
207 } | 200 } |
208 | 201 |
209 ToastContainer::iterator toast_iter = toasts_.find((*iter)->id()); | 202 if (toasts_.find((*iter)->id()) != toasts_.end()) { |
210 views::Widget* widget = NULL; | 203 delete view; |
211 if (toast_iter != toasts_.end()) { | 204 continue; |
212 widget = toast_iter->second->GetWidget(); | |
213 old_toast_ids.erase((*iter)->id()); | |
214 // Need to replace the contents because |view| can be updated, like | |
215 // image loads. | |
216 toast_iter->second->SetContents(view); | |
217 } else { | |
218 ToastContentsView* toast = new ToastContentsView(*iter, AsWeakPtr()); | |
219 widget = toast->CreateWidget(parent_); | |
220 toast->SetContents(view); | |
221 widget->AddObserver(this); | |
222 toast->StartTimer(); | |
223 toasts_[(*iter)->id()] = toast; | |
224 } | 205 } |
225 | 206 |
| 207 ToastContentsView* toast = new ToastContentsView( |
| 208 *iter, AsWeakPtr(), message_center_); |
| 209 views::Widget* widget = toast->CreateWidget(parent_); |
| 210 toast->SetContents(view); |
| 211 widget->AddObserver(this); |
| 212 toast->StartTimer(); |
| 213 toasts_[(*iter)->id()] = toast; |
| 214 widgets_.push_back(widget); |
| 215 |
226 // Place/move the toast widgets. Currently it stacks the widgets from the | 216 // Place/move the toast widgets. Currently it stacks the widgets from the |
227 // right-bottom of the work area. | 217 // right-bottom of the work area. |
228 // TODO(mukai): allow to specify the placement policy from outside of this | 218 // TODO(mukai): allow to specify the placement policy from outside of this |
229 // class. The policy should be specified from preference on Windows, or | 219 // class. The policy should be specified from preference on Windows, or |
230 // the launcher alignment on ChromeOS. | 220 // the launcher alignment on ChromeOS. |
231 if (widget) { | 221 gfx::Rect bounds(widget->GetWindowBoundsInScreen()); |
232 gfx::Rect bounds(widget->GetWindowBoundsInScreen()); | 222 bounds.set_origin(gfx::Point(left, bottom - bounds.height())); |
233 bounds.set_origin(gfx::Point(left, bottom - bounds.height())); | 223 widget->SetBounds(bounds); |
234 widget->SetBounds(bounds); | 224 widget->Show(); |
235 if (!widget->IsVisible()) | |
236 widget->Show(); | |
237 } | |
238 | |
239 bottom -= view_height + kMarginBetweenItems; | 225 bottom -= view_height + kMarginBetweenItems; |
240 } | 226 } |
241 | |
242 for (std::set<std::string>::const_iterator iter = old_toast_ids.begin(); | |
243 iter != old_toast_ids.end(); ++iter) { | |
244 ToastContainer::iterator toast_iter = toasts_.find(*iter); | |
245 DCHECK(toast_iter != toasts_.end()); | |
246 views::Widget* widget = toast_iter->second->GetWidget(); | |
247 widget->RemoveObserver(this); | |
248 widget->Close(); | |
249 toasts_.erase(toast_iter); | |
250 } | |
251 } | 227 } |
252 | 228 |
253 void MessagePopupCollection::OnMouseEntered() { | 229 void MessagePopupCollection::OnMouseEntered() { |
254 for (ToastContainer::iterator iter = toasts_.begin(); | 230 for (ToastContainer::iterator iter = toasts_.begin(); |
255 iter != toasts_.end(); ++iter) { | 231 iter != toasts_.end(); ++iter) { |
256 iter->second->SuspendTimer(); | 232 iter->second->SuspendTimer(); |
257 } | 233 } |
258 } | 234 } |
259 | 235 |
260 void MessagePopupCollection::OnMouseExited() { | 236 void MessagePopupCollection::OnMouseExited() { |
261 for (ToastContainer::iterator iter = toasts_.begin(); | 237 for (ToastContainer::iterator iter = toasts_.begin(); |
262 iter != toasts_.end(); ++iter) { | 238 iter != toasts_.end(); ++iter) { |
263 iter->second->RestartTimer(); | 239 iter->second->RestartTimer(); |
264 } | 240 } |
| 241 RepositionWidgets(); |
| 242 // Reposition could create extra space which allows additional widgets. |
| 243 UpdateWidgets(); |
265 } | 244 } |
266 | 245 |
267 void MessagePopupCollection::CloseAllWidgets() { | 246 void MessagePopupCollection::CloseAllWidgets() { |
268 for (ToastContainer::iterator iter = toasts_.begin(); | 247 for (ToastContainer::iterator iter = toasts_.begin(); |
269 iter != toasts_.end(); ++iter) { | 248 iter != toasts_.end(); ++iter) { |
270 iter->second->SuspendTimer(); | 249 iter->second->SuspendTimer(); |
271 views::Widget* widget = iter->second->GetWidget(); | 250 views::Widget* widget = iter->second->GetWidget(); |
272 widget->RemoveObserver(this); | 251 widget->RemoveObserver(this); |
273 widget->Close(); | 252 widget->Close(); |
274 } | 253 } |
275 toasts_.clear(); | 254 toasts_.clear(); |
| 255 widgets_.clear(); |
276 } | 256 } |
277 | 257 |
278 void MessagePopupCollection::OnWidgetDestroying(views::Widget* widget) { | 258 void MessagePopupCollection::OnWidgetDestroying(views::Widget* widget) { |
279 widget->RemoveObserver(this); | 259 widget->RemoveObserver(this); |
280 for (ToastContainer::iterator iter = toasts_.begin(); | 260 for (ToastContainer::iterator iter = toasts_.begin(); |
281 iter != toasts_.end(); ++iter) { | 261 iter != toasts_.end(); ++iter) { |
282 if (iter->second->GetWidget() == widget) { | 262 if (iter->second->GetWidget() == widget) { |
283 message_center_->MarkSinglePopupAsShown(iter->first, false); | 263 message_center_->MarkSinglePopupAsShown(iter->first, false); |
284 toasts_.erase(iter); | 264 toasts_.erase(iter); |
285 break; | 265 break; |
286 } | 266 } |
287 } | 267 } |
288 UpdatePopups(); | 268 widgets_.erase(std::find(widgets_.begin(), widgets_.end(), widget)); |
| 269 RepositionWidgets(); |
| 270 UpdateWidgets(); |
| 271 } |
| 272 |
| 273 gfx::Point MessagePopupCollection::GetWorkAreaBottomRight() { |
| 274 if (!work_area_.IsEmpty()) |
| 275 return work_area_.bottom_right(); |
| 276 |
| 277 if (!parent_) { |
| 278 // On Win+Aura, we don't have a parent since the popups currently show up |
| 279 // on the Windows desktop, not in the Aura/Ash desktop. This code will |
| 280 // display the popups on the primary display. |
| 281 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); |
| 282 work_area_ = screen->GetPrimaryDisplay().work_area(); |
| 283 } else { |
| 284 gfx::Screen* screen = gfx::Screen::GetScreenFor(parent_); |
| 285 work_area_ = screen->GetDisplayNearestWindow(parent_).work_area(); |
| 286 } |
| 287 |
| 288 return work_area_.bottom_right(); |
| 289 } |
| 290 |
| 291 void MessagePopupCollection::RepositionWidgets() { |
| 292 int bottom = GetWorkAreaBottomRight().y() - kMarginBetweenItems; |
| 293 for (std::list<views::Widget*>::iterator iter = widgets_.begin(); |
| 294 iter != widgets_.end(); ++iter) { |
| 295 gfx::Rect bounds((*iter)->GetWindowBoundsInScreen()); |
| 296 bounds.set_y(bottom - bounds.height()); |
| 297 (*iter)->SetBounds(bounds); |
| 298 bottom -= bounds.height() + kMarginBetweenItems; |
| 299 } |
| 300 } |
| 301 |
| 302 void MessagePopupCollection::RepositionWidgetsWithTarget( |
| 303 const gfx::Rect& target_bounds) { |
| 304 if (widgets_.empty()) |
| 305 return; |
| 306 |
| 307 if (widgets_.back()->GetWindowBoundsInScreen().y() > target_bounds.y()) { |
| 308 // No widgets are above, thus slides up the widgets. |
| 309 int slide_length = |
| 310 widgets_.back()->GetWindowBoundsInScreen().y() - target_bounds.y(); |
| 311 for (std::list<views::Widget*>::iterator iter = widgets_.begin(); |
| 312 iter != widgets_.end(); ++iter) { |
| 313 gfx::Rect bounds((*iter)->GetWindowBoundsInScreen()); |
| 314 bounds.set_y(bounds.y() - slide_length); |
| 315 (*iter)->SetBounds(bounds); |
| 316 } |
| 317 } else { |
| 318 std::list<views::Widget*>::reverse_iterator iter = widgets_.rbegin(); |
| 319 for (; iter != widgets_.rend(); ++iter) { |
| 320 if ((*iter)->GetWindowBoundsInScreen().y() > target_bounds.y()) |
| 321 break; |
| 322 } |
| 323 --iter; |
| 324 int slide_length = |
| 325 target_bounds.y() - (*iter)->GetWindowBoundsInScreen().y(); |
| 326 for (; ; --iter) { |
| 327 gfx::Rect bounds((*iter)->GetWindowBoundsInScreen()); |
| 328 bounds.set_y(bounds.y() + slide_length); |
| 329 (*iter)->SetBounds(bounds); |
| 330 |
| 331 if (iter == widgets_.rbegin()) |
| 332 break; |
| 333 } |
| 334 } |
| 335 } |
| 336 |
| 337 void MessagePopupCollection::OnNotificationAdded( |
| 338 const std::string& notification_id) { |
| 339 UpdateWidgets(); |
| 340 } |
| 341 |
| 342 void MessagePopupCollection::OnNotificationRemoved( |
| 343 const std::string& notification_id, |
| 344 bool by_user) { |
| 345 ToastContainer::iterator iter = toasts_.find(notification_id); |
| 346 if (iter == toasts_.end()) |
| 347 return; |
| 348 |
| 349 views::Widget* widget = iter->second->GetWidget(); |
| 350 gfx::Rect removed_bounds = widget->GetWindowBoundsInScreen(); |
| 351 widget->RemoveObserver(this); |
| 352 widget->Close(); |
| 353 widgets_.erase(std::find(widgets_.begin(), widgets_.end(), widget)); |
| 354 toasts_.erase(iter); |
| 355 bool widgets_went_empty = widgets_.empty(); |
| 356 if (by_user) |
| 357 RepositionWidgetsWithTarget(removed_bounds); |
| 358 else |
| 359 RepositionWidgets(); |
| 360 |
| 361 // A notification removal may create extra space which allows appearing |
| 362 // other notifications. |
| 363 UpdateWidgets(); |
| 364 |
| 365 // Also, if the removed notification is the last one but removing that enables |
| 366 // other notifications appearing, the newly created widgets also have to be |
| 367 // repositioned. |
| 368 if (by_user && widgets_went_empty) |
| 369 RepositionWidgetsWithTarget(removed_bounds); |
| 370 } |
| 371 |
| 372 void MessagePopupCollection::OnNotificationUpdated( |
| 373 const std::string& notification_id) { |
| 374 ToastContainer::iterator toast_iter = toasts_.find(notification_id); |
| 375 if (toast_iter == toasts_.end()) |
| 376 return; |
| 377 |
| 378 NotificationList::Notifications notifications = |
| 379 message_center_->GetNotifications(); |
| 380 bool updated = false; |
| 381 for (NotificationList::Notifications::iterator iter = |
| 382 notifications.begin(); iter != notifications.end(); ++iter) { |
| 383 if ((*iter)->id() != notification_id) |
| 384 continue; |
| 385 |
| 386 MessageView* view = NotificationView::Create( |
| 387 *(*iter), message_center_, true); |
| 388 toast_iter->second->SetContents(view); |
| 389 updated = true; |
| 390 } |
| 391 |
| 392 if (updated) { |
| 393 RepositionWidgets(); |
| 394 // Reposition could create extra space which allows additional widgets. |
| 395 UpdateWidgets(); |
| 396 } |
| 397 } |
| 398 |
| 399 void MessagePopupCollection::SetWorkAreaForTest(const gfx::Rect& work_area) { |
| 400 work_area_ = work_area; |
289 } | 401 } |
290 | 402 |
291 } // namespace message_center | 403 } // namespace message_center |
OLD | NEW |