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

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: Created 9 years, 10 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
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();

Powered by Google App Engine
This is Rietveld 408576698