OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 |
OLD | NEW |