OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 "chrome/browser/notifications/balloon_collection_impl.h" | 5 #include "chrome/browser/notifications/balloon_collection_impl.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/logging.h" | 8 #include "base/logging.h" |
9 #include "base/stl_util.h" | 9 #include "base/stl_util.h" |
10 #include "chrome/browser/notifications/balloon.h" | 10 #include "chrome/browser/notifications/balloon.h" |
11 #include "chrome/browser/notifications/balloon_host.h" | 11 #include "chrome/browser/notifications/balloon_host.h" |
12 #include "chrome/browser/notifications/notification.h" | 12 #include "chrome/browser/notifications/notification.h" |
13 #include "chrome/browser/ui/browser.h" | |
14 #include "chrome/browser/ui/panels/panel_manager.h" | |
15 #include "chrome/browser/ui/panels/panel.h" | |
13 #include "chrome/browser/ui/window_sizer.h" | 16 #include "chrome/browser/ui/window_sizer.h" |
17 #include "chrome/common/chrome_notification_types.h" | |
18 #include "content/public/browser/notification_registrar.h" | |
19 #include "content/public/browser/notification_service.h" | |
14 #include "ui/gfx/rect.h" | 20 #include "ui/gfx/rect.h" |
15 #include "ui/gfx/size.h" | 21 #include "ui/gfx/size.h" |
16 | 22 |
17 namespace { | 23 namespace { |
18 | 24 |
19 // Portion of the screen allotted for notifications. When notification balloons | 25 // Portion of the screen allotted for notifications. When notification balloons |
20 // extend over this, no new notifications are shown until some are closed. | 26 // extend over this, no new notifications are shown until some are closed. |
21 const double kPercentBalloonFillFactor = 0.7; | 27 const double kPercentBalloonFillFactor = 0.7; |
22 | 28 |
23 // Allow at least this number of balloons on the screen. | 29 // Allow at least this number of balloons on the screen. |
24 const int kMinAllowedBalloonCount = 2; | 30 const int kMinAllowedBalloonCount = 2; |
25 | 31 |
26 // Delay from the mouse leaving the balloon collection before | 32 // Delay from the mouse leaving the balloon collection before |
27 // there is a relayout, in milliseconds. | 33 // there is a relayout, in milliseconds. |
28 const int kRepositionDelay = 300; | 34 const int kRepositionDelay = 300; |
29 | 35 |
36 // The spacing between the balloon and the panel. | |
37 const int kVerticalSpacingBetweenBalloonAndPanel = 5; | |
38 | |
30 } // namespace | 39 } // namespace |
31 | 40 |
32 BalloonCollectionImpl::BalloonCollectionImpl() | 41 BalloonCollectionImpl::BalloonCollectionImpl() |
33 #if USE_OFFSETS | 42 #if USE_OFFSETS |
34 : ALLOW_THIS_IN_INITIALIZER_LIST(reposition_factory_(this)), | 43 : ALLOW_THIS_IN_INITIALIZER_LIST(reposition_factory_(this)), |
35 added_as_message_loop_observer_(false) | 44 added_as_message_loop_observer_(false) |
36 #endif | 45 #endif |
37 { | 46 { |
47 registrar_.Add(this, chrome::NOTIFICATION_PANEL_ADDED, | |
48 content::NotificationService::AllSources()); | |
49 registrar_.Add(this, chrome::NOTIFICATION_PANEL_REMOVED, | |
50 content::NotificationService::AllSources()); | |
51 registrar_.Add(this, chrome::NOTIFICATION_PANEL_CHANGED_BOUNDS, | |
52 content::NotificationService::AllSources()); | |
38 | 53 |
39 SetPositionPreference(BalloonCollection::DEFAULT_POSITION); | 54 SetPositionPreference(BalloonCollection::DEFAULT_POSITION); |
40 } | 55 } |
41 | 56 |
42 BalloonCollectionImpl::~BalloonCollectionImpl() { | 57 BalloonCollectionImpl::~BalloonCollectionImpl() { |
43 } | 58 } |
44 | 59 |
45 void BalloonCollectionImpl::Add(const Notification& notification, | 60 void BalloonCollectionImpl::Add(const Notification& notification, |
46 Profile* profile) { | 61 Profile* profile) { |
47 Balloon* new_balloon = MakeBalloon(notification, profile); | 62 Balloon* new_balloon = MakeBalloon(notification, profile); |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
146 | 161 |
147 // This is used only for testing. | 162 // This is used only for testing. |
148 if (!on_collection_changed_callback_.is_null()) | 163 if (!on_collection_changed_callback_.is_null()) |
149 on_collection_changed_callback_.Run(); | 164 on_collection_changed_callback_.Run(); |
150 } | 165 } |
151 | 166 |
152 const BalloonCollection::Balloons& BalloonCollectionImpl::GetActiveBalloons() { | 167 const BalloonCollection::Balloons& BalloonCollectionImpl::GetActiveBalloons() { |
153 return base_.balloons(); | 168 return base_.balloons(); |
154 } | 169 } |
155 | 170 |
171 void BalloonCollectionImpl::Observe( | |
172 int type, | |
173 const content::NotificationSource& source, | |
174 const content::NotificationDetails& details) { | |
175 gfx::Rect bounds; | |
176 switch (type) { | |
177 case chrome::NOTIFICATION_PANEL_CHANGED_BOUNDS: | |
178 bounds = content::Source<Panel>(source).ptr()->GetBounds(); | |
179 // Fall through. | |
180 case chrome::NOTIFICATION_PANEL_ADDED: | |
181 case chrome::NOTIFICATION_PANEL_REMOVED: | |
182 if (layout_.ComputeOffsetToMoveAbovePanels(bounds)) | |
183 PositionBalloons(true); | |
184 break; | |
185 default: | |
186 NOTREACHED(); | |
187 break; | |
188 } | |
189 } | |
190 | |
156 void BalloonCollectionImpl::PositionBalloonsInternal(bool reposition) { | 191 void BalloonCollectionImpl::PositionBalloonsInternal(bool reposition) { |
157 const Balloons& balloons = base_.balloons(); | 192 const Balloons& balloons = base_.balloons(); |
158 | 193 |
159 layout_.RefreshSystemMetrics(); | 194 layout_.RefreshSystemMetrics(); |
160 gfx::Point origin = layout_.GetLayoutOrigin(); | 195 gfx::Point origin = layout_.GetLayoutOrigin(); |
161 for (Balloons::const_iterator it = balloons.begin(); | 196 for (Balloons::const_iterator it = balloons.begin(); |
162 it != balloons.end(); | 197 it != balloons.end(); |
163 ++it) { | 198 ++it) { |
164 gfx::Point upper_left = layout_.NextPosition((*it)->GetViewSize(), &origin); | 199 gfx::Point upper_left = layout_.NextPosition((*it)->GetViewSize(), &origin); |
165 (*it)->SetPosition(upper_left, reposition); | 200 (*it)->SetPosition(upper_left, reposition); |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
224 reposition_factory_.GetWeakPtr()), | 259 reposition_factory_.GetWeakPtr()), |
225 kRepositionDelay); | 260 kRepositionDelay); |
226 } | 261 } |
227 } else { | 262 } else { |
228 // Mouse moved back into the region. Cancel the reposition. | 263 // Mouse moved back into the region. Cancel the reposition. |
229 reposition_factory_.InvalidateWeakPtrs(); | 264 reposition_factory_.InvalidateWeakPtrs(); |
230 } | 265 } |
231 } | 266 } |
232 #endif | 267 #endif |
233 | 268 |
234 BalloonCollectionImpl::Layout::Layout() : placement_(INVALID) { | 269 BalloonCollectionImpl::Layout::Layout() |
270 : placement_(INVALID), | |
271 offset_to_move_above_panels_(0) { | |
235 RefreshSystemMetrics(); | 272 RefreshSystemMetrics(); |
236 } | 273 } |
237 | 274 |
238 void BalloonCollectionImpl::Layout::GetMaxLinearSize(int* max_balloon_size, | 275 void BalloonCollectionImpl::Layout::GetMaxLinearSize(int* max_balloon_size, |
239 int* total_size) const { | 276 int* total_size) const { |
240 DCHECK(max_balloon_size && total_size); | 277 DCHECK(max_balloon_size && total_size); |
241 | 278 |
242 // All placement schemes are vertical, so we only care about height. | 279 // All placement schemes are vertical, so we only care about height. |
243 *total_size = work_area_.height(); | 280 *total_size = work_area_.height(); |
244 *max_balloon_size = max_balloon_height(); | 281 *max_balloon_size = max_balloon_height(); |
245 } | 282 } |
246 | 283 |
247 gfx::Point BalloonCollectionImpl::Layout::GetLayoutOrigin() const { | 284 gfx::Point BalloonCollectionImpl::Layout::GetLayoutOrigin() const { |
285 // For lower-left and lower-right positioning, we need to add an offset | |
286 // to ensure balloons to stay on top of panels to avoid overlapping. | |
248 int x = 0; | 287 int x = 0; |
249 int y = 0; | 288 int y = 0; |
250 switch (placement_) { | 289 switch (placement_) { |
251 case VERTICALLY_FROM_TOP_LEFT: | 290 case VERTICALLY_FROM_TOP_LEFT: { |
252 x = work_area_.x() + HorizontalEdgeMargin(); | 291 x = work_area_.x() + HorizontalEdgeMargin(); |
253 y = work_area_.y() + VerticalEdgeMargin(); | 292 y = work_area_.y() + VerticalEdgeMargin() + offset_to_move_above_panels_; |
254 break; | 293 break; |
255 case VERTICALLY_FROM_TOP_RIGHT: | 294 } |
295 case VERTICALLY_FROM_TOP_RIGHT: { | |
256 x = work_area_.right() - HorizontalEdgeMargin(); | 296 x = work_area_.right() - HorizontalEdgeMargin(); |
257 y = work_area_.y() + VerticalEdgeMargin(); | 297 y = work_area_.y() + VerticalEdgeMargin() + offset_to_move_above_panels_; |
258 break; | 298 break; |
299 } | |
259 case VERTICALLY_FROM_BOTTOM_LEFT: | 300 case VERTICALLY_FROM_BOTTOM_LEFT: |
260 x = work_area_.x() + HorizontalEdgeMargin(); | 301 x = work_area_.x() + HorizontalEdgeMargin(); |
261 y = work_area_.bottom() - VerticalEdgeMargin(); | 302 y = work_area_.bottom() - VerticalEdgeMargin() - |
303 offset_to_move_above_panels_; | |
262 break; | 304 break; |
263 case VERTICALLY_FROM_BOTTOM_RIGHT: | 305 case VERTICALLY_FROM_BOTTOM_RIGHT: |
264 x = work_area_.right() - HorizontalEdgeMargin(); | 306 x = work_area_.right() - HorizontalEdgeMargin(); |
265 y = work_area_.bottom() - VerticalEdgeMargin(); | 307 y = work_area_.bottom() - VerticalEdgeMargin() - |
308 offset_to_move_above_panels_; | |
266 break; | 309 break; |
267 default: | 310 default: |
268 NOTREACHED(); | 311 NOTREACHED(); |
269 break; | 312 break; |
270 } | 313 } |
271 return gfx::Point(x, y); | 314 return gfx::Point(x, y); |
272 } | 315 } |
273 | 316 |
274 gfx::Point BalloonCollectionImpl::Layout::NextPosition( | 317 gfx::Point BalloonCollectionImpl::Layout::NextPosition( |
275 const gfx::Size& balloon_size, | 318 const gfx::Size& balloon_size, |
(...skipping 28 matching lines...) Expand all Loading... | |
304 y = position_iterator->y(); | 347 y = position_iterator->y(); |
305 break; | 348 break; |
306 default: | 349 default: |
307 NOTREACHED(); | 350 NOTREACHED(); |
308 break; | 351 break; |
309 } | 352 } |
310 return gfx::Point(x, y); | 353 return gfx::Point(x, y); |
311 } | 354 } |
312 | 355 |
313 gfx::Point BalloonCollectionImpl::Layout::OffScreenLocation() const { | 356 gfx::Point BalloonCollectionImpl::Layout::OffScreenLocation() const { |
314 int x = 0; | 357 gfx::Point location = GetLayoutOrigin(); |
315 int y = 0; | |
316 switch (placement_) { | 358 switch (placement_) { |
317 case VERTICALLY_FROM_TOP_LEFT: | 359 case VERTICALLY_FROM_TOP_LEFT: |
318 x = work_area_.x() + HorizontalEdgeMargin(); | 360 case VERTICALLY_FROM_BOTTOM_LEFT: |
319 y = work_area_.y() + kBalloonMaxHeight + VerticalEdgeMargin(); | 361 location.Offset(0, kBalloonMaxHeight); |
320 break; | 362 break; |
321 case VERTICALLY_FROM_TOP_RIGHT: | 363 case VERTICALLY_FROM_TOP_RIGHT: |
322 x = work_area_.right() - kBalloonMaxWidth - HorizontalEdgeMargin(); | |
323 y = work_area_.y() + kBalloonMaxHeight + VerticalEdgeMargin(); | |
324 break; | |
325 case VERTICALLY_FROM_BOTTOM_LEFT: | |
326 x = work_area_.x() + HorizontalEdgeMargin(); | |
327 y = work_area_.bottom() + kBalloonMaxHeight + VerticalEdgeMargin(); | |
328 break; | |
329 case VERTICALLY_FROM_BOTTOM_RIGHT: | 364 case VERTICALLY_FROM_BOTTOM_RIGHT: |
330 x = work_area_.right() - kBalloonMaxWidth - HorizontalEdgeMargin(); | 365 location.Offset(-kBalloonMaxWidth, kBalloonMaxHeight); |
331 y = work_area_.bottom() + kBalloonMaxHeight + VerticalEdgeMargin(); | |
332 break; | 366 break; |
333 default: | 367 default: |
334 NOTREACHED(); | 368 NOTREACHED(); |
335 break; | 369 break; |
336 } | 370 } |
337 return gfx::Point(x, y); | 371 return location; |
338 } | 372 } |
339 | 373 |
340 bool BalloonCollectionImpl::Layout::RequiresOffsets() const { | 374 bool BalloonCollectionImpl::Layout::RequiresOffsets() const { |
341 // Layout schemes that grow up from the bottom require offsets; | 375 // Layout schemes that grow up from the bottom require offsets; |
342 // schemes that grow down do not require offsets. | 376 // schemes that grow down do not require offsets. |
343 bool offsets = (placement_ == VERTICALLY_FROM_BOTTOM_LEFT || | 377 bool offsets = (placement_ == VERTICALLY_FROM_BOTTOM_LEFT || |
344 placement_ == VERTICALLY_FROM_BOTTOM_RIGHT); | 378 placement_ == VERTICALLY_FROM_BOTTOM_RIGHT); |
345 | 379 |
346 #if defined(OS_MACOSX) | 380 #if defined(OS_MACOSX) |
347 // These schemes are in screen-coordinates, and top and bottom | 381 // These schemes are in screen-coordinates, and top and bottom |
348 // are inverted on Mac. | 382 // are inverted on Mac. |
349 offsets = !offsets; | 383 offsets = !offsets; |
350 #endif | 384 #endif |
351 | 385 |
352 return offsets; | 386 return offsets; |
353 } | 387 } |
354 | 388 |
355 // static | 389 // static |
356 gfx::Size BalloonCollectionImpl::Layout::ConstrainToSizeLimits( | 390 gfx::Size BalloonCollectionImpl::Layout::ConstrainToSizeLimits( |
357 const gfx::Size& size) { | 391 const gfx::Size& size) { |
358 // restrict to the min & max sizes | 392 // restrict to the min & max sizes |
359 return gfx::Size( | 393 return gfx::Size( |
360 std::max(min_balloon_width(), | 394 std::max(min_balloon_width(), |
361 std::min(max_balloon_width(), size.width())), | 395 std::min(max_balloon_width(), size.width())), |
362 std::max(min_balloon_height(), | 396 std::max(min_balloon_height(), |
363 std::min(max_balloon_height(), size.height()))); | 397 std::min(max_balloon_height(), size.height()))); |
364 } | 398 } |
365 | 399 |
400 bool BalloonCollectionImpl::Layout::ComputeOffsetToMoveAbovePanels( | |
401 const gfx::Rect& panel_bounds) { | |
402 const PanelManager::Panels& panels = PanelManager::GetInstance()->panels(); | |
403 int offset_to_move_above_panels = 0; | |
404 | |
405 // The offset is the maximum height of panels that could overlap with the | |
406 // balloons. | |
407 if (NeedToMoveAboveLeftSidePanels()) { | |
408 // If the affecting panel does not lie in the balloon area, no need to | |
409 // update the offset. | |
410 if (!panel_bounds.IsEmpty() && | |
411 (panel_bounds.right() <= work_area_.x() || | |
412 panel_bounds.x() >= work_area_.x() + max_balloon_width())) { | |
413 return false; | |
414 } | |
415 | |
416 for (PanelManager::Panels::const_reverse_iterator iter = panels.rbegin(); | |
417 iter != panels.rend(); ++iter) { | |
418 // No need to check panels beyond the area occupied by the balloons. | |
419 if ((*iter)->GetBounds().x() >= work_area_.x() + max_balloon_width()) | |
jennb
2011/11/11 23:02:43
FYI, until your overflow changes are in, this will
| |
420 break; | |
421 | |
422 int current_height = (*iter)->GetBounds().height(); | |
423 if (current_height > offset_to_move_above_panels) | |
424 offset_to_move_above_panels = current_height; | |
425 } | |
426 } else if (NeedToMoveAboveRightSidePanels()) { | |
427 // If the affecting panel does not lie in the balloon area, no need to | |
428 // update the offset. | |
429 if (!panel_bounds.IsEmpty() && | |
430 (panel_bounds.right() <= work_area_.right() - max_balloon_width() || | |
431 panel_bounds.x() >= work_area_.right())) { | |
432 return false; | |
433 } | |
434 | |
435 for (PanelManager::Panels::const_iterator iter = panels.begin(); | |
436 iter != panels.end(); ++iter) { | |
437 // No need to check panels beyond the area occupied by the balloons. | |
438 if ((*iter)->GetBounds().right() <= | |
439 work_area_.right() - max_balloon_width()) | |
440 break; | |
441 | |
442 int current_height = (*iter)->GetBounds().height(); | |
443 if (current_height > offset_to_move_above_panels) | |
444 offset_to_move_above_panels = current_height; | |
445 } | |
446 } | |
447 | |
448 // Ensure that we have some sort of margin between the 1st balloon and the | |
449 // panel beneath it even the vertical edge margin is 0 as on Mac. | |
450 if (offset_to_move_above_panels && !VerticalEdgeMargin()) | |
451 offset_to_move_above_panels += kVerticalSpacingBetweenBalloonAndPanel; | |
452 | |
453 // If no change is detected, return false to indicate that we do not need to | |
454 // reposition balloons. | |
455 if (offset_to_move_above_panels_ == offset_to_move_above_panels) | |
456 return false; | |
457 | |
458 offset_to_move_above_panels_ = offset_to_move_above_panels; | |
459 return true; | |
460 } | |
461 | |
366 bool BalloonCollectionImpl::Layout::RefreshSystemMetrics() { | 462 bool BalloonCollectionImpl::Layout::RefreshSystemMetrics() { |
367 bool changed = false; | 463 bool changed = false; |
368 | 464 |
369 #if defined(OS_MACOSX) | 465 #if defined(OS_MACOSX) |
370 gfx::Rect new_work_area = GetMacWorkArea(); | 466 gfx::Rect new_work_area = GetMacWorkArea(); |
371 #else | 467 #else |
372 scoped_ptr<WindowSizer::MonitorInfoProvider> info_provider( | 468 scoped_ptr<WindowSizer::MonitorInfoProvider> info_provider( |
373 WindowSizer::CreateDefaultMonitorInfoProvider()); | 469 WindowSizer::CreateDefaultMonitorInfoProvider()); |
374 gfx::Rect new_work_area = info_provider->GetPrimaryMonitorWorkArea(); | 470 gfx::Rect new_work_area = info_provider->GetPrimaryMonitorWorkArea(); |
375 #endif | 471 #endif |
376 if (!work_area_.Equals(new_work_area)) { | 472 if (!work_area_.Equals(new_work_area)) { |
377 work_area_.SetRect(new_work_area.x(), new_work_area.y(), | 473 work_area_.SetRect(new_work_area.x(), new_work_area.y(), |
378 new_work_area.width(), new_work_area.height()); | 474 new_work_area.width(), new_work_area.height()); |
379 changed = true; | 475 changed = true; |
380 } | 476 } |
381 | 477 |
382 return changed; | 478 return changed; |
383 } | 479 } |
OLD | NEW |