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

Side by Side Diff: chrome/browser/notifications/balloon_collection_impl.cc

Issue 231723006: Remove balloon notification code. The last user was the Linux GTK port but that's deleted now. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: sync after elliot's r263101 Created 6 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
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/notifications/balloon_collection_impl.h"
6
7 #include "base/bind.h"
8 #include "base/logging.h"
9 #include "base/stl_util.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/notifications/balloon.h"
12 #include "chrome/browser/notifications/balloon_host.h"
13 #include "chrome/browser/notifications/notification.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/panels/docked_panel_collection.h"
16 #include "chrome/browser/ui/panels/panel.h"
17 #include "chrome/browser/ui/panels/panel_manager.h"
18 #include "content/public/browser/notification_registrar.h"
19 #include "content/public/browser/notification_service.h"
20 #include "ui/gfx/rect.h"
21 #include "ui/gfx/screen.h"
22 #include "ui/gfx/size.h"
23
24 // Portion of the screen allotted for notifications. When notification balloons
25 // extend over this, no new notifications are shown until some are closed.
26 const double kPercentBalloonFillFactor = 0.7;
27
28 // Allow at least this number of balloons on the screen.
29 const int kMinAllowedBalloonCount = 2;
30
31 // The spacing between the balloon and the panel.
32 const int kVerticalSpacingBetweenBalloonAndPanel = 5;
33
34 // Delay from the mouse leaving the balloon collection before
35 // there is a relayout, in milliseconds.
36 const int kRepositionDelayMs = 300;
37
38
39 BalloonCollectionImpl::BalloonCollectionImpl()
40 : reposition_factory_(this),
41 added_as_message_loop_observer_(false) {
42 registrar_.Add(this, chrome::NOTIFICATION_PANEL_COLLECTION_UPDATED,
43 content::NotificationService::AllSources());
44 registrar_.Add(this, chrome::NOTIFICATION_PANEL_CHANGED_EXPANSION_STATE,
45 content::NotificationService::AllSources());
46
47 SetPositionPreference(BalloonCollection::DEFAULT_POSITION);
48 }
49
50 BalloonCollectionImpl::~BalloonCollectionImpl() {
51 RemoveMessageLoopObserver();
52 }
53
54 void BalloonCollectionImpl::AddImpl(const Notification& notification,
55 Profile* profile,
56 bool add_to_front) {
57 Balloon* new_balloon = MakeBalloon(notification, profile);
58 // The +1 on width is necessary because width is fixed on notifications,
59 // so since we always have the max size, we would always hit the scrollbar
60 // condition. We are only interested in comparing height to maximum.
61 new_balloon->set_min_scrollbar_size(gfx::Size(1 + layout_.max_balloon_width(),
62 layout_.max_balloon_height()));
63 new_balloon->SetPosition(layout_.OffScreenLocation(), false);
64 new_balloon->Show();
65 int count = base_.count();
66 if (count > 0 && layout_.RequiresOffsets())
67 new_balloon->set_offset(base_.balloons()[count - 1]->offset());
68 base_.Add(new_balloon, add_to_front);
69 PositionBalloons(false);
70
71 // There may be no listener in a unit test.
72 if (space_change_listener_)
73 space_change_listener_->OnBalloonSpaceChanged();
74
75 // This is used only for testing.
76 if (!on_collection_changed_callback_.is_null())
77 on_collection_changed_callback_.Run();
78 }
79
80 void BalloonCollectionImpl::Add(const Notification& notification,
81 Profile* profile) {
82 AddImpl(notification, profile, false);
83 }
84
85 const Notification* BalloonCollectionImpl::FindById(
86 const std::string& id) const {
87 return base_.FindById(id);
88 }
89
90 bool BalloonCollectionImpl::RemoveById(const std::string& id) {
91 return base_.CloseById(id);
92 }
93
94 bool BalloonCollectionImpl::RemoveBySourceOrigin(const GURL& origin) {
95 return base_.CloseAllBySourceOrigin(origin);
96 }
97
98 bool BalloonCollectionImpl::RemoveByProfile(Profile* profile) {
99 return base_.CloseAllByProfile(profile);
100 }
101
102 void BalloonCollectionImpl::RemoveAll() {
103 base_.CloseAll();
104 }
105
106 bool BalloonCollectionImpl::HasSpace() const {
107 int count = base_.count();
108 if (count < kMinAllowedBalloonCount)
109 return true;
110
111 int max_balloon_size = 0;
112 int total_size = 0;
113 layout_.GetMaxLinearSize(&max_balloon_size, &total_size);
114
115 int current_max_size = max_balloon_size * count;
116 int max_allowed_size = static_cast<int>(total_size *
117 kPercentBalloonFillFactor);
118 return current_max_size < max_allowed_size - max_balloon_size;
119 }
120
121 void BalloonCollectionImpl::ResizeBalloon(Balloon* balloon,
122 const gfx::Size& size) {
123 balloon->set_content_size(Layout::ConstrainToSizeLimits(size));
124 PositionBalloons(true);
125 }
126
127 void BalloonCollectionImpl::DisplayChanged() {
128 layout_.RefreshSystemMetrics();
129 PositionBalloons(true);
130 }
131
132 void BalloonCollectionImpl::OnBalloonClosed(Balloon* source) {
133 // We want to free the balloon when finished.
134 const Balloons& balloons = base_.balloons();
135
136 Balloons::const_iterator it = balloons.begin();
137 if (layout_.RequiresOffsets()) {
138 gfx::Vector2d offset;
139 bool apply_offset = false;
140 while (it != balloons.end()) {
141 if (*it == source) {
142 ++it;
143 if (it != balloons.end()) {
144 apply_offset = true;
145 offset.set_y((source)->offset().y() - (*it)->offset().y() +
146 (*it)->content_size().height() - source->content_size().height());
147 }
148 } else {
149 if (apply_offset)
150 (*it)->add_offset(offset);
151 ++it;
152 }
153 }
154 // Start listening for UI events so we cancel the offset when the mouse
155 // leaves the balloon area.
156 if (apply_offset)
157 AddMessageLoopObserver();
158 }
159
160 base_.Remove(source);
161 PositionBalloons(true);
162
163 // There may be no listener in a unit test.
164 if (space_change_listener_)
165 space_change_listener_->OnBalloonSpaceChanged();
166
167 // This is used only for testing.
168 if (!on_collection_changed_callback_.is_null())
169 on_collection_changed_callback_.Run();
170 }
171
172 const BalloonCollection::Balloons& BalloonCollectionImpl::GetActiveBalloons() {
173 return base_.balloons();
174 }
175
176 void BalloonCollectionImpl::Observe(
177 int type,
178 const content::NotificationSource& source,
179 const content::NotificationDetails& details) {
180 gfx::Rect bounds;
181 switch (type) {
182 case chrome::NOTIFICATION_PANEL_COLLECTION_UPDATED:
183 case chrome::NOTIFICATION_PANEL_CHANGED_EXPANSION_STATE:
184 layout_.enable_computing_panel_offset();
185 if (layout_.ComputeOffsetToMoveAbovePanels())
186 PositionBalloons(true);
187 break;
188 default:
189 NOTREACHED();
190 break;
191 }
192 }
193
194 void BalloonCollectionImpl::PositionBalloonsInternal(bool reposition) {
195 const Balloons& balloons = base_.balloons();
196
197 layout_.RefreshSystemMetrics();
198 gfx::Point origin = layout_.GetLayoutOrigin();
199 for (Balloons::const_iterator it = balloons.begin();
200 it != balloons.end();
201 ++it) {
202 gfx::Point upper_left = layout_.NextPosition((*it)->GetViewSize(), &origin);
203 (*it)->SetPosition(upper_left, reposition);
204 }
205 }
206
207 gfx::Rect BalloonCollectionImpl::GetBalloonsBoundingBox() const {
208 // Start from the layout origin.
209 gfx::Rect bounds = gfx::Rect(layout_.GetLayoutOrigin(), gfx::Size(0, 0));
210
211 // For each balloon, extend the rectangle. This approach is indifferent to
212 // the orientation of the balloons.
213 const Balloons& balloons = base_.balloons();
214 Balloons::const_iterator iter;
215 for (iter = balloons.begin(); iter != balloons.end(); ++iter) {
216 gfx::Rect balloon_box = gfx::Rect((*iter)->GetPosition(),
217 (*iter)->GetViewSize());
218 bounds.Union(balloon_box);
219 }
220
221 return bounds;
222 }
223
224 void BalloonCollectionImpl::AddMessageLoopObserver() {
225 if (!added_as_message_loop_observer_) {
226 base::MessageLoopForUI::current()->AddObserver(this);
227 added_as_message_loop_observer_ = true;
228 }
229 }
230
231 void BalloonCollectionImpl::RemoveMessageLoopObserver() {
232 if (added_as_message_loop_observer_) {
233 base::MessageLoopForUI::current()->RemoveObserver(this);
234 added_as_message_loop_observer_ = false;
235 }
236 }
237
238 void BalloonCollectionImpl::CancelOffsets() {
239 reposition_factory_.InvalidateWeakPtrs();
240
241 // Unhook from listening to all UI events.
242 RemoveMessageLoopObserver();
243
244 const Balloons& balloons = base_.balloons();
245 for (Balloons::const_iterator it = balloons.begin();
246 it != balloons.end();
247 ++it)
248 (*it)->set_offset(gfx::Vector2d());
249
250 PositionBalloons(true);
251 }
252
253 void BalloonCollectionImpl::HandleMouseMoveEvent() {
254 if (!IsCursorInBalloonCollection()) {
255 // Mouse has left the region. Schedule a reposition after
256 // a short delay.
257 if (!reposition_factory_.HasWeakPtrs()) {
258 base::MessageLoop::current()->PostDelayedTask(
259 FROM_HERE,
260 base::Bind(&BalloonCollectionImpl::CancelOffsets,
261 reposition_factory_.GetWeakPtr()),
262 base::TimeDelta::FromMilliseconds(kRepositionDelayMs));
263 }
264 } else {
265 // Mouse moved back into the region. Cancel the reposition.
266 reposition_factory_.InvalidateWeakPtrs();
267 }
268 }
269
270 BalloonCollectionImpl::Layout::Layout()
271 : placement_(INVALID),
272 need_to_compute_panel_offset_(false),
273 offset_to_move_above_panels_(0) {
274 RefreshSystemMetrics();
275 }
276
277 void BalloonCollectionImpl::Layout::GetMaxLinearSize(int* max_balloon_size,
278 int* total_size) const {
279 DCHECK(max_balloon_size && total_size);
280
281 // All placement schemes are vertical, so we only care about height.
282 *total_size = work_area_.height();
283 *max_balloon_size = max_balloon_height();
284 }
285
286 gfx::Point BalloonCollectionImpl::Layout::GetLayoutOrigin() const {
287 // For lower-left and lower-right positioning, we need to add an offset
288 // to ensure balloons to stay on top of panels to avoid overlapping.
289 int x = 0;
290 int y = 0;
291 switch (placement_) {
292 case VERTICALLY_FROM_TOP_LEFT: {
293 x = work_area_.x() + HorizontalEdgeMargin();
294 y = work_area_.y() + VerticalEdgeMargin() + offset_to_move_above_panels_;
295 break;
296 }
297 case VERTICALLY_FROM_TOP_RIGHT: {
298 x = work_area_.right() - HorizontalEdgeMargin();
299 y = work_area_.y() + VerticalEdgeMargin() + offset_to_move_above_panels_;
300 break;
301 }
302 case VERTICALLY_FROM_BOTTOM_LEFT:
303 x = work_area_.x() + HorizontalEdgeMargin();
304 y = work_area_.bottom() - VerticalEdgeMargin() -
305 offset_to_move_above_panels_;
306 break;
307 case VERTICALLY_FROM_BOTTOM_RIGHT:
308 x = work_area_.right() - HorizontalEdgeMargin();
309 y = work_area_.bottom() - VerticalEdgeMargin() -
310 offset_to_move_above_panels_;
311 break;
312 default:
313 NOTREACHED();
314 break;
315 }
316 return gfx::Point(x, y);
317 }
318
319 gfx::Point BalloonCollectionImpl::Layout::NextPosition(
320 const gfx::Size& balloon_size,
321 gfx::Point* position_iterator) const {
322 DCHECK(position_iterator);
323
324 int x = 0;
325 int y = 0;
326 switch (placement_) {
327 case VERTICALLY_FROM_TOP_LEFT:
328 x = position_iterator->x();
329 y = position_iterator->y();
330 position_iterator->set_y(position_iterator->y() + balloon_size.height() +
331 InterBalloonMargin());
332 break;
333 case VERTICALLY_FROM_TOP_RIGHT:
334 x = position_iterator->x() - balloon_size.width();
335 y = position_iterator->y();
336 position_iterator->set_y(position_iterator->y() + balloon_size.height() +
337 InterBalloonMargin());
338 break;
339 case VERTICALLY_FROM_BOTTOM_LEFT:
340 position_iterator->set_y(position_iterator->y() - balloon_size.height() -
341 InterBalloonMargin());
342 x = position_iterator->x();
343 y = position_iterator->y();
344 break;
345 case VERTICALLY_FROM_BOTTOM_RIGHT:
346 position_iterator->set_y(position_iterator->y() - balloon_size.height() -
347 InterBalloonMargin());
348 x = position_iterator->x() - balloon_size.width();
349 y = position_iterator->y();
350 break;
351 default:
352 NOTREACHED();
353 break;
354 }
355 return gfx::Point(x, y);
356 }
357
358 gfx::Point BalloonCollectionImpl::Layout::OffScreenLocation() const {
359 gfx::Point location = GetLayoutOrigin();
360 switch (placement_) {
361 case VERTICALLY_FROM_TOP_LEFT:
362 case VERTICALLY_FROM_BOTTOM_LEFT:
363 location.Offset(0, kBalloonMaxHeight);
364 break;
365 case VERTICALLY_FROM_TOP_RIGHT:
366 case VERTICALLY_FROM_BOTTOM_RIGHT:
367 location.Offset(-kBalloonMaxWidth - BalloonView::GetHorizontalMargin(),
368 kBalloonMaxHeight);
369 break;
370 default:
371 NOTREACHED();
372 break;
373 }
374 return location;
375 }
376
377 bool BalloonCollectionImpl::Layout::RequiresOffsets() const {
378 // Layout schemes that grow up from the bottom require offsets;
379 // schemes that grow down do not require offsets.
380 bool offsets = (placement_ == VERTICALLY_FROM_BOTTOM_LEFT ||
381 placement_ == VERTICALLY_FROM_BOTTOM_RIGHT);
382 return offsets;
383 }
384
385 // static
386 gfx::Size BalloonCollectionImpl::Layout::ConstrainToSizeLimits(
387 const gfx::Size& size) {
388 // restrict to the min & max sizes
389 return gfx::Size(
390 std::max(min_balloon_width(),
391 std::min(max_balloon_width(), size.width())),
392 std::max(min_balloon_height(),
393 std::min(max_balloon_height(), size.height())));
394 }
395
396 bool BalloonCollectionImpl::Layout::ComputeOffsetToMoveAbovePanels() {
397 // If the offset is not enabled due to that we have not received a
398 // notification about panel, don't proceed because we don't want to call
399 // PanelManager::GetInstance() to create an instance when panel is not
400 // present.
401 if (!need_to_compute_panel_offset_)
402 return false;
403
404 const DockedPanelCollection::Panels& panels =
405 PanelManager::GetInstance()->docked_collection()->panels();
406 int offset_to_move_above_panels = 0;
407
408 // The offset is the maximum height of panels that could overlap with the
409 // balloons.
410 if (NeedToMoveAboveLeftSidePanels()) {
411 for (DockedPanelCollection::Panels::const_reverse_iterator iter =
412 panels.rbegin();
413 iter != panels.rend(); ++iter) {
414 // No need to check panels beyond the area occupied by the balloons.
415 if ((*iter)->GetBounds().x() >= work_area_.x() + max_balloon_width())
416 break;
417
418 int current_height = (*iter)->GetBounds().height();
419 if (current_height > offset_to_move_above_panels)
420 offset_to_move_above_panels = current_height;
421 }
422 } else if (NeedToMoveAboveRightSidePanels()) {
423 for (DockedPanelCollection::Panels::const_iterator iter = panels.begin();
424 iter != panels.end(); ++iter) {
425 // No need to check panels beyond the area occupied by the balloons.
426 if ((*iter)->GetBounds().right() <=
427 work_area_.right() - max_balloon_width())
428 break;
429
430 int current_height = (*iter)->GetBounds().height();
431 if (current_height > offset_to_move_above_panels)
432 offset_to_move_above_panels = current_height;
433 }
434 }
435
436 // Ensure that we have some sort of margin between the 1st balloon and the
437 // panel beneath it even the vertical edge margin is 0 as on Mac.
438 if (offset_to_move_above_panels && !VerticalEdgeMargin())
439 offset_to_move_above_panels += kVerticalSpacingBetweenBalloonAndPanel;
440
441 // If no change is detected, return false to indicate that we do not need to
442 // reposition balloons.
443 if (offset_to_move_above_panels_ == offset_to_move_above_panels)
444 return false;
445
446 offset_to_move_above_panels_ = offset_to_move_above_panels;
447 return true;
448 }
449
450 bool BalloonCollectionImpl::Layout::RefreshSystemMetrics() {
451 bool changed = false;
452
453 // TODO(scottmg): NativeScreen is wrong. http://crbug.com/133312
454 gfx::Rect new_work_area =
455 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().work_area();
456 if (work_area_ != new_work_area) {
457 work_area_.SetRect(new_work_area.x(), new_work_area.y(),
458 new_work_area.width(), new_work_area.height());
459 changed = true;
460 }
461
462 return changed;
463 }
OLDNEW
« no previous file with comments | « chrome/browser/notifications/balloon_collection_impl.h ('k') | chrome/browser/notifications/balloon_host.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698