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 |