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

Side by Side Diff: chrome/browser/ui/cocoa/download/download_shelf_controller.mm

Issue 12995025: [Mac] DownloadShelf should be notified when autoclosing. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 9 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) 2012 The Chromium Authors. All rights reserved. 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 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 #import "chrome/browser/ui/cocoa/download/download_shelf_controller.h" 5 #import "chrome/browser/ui/cocoa/download/download_shelf_controller.h"
6 6
7 #include "base/mac/bundle_locations.h" 7 #include "base/mac/bundle_locations.h"
8 #include "base/mac/mac_util.h" 8 #include "base/mac/mac_util.h"
9 #include "base/sys_string_conversions.h" 9 #include "base/sys_string_conversions.h"
10 #include "chrome/browser/download/download_util.h" 10 #include "chrome/browser/download/download_util.h"
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
64 // Amount of time between when the mouse is moved off the shelf and the shelf is 64 // Amount of time between when the mouse is moved off the shelf and the shelf is
65 // autoclosed, in seconds. 65 // autoclosed, in seconds.
66 const NSTimeInterval kAutoCloseDelaySeconds = 5; 66 const NSTimeInterval kAutoCloseDelaySeconds = 5;
67 67
68 // The size of the x button by default. 68 // The size of the x button by default.
69 const NSSize kHoverCloseButtonDefaultSize = { 18, 18 }; 69 const NSSize kHoverCloseButtonDefaultSize = { 18, 18 };
70 70
71 } // namespace 71 } // namespace
72 72
73 @interface DownloadShelfController(Private) 73 @interface DownloadShelfController(Private)
74 - (void)showDownloadShelf:(BOOL)enable; 74 - (void)viewFrameDidChange:(NSNotification*)notification;
75 - (void)layoutItems:(BOOL)skipFirst; 75 - (void)layoutItems:(BOOL)skipFirst;
76 - (void)closed; 76 - (void)closed;
77 - (BOOL)canAutoClose; 77 - (void)maybeAutoCloseAfterDelay;
78 78 - (void)autoClose;
79 - (void)viewFrameDidChange:(NSNotification*)notification;
80
81 - (void)installTrackingArea; 79 - (void)installTrackingArea;
82 - (void)cancelAutoCloseAndRemoveTrackingArea; 80 - (void)cancelAutoCloseAndRemoveTrackingArea;
83
84 - (void)willEnterFullscreen; 81 - (void)willEnterFullscreen;
85 - (void)willLeaveFullscreen; 82 - (void)willLeaveFullscreen;
86 - (void)updateCloseButton; 83 - (void)updateCloseButton;
87 @end 84 @end
88 85
89 86
90 @implementation DownloadShelfController 87 @implementation DownloadShelfController
91 88
92 - (id)initWithBrowser:(Browser*)browser 89 - (id)initWithBrowser:(Browser*)browser
93 resizeDelegate:(id<ViewResizer>)resizeDelegate { 90 resizeDelegate:(id<ViewResizer>)resizeDelegate {
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
143 140
144 - (void)dealloc { 141 - (void)dealloc {
145 [[NSNotificationCenter defaultCenter] removeObserver:self]; 142 [[NSNotificationCenter defaultCenter] removeObserver:self];
146 [self cancelAutoCloseAndRemoveTrackingArea]; 143 [self cancelAutoCloseAndRemoveTrackingArea];
147 144
148 // The controllers will unregister themselves as observers when they are 145 // The controllers will unregister themselves as observers when they are
149 // deallocated. No need to do that here. 146 // deallocated. No need to do that here.
150 [super dealloc]; 147 [super dealloc];
151 } 148 }
152 149
153 // Called after the frame's rect has changed; usually when the height is 150 - (IBAction)showDownloadsTab:(id)sender {
154 // animated.
155 - (void)viewFrameDidChange:(NSNotification*)notification {
asanka 2013/03/22 20:11:01 Methods were re-ordered to match declaration order
Nico 2013/03/22 20:24:11 Can you land that part as a separate CL? That woul
asanka 2013/03/22 21:22:13 I undid the re-ordering. I see the point about mes
156 // Anchor subviews at the top of |view|, so that it looks like the shelf
157 // is sliding out.
158 CGFloat newShelfHeight = NSHeight([[self view] frame]);
159 if (newShelfHeight == currentShelfHeight_)
160 return;
161
162 for (NSView* view in [[self view] subviews]) {
163 NSRect frame = [view frame];
164 frame.origin.y -= currentShelfHeight_ - newShelfHeight;
165 [view setFrame:frame];
166 }
167 currentShelfHeight_ = newShelfHeight;
168 }
169
170 - (AnimatableView*)animatableView {
171 return static_cast<AnimatableView*>([self view]);
172 }
173
174 - (void)showDownloadsTab:(id)sender {
175 chrome::ShowDownloads(bridge_->browser()); 151 chrome::ShowDownloads(bridge_->browser());
176 } 152 }
177 153
178 - (void)remove:(DownloadItemController*)download { 154 - (IBAction)handleClose:(id)sender {
179 // Look for the download in our controller array and remove it. This will 155 bridge_->Close(DownloadShelf::USER_ACTION);
180 // explicity release it so that it removes itself as an Observer of the
181 // DownloadItem. We don't want to wait for autorelease since the DownloadItem
182 // we are observing will likely be gone by then.
183 [[NSNotificationCenter defaultCenter] removeObserver:download];
184
185 // TODO(dmaclach): Remove -- http://crbug.com/25845
186 [[download view] removeFromSuperview];
187
188 [downloadItemControllers_ removeObject:download];
189
190 [self layoutItems];
191
192 // Check to see if we have any downloads remaining and if not, hide the shelf.
193 if (![downloadItemControllers_ count])
194 [self showDownloadShelf:NO];
195 } 156 }
196 157
197 - (void)downloadWasOpened:(DownloadItemController*)item_controller { 158 - (void)showDownloadShelf:(BOOL)show
198 // This should only be called on the main thead. 159 isUserAction:(BOOL)isUserAction {
199 DCHECK([NSThread isMainThread]); 160 if ([self isVisible] == show)
200
201 if ([self canAutoClose])
202 [self installTrackingArea];
203 }
204
205 // We need to explicitly release our download controllers here since they need
206 // to remove themselves as observers before the remaining shutdown happens.
207 - (void)exiting {
208 [[self animatableView] stopAnimation];
209 [self cancelAutoCloseAndRemoveTrackingArea];
210 downloadItemControllers_.reset();
211 }
212
213 // Show or hide the bar based on the value of |enable|. Handles animating the
214 // resize of the content view.
215 - (void)showDownloadShelf:(BOOL)enable {
216 if ([self isVisible] == enable)
217 return; 161 return;
218 162
163 if (!show) {
164 [self cancelAutoCloseAndRemoveTrackingArea];
165 int numInProgress = 0;
166 for (NSUInteger i = 0; i < [downloadItemControllers_ count]; ++i) {
167 if ([[downloadItemControllers_ objectAtIndex:i]download]->IsInProgress())
168 ++numInProgress;
169 }
170 download_util::RecordShelfClose(
171 [downloadItemControllers_ count], numInProgress, !isUserAction);
172 }
219 // Animate the shelf out, but not in. 173 // Animate the shelf out, but not in.
220 // TODO(rohitrao): We do not animate on the way in because Cocoa is already 174 // TODO(rohitrao): We do not animate on the way in because Cocoa is already
221 // doing a lot of work to set up the download arrow animation. I've chosen to 175 // doing a lot of work to set up the download arrow animation. I've chosen to
222 // do no animation over janky animation. Find a way to make animating in 176 // do no animation over janky animation. Find a way to make animating in
223 // smoother. 177 // smoother.
224 AnimatableView* view = [self animatableView]; 178 AnimatableView* view = [self animatableView];
225 if (enable) 179 if (show)
226 [view setHeight:maxShelfHeight_]; 180 [view setHeight:maxShelfHeight_];
227 else 181 else
228 [view animateToNewHeight:0 duration:kDownloadShelfCloseDuration]; 182 [view animateToNewHeight:0 duration:kDownloadShelfCloseDuration];
229 183
230 barIsVisible_ = enable; 184 barIsVisible_ = show;
231 [self updateCloseButton]; 185 [self updateCloseButton];
232 } 186 }
233 187
234 - (DownloadShelf*)bridge {
235 return bridge_.get();
236 }
237
238 - (BOOL)isVisible {
239 return barIsVisible_;
240 }
241
242 - (void)show:(id)sender {
243 [self showDownloadShelf:YES];
244 }
245
246 - (void)hide:(id)sender {
247 [self cancelAutoCloseAndRemoveTrackingArea];
248
249 // If |sender| isn't nil, then we're being closed from the UI by the user and
250 // we need to tell our shelf implementation to close. Otherwise, we're being
251 // closed programmatically by our shelf implementation.
252 bool auto_closed = (sender == nil);
253
254 int numInProgress = 0;
255 for (NSUInteger i = 0; i < [downloadItemControllers_ count]; ++i) {
256 if ([[downloadItemControllers_ objectAtIndex:i]download]->IsInProgress())
257 ++numInProgress;
258 }
259 download_util::RecordShelfClose(
260 [downloadItemControllers_ count], numInProgress, auto_closed);
261 if (auto_closed)
262 [self showDownloadShelf:NO];
263 else
264 bridge_->Close();
265 }
266
267 - (void)animationDidEnd:(NSAnimation*)animation {
268 if (![self isVisible])
269 [self closed];
270 }
271
272 - (float)height {
273 return maxShelfHeight_;
274 }
275
276 // If |skipFirst| is true, the frame of the leftmost item is not set.
277 - (void)layoutItems:(BOOL)skipFirst {
278 CGFloat currentX = 0;
279 for (DownloadItemController* itemController
280 in downloadItemControllers_.get()) {
281 NSRect frame = [[itemController view] frame];
282 frame.origin.x = currentX;
283 frame.size.width = [itemController preferredSize].width;
284 if (!skipFirst)
285 [[[itemController view] animator] setFrame:frame];
286 currentX += frame.size.width + kDownloadItemPadding;
287 skipFirst = NO;
288 }
289 }
290
291 - (void)layoutItems { 188 - (void)layoutItems {
292 [self layoutItems:NO]; 189 [self layoutItems:NO];
293 } 190 }
294 191
295 - (void)addDownloadItem:(DownloadItem*)downloadItem { 192 - (void)addDownloadItem:(DownloadItem*)downloadItem {
296 DCHECK([NSThread isMainThread]); 193 DCHECK([NSThread isMainThread]);
297 [self cancelAutoCloseAndRemoveTrackingArea]; 194 [self cancelAutoCloseAndRemoveTrackingArea];
298 195
299 // Insert new item at the left. 196 // Insert new item at the left.
300 scoped_nsobject<DownloadItemController> controller( 197 scoped_nsobject<DownloadItemController> controller(
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
343 // there's no point in animating the removal. 240 // there's no point in animating the removal.
344 [self remove:[downloadItemControllers_ lastObject]]; 241 [self remove:[downloadItemControllers_ lastObject]];
345 } 242 }
346 243
347 // Finally, move the remaining items to the right. Skip the first item when 244 // Finally, move the remaining items to the right. Skip the first item when
348 // laying out the items, so that the longer animation duration we set up above 245 // laying out the items, so that the longer animation duration we set up above
349 // is not overwritten. 246 // is not overwritten.
350 [self layoutItems:YES]; 247 [self layoutItems:YES];
351 } 248 }
352 249
250 - (void)remove:(DownloadItemController*)download {
251 // Look for the download in our controller array and remove it. This will
252 // explicity release it so that it removes itself as an Observer of the
253 // DownloadItem. We don't want to wait for autorelease since the DownloadItem
254 // we are observing will likely be gone by then.
255 [[NSNotificationCenter defaultCenter] removeObserver:download];
256
257 // TODO(dmaclach): Remove -- http://crbug.com/25845
258 [[download view] removeFromSuperview];
259
260 [downloadItemControllers_ removeObject:download];
261 [self layoutItems];
262
263 // If there are no more downloads or if all the remaining downloads have been
264 // opened, we can close the shelf.
265 [self maybeAutoCloseAfterDelay];
266 }
267
268 - (void)downloadWasOpened:(DownloadItemController*)item_controller {
269 // This should only be called on the main thead.
270 DCHECK([NSThread isMainThread]);
271 [self maybeAutoCloseAfterDelay];
272 }
273
274 // We need to explicitly release our download controllers here since they need
275 // to remove themselves as observers before the remaining shutdown happens.
276 - (void)exiting {
277 [[self animatableView] stopAnimation];
278 [self cancelAutoCloseAndRemoveTrackingArea];
279 downloadItemControllers_.reset();
280 }
281
282 - (AnimatableView*)animatableView {
283 return static_cast<AnimatableView*>([self view]);
284 }
285
286 - (DownloadShelf*)bridge {
287 return bridge_.get();
288 }
289
290 - (BOOL)isVisible {
291 return barIsVisible_;
292 }
293
294 - (float)height {
295 return maxShelfHeight_;
296 }
297
298 // Called after the frame's rect has changed; usually when the height is
299 // animated.
300 - (void)viewFrameDidChange:(NSNotification*)notification {
301 // Anchor subviews at the top of |view|, so that it looks like the shelf
302 // is sliding out.
303 CGFloat newShelfHeight = NSHeight([[self view] frame]);
304 if (newShelfHeight == currentShelfHeight_)
305 return;
306
307 for (NSView* view in [[self view] subviews]) {
308 NSRect frame = [view frame];
309 frame.origin.y -= currentShelfHeight_ - newShelfHeight;
310 [view setFrame:frame];
311 }
312 currentShelfHeight_ = newShelfHeight;
313 }
314
315 - (void)animationDidEnd:(NSAnimation*)animation {
316 if (![self isVisible])
317 [self closed];
318 }
319
320 // If |skipFirst| is true, the frame of the leftmost item is not set.
321 - (void)layoutItems:(BOOL)skipFirst {
322 CGFloat currentX = 0;
323 for (DownloadItemController* itemController
324 in downloadItemControllers_.get()) {
325 NSRect frame = [[itemController view] frame];
326 frame.origin.x = currentX;
327 frame.size.width = [itemController preferredSize].width;
328 if (!skipFirst)
329 [[[itemController view] animator] setFrame:frame];
330 currentX += frame.size.width + kDownloadItemPadding;
331 skipFirst = NO;
332 }
333 }
334
353 - (void)closed { 335 - (void)closed {
354 // Don't remove completed downloads if the shelf is just being auto-hidden 336 // Don't remove completed downloads if the shelf is just being auto-hidden
355 // rather than explicitly closed by the user. 337 // rather than explicitly closed by the user.
356 if (bridge_->is_hidden()) 338 if (bridge_->is_hidden())
357 return; 339 return;
358 NSUInteger i = 0; 340 NSUInteger i = 0;
359 while (i < [downloadItemControllers_ count]) { 341 while (i < [downloadItemControllers_ count]) {
360 DownloadItemController* itemController = 342 DownloadItemController* itemController =
361 [downloadItemControllers_ objectAtIndex:i]; 343 [downloadItemControllers_ objectAtIndex:i];
362 DownloadItem* download = [itemController download]; 344 DownloadItem* download = [itemController download];
(...skipping 13 matching lines...) Expand all
376 358
377 - (void)mouseEntered:(NSEvent*)event { 359 - (void)mouseEntered:(NSEvent*)event {
378 // If the mouse re-enters the download shelf, cancel the auto-close. Further 360 // If the mouse re-enters the download shelf, cancel the auto-close. Further
379 // mouse exits should not trigger autoclose, so also remove the tracking area. 361 // mouse exits should not trigger autoclose, so also remove the tracking area.
380 [self cancelAutoCloseAndRemoveTrackingArea]; 362 [self cancelAutoCloseAndRemoveTrackingArea];
381 } 363 }
382 364
383 - (void)mouseExited:(NSEvent*)event { 365 - (void)mouseExited:(NSEvent*)event {
384 // Cancel any previous hide requests, just to be safe. 366 // Cancel any previous hide requests, just to be safe.
385 [NSObject cancelPreviousPerformRequestsWithTarget:self 367 [NSObject cancelPreviousPerformRequestsWithTarget:self
386 selector:@selector(hide:) 368 selector:@selector(autoClose)
387 object:self]; 369 object:nil];
388 370
389 // Schedule an autoclose after a delay. If the mouse is moved back into the 371 // Schedule an autoclose after a delay. If the mouse is moved back into the
390 // view, or if an item is added to the shelf, the timer will be canceled. 372 // view, or if an item is added to the shelf, the timer will be canceled.
391 [self performSelector:@selector(hide:) 373 [self performSelector:@selector(autoClose)
392 withObject:self 374 withObject:nil
393 afterDelay:kAutoCloseDelaySeconds]; 375 afterDelay:kAutoCloseDelaySeconds];
394 } 376 }
395 377
396 - (BOOL)canAutoClose { 378 - (void)maybeAutoCloseAfterDelay {
379 // We can close the shelf automatically if all the downloads on the shelf have
380 // been opened.
397 for (NSUInteger i = 0; i < [downloadItemControllers_ count]; ++i) { 381 for (NSUInteger i = 0; i < [downloadItemControllers_ count]; ++i) {
398 DownloadItemController* itemController = 382 DownloadItemController* itemController =
399 [downloadItemControllers_ objectAtIndex:i]; 383 [downloadItemControllers_ objectAtIndex:i];
400 if (![itemController download]->GetOpened()) 384 if (![itemController download]->GetOpened())
401 return NO; 385 return;
402 } 386 }
403 return YES; 387
388 if ([self isVisible] && [downloadItemControllers_ count] > 0) {
389 // If the shelf is visible and has download items remaining on it, close the
390 // shelf after the user moves the mouse out of the download shelf.
391 [self installTrackingArea];
392 } else {
393 // We notify the DownloadShelf of our intention to close even if the shelf
394 // is currently hidden. If the shelf was temporarily hidden (e.g. because
395 // the browser window entered fullscreen mode), then this prevents the shelf
396 // from being shown again when the browser exits fullscreen mode.
397 [self autoClose];
398 }
399 }
400
401 - (void)autoClose {
402 bridge_->Close(DownloadShelf::AUTOMATIC);
404 } 403 }
405 404
406 - (void)installTrackingArea { 405 - (void)installTrackingArea {
407 // Install the tracking area to listen for mouseExited messages and trigger 406 // Install the tracking area to listen for mouseExited messages and trigger
408 // the shelf autoclose. 407 // the shelf autoclose.
409 if (trackingArea_.get()) 408 if (trackingArea_.get())
410 return; 409 return;
411 410
412 trackingArea_.reset([[NSTrackingArea alloc] 411 trackingArea_.reset([[NSTrackingArea alloc]
413 initWithRect:[[self view] bounds] 412 initWithRect:[[self view] bounds]
414 options:NSTrackingMouseEnteredAndExited | 413 options:NSTrackingMouseEnteredAndExited |
415 NSTrackingActiveAlways 414 NSTrackingActiveAlways
416 owner:self 415 owner:self
417 userInfo:nil]); 416 userInfo:nil]);
418 [[self view] addTrackingArea:trackingArea_]; 417 [[self view] addTrackingArea:trackingArea_];
419 } 418 }
420 419
421 - (void)cancelAutoCloseAndRemoveTrackingArea { 420 - (void)cancelAutoCloseAndRemoveTrackingArea {
422 [NSObject cancelPreviousPerformRequestsWithTarget:self 421 [NSObject cancelPreviousPerformRequestsWithTarget:self
423 selector:@selector(hide:) 422 selector:@selector(autoClose)
424 object:self]; 423 object:nil];
425 424
426 if (trackingArea_.get()) { 425 if (trackingArea_.get()) {
427 [[self view] removeTrackingArea:trackingArea_]; 426 [[self view] removeTrackingArea:trackingArea_];
428 trackingArea_.reset(nil); 427 trackingArea_.reset(nil);
429 } 428 }
430 } 429 }
431 430
432 - (void)willEnterFullscreen { 431 - (void)willEnterFullscreen {
433 isFullscreen_ = YES; 432 isFullscreen_ = YES;
434 [self updateCloseButton]; 433 [self updateCloseButton];
(...skipping 24 matching lines...) Expand all
459 } 458 }
460 459
461 // Set the tracking off to create a new tracking area for the control. 460 // Set the tracking off to create a new tracking area for the control.
462 // When changing the bounds/frame on a HoverButton, the tracking isn't updated 461 // When changing the bounds/frame on a HoverButton, the tracking isn't updated
463 // correctly, it needs to be turned off and back on. 462 // correctly, it needs to be turned off and back on.
464 [hoverCloseButton_ setTrackingEnabled:NO]; 463 [hoverCloseButton_ setTrackingEnabled:NO];
465 [hoverCloseButton_ setFrame:bounds]; 464 [hoverCloseButton_ setFrame:bounds];
466 [hoverCloseButton_ setTrackingEnabled:YES]; 465 [hoverCloseButton_ setTrackingEnabled:YES];
467 } 466 }
468 @end 467 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698