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

Side by Side 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, 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 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 <Cocoa/Cocoa.h> 5 #import <Cocoa/Cocoa.h>
6 #import <QuartzCore/QuartzCore.h> 6 #import <QuartzCore/QuartzCore.h>
7 7
8 #include "base/logging.h" 8 #include "base/logging.h"
9 #include "base/scoped_nsobject.h" 9 #include "base/scoped_nsobject.h"
10 #include "base/sys_string_conversions.h" 10 #include "base/sys_string_conversions.h"
11 #import "chrome/browser/ui/cocoa/confirm_quit_panel_controller.h" 11 #import "chrome/browser/ui/cocoa/confirm_quit_panel_controller.h"
12 #include "grit/generated_resources.h" 12 #include "grit/generated_resources.h"
13 #include "ui/base/l10n/l10n_util_mac.h" 13 #include "ui/base/l10n/l10n_util_mac.h"
14 14
15 // Constants ///////////////////////////////////////////////////////////////////
16
17 // How long the user must hold down Cmd+Q to confirm the quit.
18 const NSTimeInterval kTimeToConfirmQuit = 1.5;
19
20 // Leeway between the |targetDate| and the current time that will confirm a
21 // quit.
22 const NSTimeInterval kTimeDeltaFuzzFactor = 1.0;
23
24 // Duration of the window fade out animation.
25 const NSTimeInterval kWindowFadeAnimationDuration = 0.2;
26
15 // Custom Content View ///////////////////////////////////////////////////////// 27 // Custom Content View /////////////////////////////////////////////////////////
16 28
17 // The content view of the window that draws a custom frame. 29 // The content view of the window that draws a custom frame.
18 @interface ConfirmQuitFrameView : NSView { 30 @interface ConfirmQuitFrameView : NSView {
19 @private 31 @private
20 NSTextField* message_; // Weak, owned by the view hierarchy. 32 NSTextField* message_; // Weak, owned by the view hierarchy.
21 } 33 }
22 - (void)setMessageText:(NSString*)text; 34 - (void)setMessageText:(NSString*)text;
23 @end 35 @end
24 36
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
82 [message_ setFrame:messageFrame]; 94 [message_ setFrame:messageFrame];
83 } 95 }
84 96
85 @end 97 @end
86 98
87 // Private Interface /////////////////////////////////////////////////////////// 99 // Private Interface ///////////////////////////////////////////////////////////
88 100
89 @interface ConfirmQuitPanelController (Private) 101 @interface ConfirmQuitPanelController (Private)
90 - (void)animateFadeOut; 102 - (void)animateFadeOut;
91 - (NSString*)keyCommandString; 103 - (NSString*)keyCommandString;
104 - (NSEvent*)pumpEventQueueForKeyUp:(NSApplication*)app;
105 - (void)hideAllWindowsForApplication:(NSApplication*)app
106 withDuration:(NSTimeInterval)duration;
92 @end 107 @end
93 108
94 ConfirmQuitPanelController* g_confirmQuitPanelController = nil; 109 ConfirmQuitPanelController* g_confirmQuitPanelController = nil;
95 110
96 //////////////////////////////////////////////////////////////////////////////// 111 ////////////////////////////////////////////////////////////////////////////////
97 112
98 @implementation ConfirmQuitPanelController 113 @implementation ConfirmQuitPanelController
99 114
100 + (ConfirmQuitPanelController*)sharedController { 115 + (ConfirmQuitPanelController*)sharedController {
101 if (!g_confirmQuitPanelController) { 116 if (!g_confirmQuitPanelController) {
(...skipping 24 matching lines...) Expand all
126 [window setContentView:contentView_]; 141 [window setContentView:contentView_];
127 142
128 // Set the proper string. 143 // Set the proper string.
129 NSString* message = l10n_util::GetNSStringF(IDS_CONFIRM_TO_QUIT_DESCRIPTION, 144 NSString* message = l10n_util::GetNSStringF(IDS_CONFIRM_TO_QUIT_DESCRIPTION,
130 base::SysNSStringToUTF16([self keyCommandString])); 145 base::SysNSStringToUTF16([self keyCommandString]));
131 [contentView_ setMessageText:message]; 146 [contentView_ setMessageText:message];
132 } 147 }
133 return self; 148 return self;
134 } 149 }
135 150
151 - (NSApplicationTerminateReply)runModalLoopForApplication:(NSApplication*)app {
152 // If this is the second of two such attempts to quit within a certain time
153 // interval, then just quit.
154 // Time of last quit attempt, if any.
155 static NSDate* lastQuitAttempt; // Initially nil, as it's static.
156 NSDate* timeNow = [NSDate date];
157 if (lastQuitAttempt &&
158 [timeNow timeIntervalSinceDate:lastQuitAttempt] < kTimeDeltaFuzzFactor) {
159 // The panel tells users to Hold Cmd+Q. However, we also want to have a
160 // double-tap shortcut that allows for a quick quit path. For the users who
161 // tap Cmd+Q and then hold it with the window still open, this double-tap
162 // logic will run and cause the quit to get committed. If the key
163 // combination held down, the system will start sending the Cmd+Q event to
164 // the next key application, and so on. This is bad, so instead we hide all
165 // the windows (without animation) to look like we've "quit" and then wait
166 // for the KeyUp event to commit the quit.
167 [self hideAllWindowsForApplication:app withDuration:0];
168 NSEvent* nextEvent = nil;
169 do {
170 nextEvent = [self pumpEventQueueForKeyUp:app];
171 } 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.
172 [app discardEventsMatchingMask:NSAnyEventMask beforeEvent:nextEvent];
173 return NSTerminateNow;
174 } else {
175 [lastQuitAttempt release]; // Harmless if already nil.
176 lastQuitAttempt = [timeNow retain]; // Record this attempt for next time.
177 }
178
179 // Show the info panel that explains what the user must to do confirm quit.
180 [self showWindow:self];
181
182 // Spin a nested run loop until the |targetDate| is reached or a KeyUp event
183 // is sent.
184 NSDate* targetDate = [NSDate dateWithTimeIntervalSinceNow:kTimeToConfirmQuit];
185 BOOL willQuit = NO;
186 NSEvent* nextEvent = nil;
187 do {
188 // Dequeue events until a key up is received.
189 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
190
191 // Wait for the time expiry to happen. Once past the hold threshold,
192 // commit to quitting and hide all the open windows.
193 if (!willQuit) {
194 NSDate* now = [NSDate date];
195 NSTimeInterval difference = [targetDate timeIntervalSinceDate:now];
196 if (difference < kTimeDeltaFuzzFactor) {
197 willQuit = YES;
198
199 // At this point, the quit has been confirmed and windows should all
200 // fade out to convince the user to release the key combo to finalize
201 // the quit.
202 [self hideAllWindowsForApplication:app
203 withDuration:kWindowFadeAnimationDuration];
204 }
205 }
206 } while (!nextEvent);
207
208 // The user has released the key combo. Discard any events (i.e. the
209 // repeated KeyDown Cmd+Q).
210 [app discardEventsMatchingMask:NSAnyEventMask beforeEvent:nextEvent];
211
212 if (willQuit) {
213 // The user held down the combination long enough that quitting should
214 // happen.
215 return NSTerminateNow;
216 } else {
217 // Slowly fade the confirm window out in case the user doesn't
218 // understand what they have to do to quit.
219 [self dismissPanel];
220 return NSTerminateCancel;
221 }
222
223 // Default case: terminate.
224 return NSTerminateNow;
225 }
226
136 - (void)windowWillClose:(NSNotification*)notif { 227 - (void)windowWillClose:(NSNotification*)notif {
137 // Release all animations because CAAnimation retains its delegate (self), 228 // Release all animations because CAAnimation retains its delegate (self),
138 // which will cause a retain cycle. Break it! 229 // which will cause a retain cycle. Break it!
139 [[self window] setAnimations:[NSDictionary dictionary]]; 230 [[self window] setAnimations:[NSDictionary dictionary]];
140 g_confirmQuitPanelController = nil; 231 g_confirmQuitPanelController = nil;
141 [self autorelease]; 232 [self autorelease];
142 } 233 }
143 234
144 - (void)showWindow:(id)sender { 235 - (void)showWindow:(id)sender {
145 // If a panel that is fading out is going to be reused here, make sure it 236 // If a panel that is fading out is going to be reused here, make sure it
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
193 } 284 }
194 285
195 // This looks at the Main Menu and determines what the user has set as the 286 // This looks at the Main Menu and determines what the user has set as the
196 // key combination for quit. It then gets the modifiers and builds a string 287 // key combination for quit. It then gets the modifiers and builds a string
197 // to display them. 288 // to display them.
198 - (NSString*)keyCommandString { 289 - (NSString*)keyCommandString {
199 ui::AcceleratorCocoa accelerator = [[self class] quitAccelerator]; 290 ui::AcceleratorCocoa accelerator = [[self class] quitAccelerator];
200 return [self keyCombinationForAccelerator:accelerator]; 291 return [self keyCombinationForAccelerator:accelerator];
201 } 292 }
202 293
294 // Runs a nested loop that pumps the event queue until the next KeyUp event.
295 - (NSEvent*)pumpEventQueueForKeyUp:(NSApplication*)app {
296 //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.
297 return [app nextEventMatchingMask:NSKeyUpMask
298 untilDate:nil
299 inMode:NSEventTrackingRunLoopMode
300 dequeue:YES];
301 }
302
303 // Iterates through the list of open windows and hides them all.
304 - (void)hideAllWindowsForApplication:(NSApplication*)app
305 withDuration:(NSTimeInterval)duration {
306 [NSAnimationContext beginGrouping];
307 [[NSAnimationContext currentContext] setDuration:duration];
308 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.
309 // Windows that are set to animate and have a delegate do no expect to be
310 // animated by other things and could result in an invalid state. If a
311 // window is set up like so, just force the alpha value to 0. Otherwise,
312 // animate all pretty and stuff.
313 if (duration > 0 && ![[aWindow animationForKey:@"alphaValue"] delegate]) {
314 [[aWindow animator] setAlphaValue:0.0];
315 } else {
316 [aWindow setAlphaValue:0.0];
317 }
318 }
319 [NSAnimationContext endGrouping];
320 }
321
203 - (NSString*)keyCombinationForAccelerator:(const ui::AcceleratorCocoa&)item { 322 - (NSString*)keyCombinationForAccelerator:(const ui::AcceleratorCocoa&)item {
204 NSMutableString* string = [NSMutableString string]; 323 NSMutableString* string = [NSMutableString string];
205 NSUInteger modifiers = item.modifiers(); 324 NSUInteger modifiers = item.modifiers();
206 325
207 if (modifiers & NSCommandKeyMask) 326 if (modifiers & NSCommandKeyMask)
208 [string appendString:@"\u2318"]; 327 [string appendString:@"\u2318"];
209 if (modifiers & NSControlKeyMask) 328 if (modifiers & NSControlKeyMask)
210 [string appendString:@"\u2303"]; 329 [string appendString:@"\u2303"];
211 if (modifiers & NSAlternateKeyMask) 330 if (modifiers & NSAlternateKeyMask)
212 [string appendString:@"\u2325"]; 331 [string appendString:@"\u2325"];
213 if (modifiers & NSShiftKeyMask) 332 if (modifiers & NSShiftKeyMask)
214 [string appendString:@"\u21E7"]; 333 [string appendString:@"\u21E7"];
215 334
216 [string appendString:[item.characters() uppercaseString]]; 335 [string appendString:[item.characters() uppercaseString]];
217 return string; 336 return string;
218 } 337 }
219 338
220 @end 339 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698