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/ui/cocoa/download/download_shelf_controller.h" | 5 #import "chrome/browser/ui/cocoa/download/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/download/download_item.h" | 11 #include "chrome/browser/download/download_item.h" |
12 #include "chrome/browser/download/download_manager.h" | 12 #include "chrome/browser/download/download_manager.h" |
13 #include "chrome/browser/profiles/profile.h" | 13 #include "chrome/browser/profiles/profile.h" |
14 #include "chrome/browser/themes/browser_theme_provider.h" | 14 #include "chrome/browser/themes/browser_theme_provider.h" |
15 #include "chrome/browser/ui/browser.h" | 15 #include "chrome/browser/ui/browser.h" |
16 #import "chrome/browser/ui/cocoa/animatable_view.h" | 16 #import "chrome/browser/ui/cocoa/animatable_view.h" |
17 #include "chrome/browser/ui/cocoa/browser_window_cocoa.h" | 17 #include "chrome/browser/ui/cocoa/browser_window_cocoa.h" |
18 #import "chrome/browser/ui/cocoa/browser_window_controller.h" | 18 #import "chrome/browser/ui/cocoa/browser_window_controller.h" |
19 #include "chrome/browser/ui/cocoa/download/download_item_controller.h" | 19 #include "chrome/browser/ui/cocoa/download/download_item_controller.h" |
20 #include "chrome/browser/ui/cocoa/download/download_shelf_mac.h" | 20 #include "chrome/browser/ui/cocoa/download/download_shelf_mac.h" |
21 #import "chrome/browser/ui/cocoa/download/download_shelf_view.h" | 21 #import "chrome/browser/ui/cocoa/download/download_shelf_view.h" |
22 #import "chrome/browser/ui/cocoa/hyperlink_button_cell.h" | 22 #import "chrome/browser/ui/cocoa/hyperlink_button_cell.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 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. |
| 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 kAutoCloseDelaySeconds = 5; |
| 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.GetNativeImageNamed(IDR_DOWNLOADS_FAVICON); | 122 NSImage* favicon = rb.GetNativeImageNamed(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:kAutoCloseDelaySeconds]; |
| 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 |