Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(40)

Unified Diff: chrome/browser/ui/cocoa/confirm_quit_panel_controller.mm

Issue 6625025: [Mac] Confirm-to-Quit: Wait for a KeyUp event if the user held Cmd+Q on the second tap. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address comments Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/ui/cocoa/confirm_quit_panel_controller.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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();
« no previous file with comments | « chrome/browser/ui/cocoa/confirm_quit_panel_controller.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698