NSAlert: Make second button both the default and cancel button

798 views Asked by At

Apple's original HIGs (now disappeared from the web site, sadly) stated that:

The rightmost button in the dialog, the action button, is the button that confirms the alert message text. The action button is usually, but not always, the default button

In my case, I have some destructive operations (such as erasing a disk) that need "safe" confirmation dialogs, like this:

"Safe" confirmation dialog

The worst option would be to make a dialog where the rightmost button would become the "do not erase" button, and the one left of it, which is usually, the Cancel button, would become the "erase" button, because that would lead easily to disaster (happened to me with a Microsoft-made dialog once), because people are trained to click the second button whenever they want to cancel an operation.

So, what I need is that the left (cancel) button becomes both the default button, and also reacts to the Return, Esc and cmd-period keys.

To make it default and also react to the Return key, I simply have to set the first button's keyEquivalent to an empty string, and the second button's to "\r".

But how to I also make the alert cancel when Esc or cmd-. are typed?

1

There are 1 answers

4
Zoë Peterson On BEST ANSWER

Setup the NSAlert the way you normally would, with the default button assigned. Create a new subclass of NSView with an empty bounds and add it as the NSAlert's accessory view. In the subclass's performKeyEquivalent, check for Esc and if it matches call [-NSApplication stopModalWithCode:] or [-NSWindow endSheet:returnCode:].

#import "AppDelegate.h"

@interface AlertEscHandler : NSView
@end

@implementation AlertEscHandler
-(BOOL)performKeyEquivalent:(NSEvent *)event {
    NSString *typed = event.charactersIgnoringModifiers;
    NSEventModifierFlags mods = (event.modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask);
    BOOL isCmdDown = (mods & NSEventModifierFlagCommand) != 0;
    if ((mods == 0 && event.keyCode == 53) || (isCmdDown && [typed isEqualToString:@"."])) { // ESC key or cmd-.
        [NSApp stopModalWithCode:1001]; // 1001 is the second button's response code
    }
    return [super performKeyEquivalent:event];
}
@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    [self alertTest];
    [NSApp terminate:0];
}

- (void)alertTest {
    NSAlert *alert = [NSAlert new];
    alert.messageText = @"alert msg";
    [alert addButtonWithTitle:@"OK"];
    NSButton *cancelButton = [alert addButtonWithTitle:@"Cancel"];
    alert.window.defaultButtonCell = cancelButton.cell;
    alert.accessoryView = [AlertEscHandler new];
    NSModalResponse choice = [alert runModal];
    NSLog (@"User chose button %d", (int)choice);
}