Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 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/cocoa/download_shelf_controller.h" | 5 #import "chrome/browser/cocoa/download_shelf_controller.h" |
| 6 | 6 |
| 7 #include "app/l10n_util.h" | 7 #include "app/l10n_util.h" |
| 8 #include "app/resource_bundle.h" | 8 #include "app/resource_bundle.h" |
| 9 #include "base/mac_util.h" | 9 #include "base/mac_util.h" |
| 10 #include "base/sys_string_conversions.h" | 10 #include "base/sys_string_conversions.h" |
| 11 #include "chrome/browser/browser.h" | 11 #include "chrome/browser/browser.h" |
| 12 #import "chrome/browser/cocoa/animatable_view.h" | 12 #import "chrome/browser/cocoa/animatable_view.h" |
| 13 #include "chrome/browser/cocoa/browser_window_cocoa.h" | 13 #include "chrome/browser/cocoa/browser_window_cocoa.h" |
| 14 #import "chrome/browser/cocoa/browser_window_controller.h" | 14 #import "chrome/browser/cocoa/browser_window_controller.h" |
| 15 #include "chrome/browser/cocoa/download_item_controller.h" | 15 #include "chrome/browser/cocoa/download_item_controller.h" |
| 16 #include "chrome/browser/cocoa/download_shelf_mac.h" | 16 #include "chrome/browser/cocoa/download_shelf_mac.h" |
| 17 #import "chrome/browser/cocoa/download_shelf_view.h" | 17 #import "chrome/browser/cocoa/download_shelf_view.h" |
| 18 #import "chrome/browser/cocoa/hyperlink_button_cell.h" | 18 #import "chrome/browser/cocoa/hyperlink_button_cell.h" |
| 19 #include "chrome/browser/download/download_item.h" | 19 #include "chrome/browser/download/download_item.h" |
| 20 #include "chrome/browser/download/download_manager.h" | 20 #include "chrome/browser/download/download_manager.h" |
| 21 #include "chrome/browser/profile.h" | 21 #include "chrome/browser/profile.h" |
| 22 #include "chrome/browser/themes/browser_theme_provider.h" | 22 #include "chrome/browser/themes/browser_theme_provider.h" |
| 23 #include "grit/generated_resources.h" | 23 #include "grit/generated_resources.h" |
| 24 #include "grit/theme_resources.h" | 24 #include "grit/theme_resources.h" |
| 25 #import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h" | 25 #import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h" |
| 26 | 26 |
| 27 // Download shelf autoclose behavior: | |
| 28 // | |
| 29 // The download shelf autocloses if all of: | |
|
Nico
2010/12/12 22:03:19
"if all this is true:"
| |
| 30 // 1) An item on the shelf has just been opened. | |
| 31 // 2) All remaining items on the shelf have been opened in the past. | |
| 32 // 3) The mouse leaves the shelf and remains off the shelf for 5 seconds. | |
| 33 // | |
| 34 // If the mouse re-enters the shelf within the 5 second grace period, the | |
| 35 // autoclose is canceled. An autoclose can only be scheduled in response to a | |
| 36 // shelf item being opened or removed. If an item is opened and then the | |
| 37 // resulting autoclose is canceled, subsequent mouse exited events will NOT | |
| 38 // trigger an autoclose. | |
| 39 // | |
| 40 // If the shelf is manually closed while a download is still in progress, that | |
| 41 // download is marked as "opened" for these purposes. If the shelf is later | |
| 42 // reopened, these previously-in-progress download will not block autoclose, | |
| 43 // even if that download was never actually clicked on and opened. | |
|
Nico
2010/09/28 18:12:29
Is there some way to have this logic (and this com
rohitrao (ping after 24h)
2010/09/28 19:12:59
I don't think there's a suitable cross-platform cl
Nico
2010/09/29 15:52:46
Should there be one?
| |
| 44 | |
| 27 namespace { | 45 namespace { |
| 28 | 46 |
| 29 // Max number of download views we'll contain. Any time a view is added and | 47 // Max number of download views we'll contain. Any time a view is added and |
| 30 // we already have this many download views, one is removed. | 48 // we already have this many download views, one is removed. |
| 31 const size_t kMaxDownloadItemCount = 16; | 49 const size_t kMaxDownloadItemCount = 16; |
| 32 | 50 |
| 33 // Horizontal padding between two download items. | 51 // Horizontal padding between two download items. |
| 34 const int kDownloadItemPadding = 0; | 52 const int kDownloadItemPadding = 0; |
| 35 | 53 |
| 36 // Duration for the open-new-leftmost-item animation, in seconds. | 54 // Duration for the open-new-leftmost-item animation, in seconds. |
| 37 const NSTimeInterval kDownloadItemOpenDuration = 0.8; | 55 const NSTimeInterval kDownloadItemOpenDuration = 0.8; |
| 38 | 56 |
| 39 // Duration for download shelf closing animation, in seconds. | 57 // Duration for download shelf closing animation, in seconds. |
| 40 const NSTimeInterval kDownloadShelfCloseDuration = 0.12; | 58 const NSTimeInterval kDownloadShelfCloseDuration = 0.12; |
| 41 | 59 |
| 60 // Amount of time between when the mouse is moved off the shelf and the shelf is | |
| 61 // autoclosed, in seconds. | |
| 62 const NSTimeInterval kAutoCloseDelay = 5; | |
|
Nico
2010/12/12 22:03:19
kAutoCloseDelaySeconds
| |
| 63 | |
| 42 } // namespace | 64 } // namespace |
| 43 | 65 |
| 44 @interface DownloadShelfController(Private) | 66 @interface DownloadShelfController(Private) |
| 45 - (void)showDownloadShelf:(BOOL)enable; | 67 - (void)showDownloadShelf:(BOOL)enable; |
| 46 - (void)layoutItems:(BOOL)skipFirst; | 68 - (void)layoutItems:(BOOL)skipFirst; |
| 47 - (void)closed; | 69 - (void)closed; |
| 70 - (BOOL)canAutoClose; | |
| 48 | 71 |
| 49 - (void)updateTheme; | 72 - (void)updateTheme; |
| 50 - (void)themeDidChangeNotification:(NSNotification*)notification; | 73 - (void)themeDidChangeNotification:(NSNotification*)notification; |
| 51 - (void)viewFrameDidChange:(NSNotification*)notification; | 74 - (void)viewFrameDidChange:(NSNotification*)notification; |
| 75 | |
| 76 - (void)installTrackingArea; | |
| 77 - (void)cancelAutoCloseAndRemoveTrackingArea; | |
| 52 @end | 78 @end |
| 53 | 79 |
| 54 | 80 |
| 55 @implementation DownloadShelfController | 81 @implementation DownloadShelfController |
| 56 | 82 |
| 57 - (id)initWithBrowser:(Browser*)browser | 83 - (id)initWithBrowser:(Browser*)browser |
| 58 resizeDelegate:(id<ViewResizer>)resizeDelegate { | 84 resizeDelegate:(id<ViewResizer>)resizeDelegate { |
| 59 if ((self = [super initWithNibName:@"DownloadShelf" | 85 if ((self = [super initWithNibName:@"DownloadShelf" |
| 60 bundle:mac_util::MainAppBundle()])) { | 86 bundle:mac_util::MainAppBundle()])) { |
| 61 resizeDelegate_ = resizeDelegate; | 87 resizeDelegate_ = resizeDelegate; |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 93 object:[self view]]; | 119 object:[self view]]; |
| 94 | 120 |
| 95 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | 121 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
| 96 NSImage* favicon = rb.GetNSImageNamed(IDR_DOWNLOADS_FAVICON); | 122 NSImage* favicon = rb.GetNSImageNamed(IDR_DOWNLOADS_FAVICON); |
| 97 DCHECK(favicon); | 123 DCHECK(favicon); |
| 98 [image_ setImage:favicon]; | 124 [image_ setImage:favicon]; |
| 99 } | 125 } |
| 100 | 126 |
| 101 - (void)dealloc { | 127 - (void)dealloc { |
| 102 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 128 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 129 [self cancelAutoCloseAndRemoveTrackingArea]; | |
| 103 | 130 |
| 104 // The controllers will unregister themselves as observers when they are | 131 // The controllers will unregister themselves as observers when they are |
| 105 // deallocated. No need to do that here. | 132 // deallocated. No need to do that here. |
| 106 [super dealloc]; | 133 [super dealloc]; |
| 107 } | 134 } |
| 108 | 135 |
| 109 // Called after the current theme has changed. | 136 // Called after the current theme has changed. |
| 110 - (void)themeDidChangeNotification:(NSNotification*)notification { | 137 - (void)themeDidChangeNotification:(NSNotification*)notification { |
| 111 [self updateTheme]; | 138 [self updateTheme]; |
| 112 } | 139 } |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 166 | 193 |
| 167 [downloadItemControllers_ removeObject:download]; | 194 [downloadItemControllers_ removeObject:download]; |
| 168 | 195 |
| 169 [self layoutItems]; | 196 [self layoutItems]; |
| 170 | 197 |
| 171 // Check to see if we have any downloads remaining and if not, hide the shelf. | 198 // Check to see if we have any downloads remaining and if not, hide the shelf. |
| 172 if (![downloadItemControllers_ count]) | 199 if (![downloadItemControllers_ count]) |
| 173 [self showDownloadShelf:NO]; | 200 [self showDownloadShelf:NO]; |
| 174 } | 201 } |
| 175 | 202 |
| 203 - (void)downloadWasOpened:(DownloadItemController*)item_controller { | |
| 204 // This should only be called on the main thead. | |
| 205 DCHECK([NSThread isMainThread]); | |
| 206 | |
| 207 if ([self canAutoClose]) | |
| 208 [self installTrackingArea]; | |
| 209 } | |
| 210 | |
| 176 // We need to explicitly release our download controllers here since they need | 211 // We need to explicitly release our download controllers here since they need |
| 177 // to remove themselves as observers before the remaining shutdown happens. | 212 // to remove themselves as observers before the remaining shutdown happens. |
| 178 - (void)exiting { | 213 - (void)exiting { |
| 179 [[self animatableView] stopAnimation]; | 214 [[self animatableView] stopAnimation]; |
| 215 [self cancelAutoCloseAndRemoveTrackingArea]; | |
| 180 downloadItemControllers_.reset(); | 216 downloadItemControllers_.reset(); |
| 181 } | 217 } |
| 182 | 218 |
| 183 // Show or hide the bar based on the value of |enable|. Handles animating the | 219 // Show or hide the bar based on the value of |enable|. Handles animating the |
| 184 // resize of the content view. | 220 // resize of the content view. |
| 185 - (void)showDownloadShelf:(BOOL)enable { | 221 - (void)showDownloadShelf:(BOOL)enable { |
| 186 if ([self isVisible] == enable) | 222 if ([self isVisible] == enable) |
| 187 return; | 223 return; |
| 188 | 224 |
| 189 if ([[self view] window]) | 225 if ([[self view] window]) |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 209 | 245 |
| 210 - (BOOL)isVisible { | 246 - (BOOL)isVisible { |
| 211 return barIsVisible_; | 247 return barIsVisible_; |
| 212 } | 248 } |
| 213 | 249 |
| 214 - (void)show:(id)sender { | 250 - (void)show:(id)sender { |
| 215 [self showDownloadShelf:YES]; | 251 [self showDownloadShelf:YES]; |
| 216 } | 252 } |
| 217 | 253 |
| 218 - (void)hide:(id)sender { | 254 - (void)hide:(id)sender { |
| 255 [self cancelAutoCloseAndRemoveTrackingArea]; | |
| 256 | |
| 219 // If |sender| isn't nil, then we're being closed from the UI by the user and | 257 // If |sender| isn't nil, then we're being closed from the UI by the user and |
| 220 // we need to tell our shelf implementation to close. Otherwise, we're being | 258 // we need to tell our shelf implementation to close. Otherwise, we're being |
| 221 // closed programmatically by our shelf implementation. | 259 // closed programmatically by our shelf implementation. |
| 222 if (sender) | 260 if (sender) |
| 223 bridge_->Close(); | 261 bridge_->Close(); |
| 224 else | 262 else |
| 225 [self showDownloadShelf:NO]; | 263 [self showDownloadShelf:NO]; |
| 226 } | 264 } |
| 227 | 265 |
| 228 - (void)animationDidEnd:(NSAnimation*)animation { | 266 - (void)animationDidEnd:(NSAnimation*)animation { |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 248 skipFirst = NO; | 286 skipFirst = NO; |
| 249 } | 287 } |
| 250 } | 288 } |
| 251 | 289 |
| 252 - (void)layoutItems { | 290 - (void)layoutItems { |
| 253 [self layoutItems:NO]; | 291 [self layoutItems:NO]; |
| 254 } | 292 } |
| 255 | 293 |
| 256 - (void)addDownloadItem:(BaseDownloadItemModel*)model { | 294 - (void)addDownloadItem:(BaseDownloadItemModel*)model { |
| 257 DCHECK([NSThread isMainThread]); | 295 DCHECK([NSThread isMainThread]); |
| 296 [self cancelAutoCloseAndRemoveTrackingArea]; | |
| 297 | |
| 258 // Insert new item at the left. | 298 // Insert new item at the left. |
| 259 scoped_nsobject<DownloadItemController> controller( | 299 scoped_nsobject<DownloadItemController> controller( |
| 260 [[DownloadItemController alloc] initWithModel:model shelf:self]); | 300 [[DownloadItemController alloc] initWithModel:model shelf:self]); |
| 261 | 301 |
| 262 // Adding at index 0 in NSMutableArrays is O(1). | 302 // Adding at index 0 in NSMutableArrays is O(1). |
| 263 [downloadItemControllers_ insertObject:controller.get() atIndex:0]; | 303 [downloadItemControllers_ insertObject:controller.get() atIndex:0]; |
| 264 | 304 |
| 265 [itemContainerView_ addSubview:[controller view]]; | 305 [itemContainerView_ addSubview:[controller view]]; |
| 266 | 306 |
| 267 // The controller is in charge of removing itself as an observer in its | 307 // The controller is in charge of removing itself as an observer in its |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 305 // laying out the items, so that the longer animation duration we set up above | 345 // laying out the items, so that the longer animation duration we set up above |
| 306 // is not overwritten. | 346 // is not overwritten. |
| 307 [self layoutItems:YES]; | 347 [self layoutItems:YES]; |
| 308 } | 348 } |
| 309 | 349 |
| 310 - (void)closed { | 350 - (void)closed { |
| 311 NSUInteger i = 0; | 351 NSUInteger i = 0; |
| 312 while (i < [downloadItemControllers_ count]) { | 352 while (i < [downloadItemControllers_ count]) { |
| 313 DownloadItemController* itemController = | 353 DownloadItemController* itemController = |
| 314 [downloadItemControllers_ objectAtIndex:i]; | 354 [downloadItemControllers_ objectAtIndex:i]; |
| 355 DownloadItem* download = [itemController download]; | |
| 315 bool isTransferDone = | 356 bool isTransferDone = |
| 316 [itemController download]->state() == DownloadItem::COMPLETE || | 357 download->state() == DownloadItem::COMPLETE || |
| 317 [itemController download]->state() == DownloadItem::CANCELLED; | 358 download->state() == DownloadItem::CANCELLED; |
| 318 if (isTransferDone && | 359 if (isTransferDone && |
| 319 [itemController download]->safety_state() != DownloadItem::DANGEROUS) { | 360 download->safety_state() != DownloadItem::DANGEROUS) { |
| 320 [self remove:itemController]; | 361 [self remove:itemController]; |
| 321 } else { | 362 } else { |
| 363 // Treat the item as opened when we close. This way if we get shown again | |
| 364 // the user need not open this item for the shelf to auto-close. | |
| 365 download->set_opened(true); | |
| 322 ++i; | 366 ++i; |
| 323 } | 367 } |
| 324 } | 368 } |
| 325 } | 369 } |
| 326 | 370 |
| 371 - (void)mouseEntered:(NSEvent*)event { | |
| 372 // If the mouse re-enters the download shelf, cancel the auto-close. Further | |
| 373 // mouse exits should not trigger autoclose, so also remove the tracking area. | |
| 374 [self cancelAutoCloseAndRemoveTrackingArea]; | |
| 375 } | |
| 376 | |
| 377 - (void)mouseExited:(NSEvent*)event { | |
| 378 // Cancel any previous hide requests, just to be safe. | |
| 379 [NSObject cancelPreviousPerformRequestsWithTarget:self | |
| 380 selector:@selector(hide:) | |
| 381 object:self]; | |
| 382 | |
| 383 // Schedule an autoclose after a delay. If the mouse is moved back into the | |
| 384 // view, or if an item is added to the shelf, the timer will be canceled. | |
| 385 [self performSelector:@selector(hide:) | |
| 386 withObject:self | |
| 387 afterDelay:kAutoCloseDelay]; | |
| 388 } | |
| 389 | |
| 390 - (BOOL)canAutoClose { | |
| 391 for (NSUInteger i = 0; i < [downloadItemControllers_ count]; ++i) { | |
| 392 DownloadItemController* itemController = | |
| 393 [downloadItemControllers_ objectAtIndex:i]; | |
| 394 if (![itemController download]->opened()) | |
| 395 return NO; | |
| 396 } | |
| 397 return YES; | |
| 398 } | |
| 399 | |
| 400 - (void)installTrackingArea { | |
| 401 // Install the tracking area to listen for mouseExited messages and trigger | |
| 402 // the shelf autoclose. | |
| 403 if (trackingArea_.get()) | |
| 404 return; | |
| 405 | |
| 406 trackingArea_.reset([[NSTrackingArea alloc] | |
| 407 initWithRect:[[self view] bounds] | |
| 408 options:NSTrackingMouseEnteredAndExited | | |
| 409 NSTrackingActiveAlways | |
| 410 owner:self | |
| 411 userInfo:nil]); | |
| 412 [[self view] addTrackingArea:trackingArea_]; | |
| 413 } | |
| 414 | |
| 415 - (void)cancelAutoCloseAndRemoveTrackingArea { | |
| 416 [NSObject cancelPreviousPerformRequestsWithTarget:self | |
| 417 selector:@selector(hide:) | |
| 418 object:self]; | |
| 419 | |
| 420 if (trackingArea_.get()) { | |
| 421 [[self view] removeTrackingArea:trackingArea_]; | |
| 422 trackingArea_.reset(nil); | |
| 423 } | |
| 424 } | |
| 425 | |
| 327 @end | 426 @end |
| OLD | NEW |