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

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: 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/browser/ui/cocoa/confirm_quit_panel_controller.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 untilDate:(NSDate*)date;
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 + (BOOL)eventTriggersFeature:(NSEvent*)event {
152 if ([event type] != NSKeyDown)
153 return NO;
154 ui::AcceleratorCocoa eventAccelerator([event characters],
155 [event modifierFlags] & NSDeviceIndependentModifierFlagsMask);
156 return [self quitAccelerator] == eventAccelerator;
157 }
158
159 - (NSApplicationTerminateReply)runModalLoopForApplication:(NSApplication*)app {
160 // If this is the second of two such attempts to quit within a certain time
161 // interval, then just quit.
162 // Time of last quit attempt, if any.
163 static NSDate* lastQuitAttempt; // Initially nil, as it's static.
164 NSDate* timeNow = [NSDate date];
165 if (lastQuitAttempt &&
166 [timeNow timeIntervalSinceDate:lastQuitAttempt] < kTimeDeltaFuzzFactor) {
167 // The panel tells users to Hold Cmd+Q. However, we also want to have a
168 // double-tap shortcut that allows for a quick quit path. For the users who
169 // tap Cmd+Q and then hold it with the window still open, this double-tap
170 // logic will run and cause the quit to get committed. If the key
171 // combination held down, the system will start sending the Cmd+Q event to
172 // the next key application, and so on. This is bad, so instead we hide all
173 // the windows (without animation) to look like we've "quit" and then wait
174 // for the KeyUp event to commit the quit.
175 [self hideAllWindowsForApplication:app withDuration:0];
176 NSEvent* nextEvent = [self pumpEventQueueForKeyUp:app
177 untilDate:[NSDate distantFuture]];
178 [app discardEventsMatchingMask:NSAnyEventMask beforeEvent:nextEvent];
179 return NSTerminateNow;
180 } else {
181 [lastQuitAttempt release]; // Harmless if already nil.
182 lastQuitAttempt = [timeNow retain]; // Record this attempt for next time.
183 }
184
185 // Show the info panel that explains what the user must to do confirm quit.
186 [self showWindow:self];
187
188 // Spin a nested run loop until the |targetDate| is reached or a KeyUp event
189 // is sent.
190 NSDate* targetDate = [NSDate dateWithTimeIntervalSinceNow:kTimeToConfirmQuit];
191 BOOL willQuit = NO;
192 NSEvent* nextEvent = nil;
193 do {
194 // Dequeue events until a key up is received.
195 // TODO(rsesek): Explore using |untilDate| with |targetDate|.
196 nextEvent = [self pumpEventQueueForKeyUp:app untilDate:nil];
197
198 // Wait for the time expiry to happen. Once past the hold threshold,
199 // commit to quitting and hide all the open windows.
200 if (!willQuit) {
201 NSDate* now = [NSDate date];
202 NSTimeInterval difference = [targetDate timeIntervalSinceDate:now];
203 if (difference < kTimeDeltaFuzzFactor) {
204 willQuit = YES;
205
206 // At this point, the quit has been confirmed and windows should all
207 // fade out to convince the user to release the key combo to finalize
208 // the quit.
209 [self hideAllWindowsForApplication:app
210 withDuration:kWindowFadeAnimationDuration];
211 }
212 }
213 } while (!nextEvent);
214
215 // The user has released the key combo. Discard any events (i.e. the
216 // repeated KeyDown Cmd+Q).
217 [app discardEventsMatchingMask:NSAnyEventMask beforeEvent:nextEvent];
218
219 if (willQuit) {
220 // The user held down the combination long enough that quitting should
221 // happen.
222 return NSTerminateNow;
223 } else {
224 // Slowly fade the confirm window out in case the user doesn't
225 // understand what they have to do to quit.
226 [self dismissPanel];
227 return NSTerminateCancel;
228 }
229
230 // Default case: terminate.
231 return NSTerminateNow;
232 }
233
136 - (void)windowWillClose:(NSNotification*)notif { 234 - (void)windowWillClose:(NSNotification*)notif {
137 // Release all animations because CAAnimation retains its delegate (self), 235 // Release all animations because CAAnimation retains its delegate (self),
138 // which will cause a retain cycle. Break it! 236 // which will cause a retain cycle. Break it!
139 [[self window] setAnimations:[NSDictionary dictionary]]; 237 [[self window] setAnimations:[NSDictionary dictionary]];
140 g_confirmQuitPanelController = nil; 238 g_confirmQuitPanelController = nil;
141 [self autorelease]; 239 [self autorelease];
142 } 240 }
143 241
144 - (void)showWindow:(id)sender { 242 - (void)showWindow:(id)sender {
145 // If a panel that is fading out is going to be reused here, make sure it 243 // 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 } 291 }
194 292
195 // This looks at the Main Menu and determines what the user has set as the 293 // 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 294 // key combination for quit. It then gets the modifiers and builds a string
197 // to display them. 295 // to display them.
198 - (NSString*)keyCommandString { 296 - (NSString*)keyCommandString {
199 ui::AcceleratorCocoa accelerator = [[self class] quitAccelerator]; 297 ui::AcceleratorCocoa accelerator = [[self class] quitAccelerator];
200 return [self keyCombinationForAccelerator:accelerator]; 298 return [self keyCombinationForAccelerator:accelerator];
201 } 299 }
202 300
301 // Runs a nested loop that pumps the event queue until the next KeyUp event.
302 - (NSEvent*)pumpEventQueueForKeyUp:(NSApplication*)app untilDate:(NSDate*)date {
303 return [app nextEventMatchingMask:NSKeyUpMask
304 untilDate:date
305 inMode:NSEventTrackingRunLoopMode
306 dequeue:YES];
307 }
308
309 // Iterates through the list of open windows and hides them all.
310 - (void)hideAllWindowsForApplication:(NSApplication*)app
311 withDuration:(NSTimeInterval)duration {
312 [NSAnimationContext beginGrouping];
313 [[NSAnimationContext currentContext] setDuration:duration];
314 for (NSWindow* window in [app windows]) {
315 // Windows that are set to animate and have a delegate do no expect to be
316 // animated by other things and could result in an invalid state. If a
317 // window is set up like so, just force the alpha value to 0. Otherwise,
318 // animate all pretty and stuff.
319 if (duration > 0 && ![[window animationForKey:@"alphaValue"] delegate]) {
320 [[window animator] setAlphaValue:0.0];
321 } else {
322 [window setAlphaValue:0.0];
323 }
324 }
325 [NSAnimationContext endGrouping];
326 }
327
203 - (NSString*)keyCombinationForAccelerator:(const ui::AcceleratorCocoa&)item { 328 - (NSString*)keyCombinationForAccelerator:(const ui::AcceleratorCocoa&)item {
204 NSMutableString* string = [NSMutableString string]; 329 NSMutableString* string = [NSMutableString string];
205 NSUInteger modifiers = item.modifiers(); 330 NSUInteger modifiers = item.modifiers();
206 331
207 if (modifiers & NSCommandKeyMask) 332 if (modifiers & NSCommandKeyMask)
208 [string appendString:@"\u2318"]; 333 [string appendString:@"\u2318"];
209 if (modifiers & NSControlKeyMask) 334 if (modifiers & NSControlKeyMask)
210 [string appendString:@"\u2303"]; 335 [string appendString:@"\u2303"];
211 if (modifiers & NSAlternateKeyMask) 336 if (modifiers & NSAlternateKeyMask)
212 [string appendString:@"\u2325"]; 337 [string appendString:@"\u2325"];
213 if (modifiers & NSShiftKeyMask) 338 if (modifiers & NSShiftKeyMask)
214 [string appendString:@"\u21E7"]; 339 [string appendString:@"\u21E7"];
215 340
216 [string appendString:[item.characters() uppercaseString]]; 341 [string appendString:[item.characters() uppercaseString]];
217 return string; 342 return string;
218 } 343 }
219 344
220 @end 345 @end
OLDNEW
« 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