| 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..c4cb7277d300ca3f98e6040cdd4667e5c05dd6ab 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 untilDate:(NSDate*)date;
|
| +- (void)hideAllWindowsForApplication:(NSApplication*)app
|
| + withDuration:(NSTimeInterval)duration;
|
| @end
|
|
|
| ConfirmQuitPanelController* g_confirmQuitPanelController = nil;
|
| @@ -133,6 +148,89 @@ ConfirmQuitPanelController* g_confirmQuitPanelController = nil;
|
| return self;
|
| }
|
|
|
| ++ (BOOL)eventTriggersFeature:(NSEvent*)event {
|
| + if ([event type] != NSKeyDown)
|
| + return NO;
|
| + ui::AcceleratorCocoa eventAccelerator([event characters],
|
| + [event modifierFlags] & NSDeviceIndependentModifierFlagsMask);
|
| + return [self quitAccelerator] == eventAccelerator;
|
| +}
|
| +
|
| +- (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 = [self pumpEventQueueForKeyUp:app
|
| + untilDate:[NSDate distantFuture]];
|
| + [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.
|
| + // TODO(rsesek): Explore using |untilDate| with |targetDate|.
|
| + nextEvent = [self pumpEventQueueForKeyUp:app untilDate:nil];
|
| +
|
| + // 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 +298,33 @@ 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 untilDate:(NSDate*)date {
|
| + return [app nextEventMatchingMask:NSKeyUpMask
|
| + untilDate:date
|
| + 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* window in [app windows]) {
|
| + // 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 && ![[window animationForKey:@"alphaValue"] delegate]) {
|
| + [[window animator] setAlphaValue:0.0];
|
| + } else {
|
| + [window setAlphaValue:0.0];
|
| + }
|
| + }
|
| + [NSAnimationContext endGrouping];
|
| +}
|
| +
|
| - (NSString*)keyCombinationForAccelerator:(const ui::AcceleratorCocoa&)item {
|
| NSMutableString* string = [NSMutableString string];
|
| NSUInteger modifiers = item.modifiers();
|
|
|