Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(76)

Side by Side Diff: ui/message_center/views/message_popup_collection.cc

Issue 14139014: Reposition toasts to align the closed one. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix timeout Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
OLDNEW
« no previous file with comments | « ui/message_center/views/message_popup_collection.h ('k') | ui/message_center/views/message_popup_collection_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698