Chromium Code Reviews| Index: chrome/browser/ui/cocoa/confirm_quit_panel_controller.mm |
| diff --git a/chrome/browser/ui/cocoa/confirm_quit_panel_controller.mm b/chrome/browser/ui/cocoa/confirm_quit_panel_controller.mm |
| index 2dacd76982b7f872ead904fa5cb8b23292f19559..2c9bb694eadc610698f9446d02d90e4360591098 100644 |
| --- a/chrome/browser/ui/cocoa/confirm_quit_panel_controller.mm |
| +++ b/chrome/browser/ui/cocoa/confirm_quit_panel_controller.mm |
| @@ -12,6 +12,18 @@ |
| #include "grit/generated_resources.h" |
| #include "ui/base/l10n/l10n_util_mac.h" |
| +// Constants /////////////////////////////////////////////////////////////////// |
| + |
| +// How long the user must hold down Cmd+Q to confirm the quit. |
| +const NSTimeInterval kTimeToConfirmQuit = 1.5; |
| + |
| +// Leeway between the |targetDate| and the current time that will confirm a |
| +// quit. |
| +const NSTimeInterval kTimeDeltaFuzzFactor = 1.0; |
| + |
| +// Duration of the window fade out animation. |
| +const NSTimeInterval kWindowFadeAnimationDuration = 0.2; |
| + |
| // Custom Content View ///////////////////////////////////////////////////////// |
| // The content view of the window that draws a custom frame. |
| @@ -89,6 +101,9 @@ |
| @interface ConfirmQuitPanelController (Private) |
| - (void)animateFadeOut; |
| - (NSString*)keyCommandString; |
| +- (NSEvent*)pumpEventQueueForKeyUp:(NSApplication*)app; |
| +- (void)hideAllWindowsForApplication:(NSApplication*)app |
| + withDuration:(NSTimeInterval)duration; |
| @end |
| ConfirmQuitPanelController* g_confirmQuitPanelController = nil; |
| @@ -133,6 +148,82 @@ ConfirmQuitPanelController* g_confirmQuitPanelController = nil; |
| return self; |
| } |
| +- (NSApplicationTerminateReply)runModalLoopForApplication:(NSApplication*)app { |
| + // If this is the second of two such attempts to quit within a certain time |
| + // interval, then just quit. |
| + // Time of last quit attempt, if any. |
| + static NSDate* lastQuitAttempt; // Initially nil, as it's static. |
| + NSDate* timeNow = [NSDate date]; |
| + if (lastQuitAttempt && |
| + [timeNow timeIntervalSinceDate:lastQuitAttempt] < kTimeDeltaFuzzFactor) { |
| + // The panel tells users to Hold Cmd+Q. However, we also want to have a |
| + // double-tap shortcut that allows for a quick quit path. For the users who |
| + // tap Cmd+Q and then hold it with the window still open, this double-tap |
| + // logic will run and cause the quit to get committed. If the key |
| + // combination held down, the system will start sending the Cmd+Q event to |
| + // the next key application, and so on. This is bad, so instead we hide all |
| + // the windows (without animation) to look like we've "quit" and then wait |
| + // for the KeyUp event to commit the quit. |
| + [self hideAllWindowsForApplication:app withDuration:0]; |
| + NSEvent* nextEvent = nil; |
| + do { |
| + nextEvent = [self pumpEventQueueForKeyUp:app]; |
| + } while (!nextEvent); |
|
Nico
2011/03/07 17:40:19
Wait, this busy-polls, right? Why not call nextEve
Robert Sesek
2011/03/07 18:07:43
Done.
|
| + [app discardEventsMatchingMask:NSAnyEventMask beforeEvent:nextEvent]; |
| + return NSTerminateNow; |
| + } else { |
| + [lastQuitAttempt release]; // Harmless if already nil. |
| + lastQuitAttempt = [timeNow retain]; // Record this attempt for next time. |
| + } |
| + |
| + // Show the info panel that explains what the user must to do confirm quit. |
| + [self showWindow:self]; |
| + |
| + // Spin a nested run loop until the |targetDate| is reached or a KeyUp event |
| + // is sent. |
| + NSDate* targetDate = [NSDate dateWithTimeIntervalSinceNow:kTimeToConfirmQuit]; |
| + BOOL willQuit = NO; |
| + NSEvent* nextEvent = nil; |
| + do { |
| + // Dequeue events until a key up is received. |
| + nextEvent = [self pumpEventQueueForKeyUp:app]; |
|
Nico
2011/03/07 17:40:19
I guess you could pass in |targetDate| to nEMM as
Robert Sesek
2011/03/07 18:07:43
I looked at doing this, but the FuzzFactor also mu
|
| + |
| + // Wait for the time expiry to happen. Once past the hold threshold, |
| + // commit to quitting and hide all the open windows. |
| + if (!willQuit) { |
| + NSDate* now = [NSDate date]; |
| + NSTimeInterval difference = [targetDate timeIntervalSinceDate:now]; |
| + if (difference < kTimeDeltaFuzzFactor) { |
| + willQuit = YES; |
| + |
| + // At this point, the quit has been confirmed and windows should all |
| + // fade out to convince the user to release the key combo to finalize |
| + // the quit. |
| + [self hideAllWindowsForApplication:app |
| + withDuration:kWindowFadeAnimationDuration]; |
| + } |
| + } |
| + } while (!nextEvent); |
| + |
| + // The user has released the key combo. Discard any events (i.e. the |
| + // repeated KeyDown Cmd+Q). |
| + [app discardEventsMatchingMask:NSAnyEventMask beforeEvent:nextEvent]; |
| + |
| + if (willQuit) { |
| + // The user held down the combination long enough that quitting should |
| + // happen. |
| + return NSTerminateNow; |
| + } else { |
| + // Slowly fade the confirm window out in case the user doesn't |
| + // understand what they have to do to quit. |
| + [self dismissPanel]; |
| + return NSTerminateCancel; |
| + } |
| + |
| + // Default case: terminate. |
| + return NSTerminateNow; |
| +} |
| + |
| - (void)windowWillClose:(NSNotification*)notif { |
| // Release all animations because CAAnimation retains its delegate (self), |
| // which will cause a retain cycle. Break it! |
| @@ -200,6 +291,34 @@ ConfirmQuitPanelController* g_confirmQuitPanelController = nil; |
| return [self keyCombinationForAccelerator:accelerator]; |
| } |
| +// Runs a nested loop that pumps the event queue until the next KeyUp event. |
| +- (NSEvent*)pumpEventQueueForKeyUp:(NSApplication*)app { |
| + //base::mac::ScopedNSAutoreleasePool pool; // Do we need this? |
|
Nico
2011/03/07 17:40:19
do we? my guess is 'no'.
Robert Sesek
2011/03/07 18:07:43
Nope.
|
| + return [app nextEventMatchingMask:NSKeyUpMask |
| + untilDate:nil |
| + inMode:NSEventTrackingRunLoopMode |
| + dequeue:YES]; |
| +} |
| + |
| +// Iterates through the list of open windows and hides them all. |
| +- (void)hideAllWindowsForApplication:(NSApplication*)app |
| + withDuration:(NSTimeInterval)duration { |
| + [NSAnimationContext beginGrouping]; |
| + [[NSAnimationContext currentContext] setDuration:duration]; |
| + for (NSWindow* aWindow in [app windows]) { |
|
Nico
2011/03/07 17:40:19
nit: just |window|
Robert Sesek
2011/03/07 18:07:43
Done.
|
| + // Windows that are set to animate and have a delegate do no expect to be |
| + // animated by other things and could result in an invalid state. If a |
| + // window is set up like so, just force the alpha value to 0. Otherwise, |
| + // animate all pretty and stuff. |
| + if (duration > 0 && ![[aWindow animationForKey:@"alphaValue"] delegate]) { |
| + [[aWindow animator] setAlphaValue:0.0]; |
| + } else { |
| + [aWindow setAlphaValue:0.0]; |
| + } |
| + } |
| + [NSAnimationContext endGrouping]; |
| +} |
| + |
| - (NSString*)keyCombinationForAccelerator:(const ui::AcceleratorCocoa&)item { |
| NSMutableString* string = [NSMutableString string]; |
| NSUInteger modifiers = item.modifiers(); |