1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/nsmenuitem_additions.h"
7 #include <Carbon/Carbon.h>
9 #include "base/logging.h"
11 @implementation NSMenuItem(ChromeAdditions)
13 - (BOOL)cr_firesForKeyEvent:(NSEvent*)event {
14 if (![self isEnabled])
16 return [self cr_firesForKeyEventIfEnabled:event];
19 - (BOOL)cr_firesForKeyEventIfEnabled:(NSEvent*)event {
20 DCHECK([event type] == NSKeyDown);
21 // In System Preferences->Keyboard->Keyboard Shortcuts, it is possible to add
22 // arbitrary keyboard shortcuts to applications. It is not documented how this
23 // works in detail, but |NSMenuItem| has a method |userKeyEquivalent| that
25 // However, it looks like |userKeyEquivalent| is equal to |keyEquivalent| when
26 // a user shortcut is set in system preferences, i.e. Cocoa automatically
27 // sets/overwrites |keyEquivalent| as well. Hence, this method can ignore
28 // |userKeyEquivalent| and check |keyEquivalent| only.
30 // Menu item key equivalents are nearly all stored without modifiers. The
31 // exception is shift, which is included in the key and not in the modifiers
32 // for printable characters (but not for stuff like arrow keys etc).
33 NSString* eventString = [event charactersIgnoringModifiers];
34 NSUInteger eventModifiers =
35 [event modifierFlags] & NSDeviceIndependentModifierFlagsMask;
37 if ([eventString length] == 0 || [[self keyEquivalent] length] == 0)
40 // Turns out esc never fires unless cmd or ctrl is down.
41 if ([event keyCode] == kVK_Escape &&
42 (eventModifiers & (NSControlKeyMask | NSCommandKeyMask)) == 0)
45 // From the |NSMenuItem setKeyEquivalent:| documentation:
47 // If you want to specify the Backspace key as the key equivalent for a menu
48 // item, use a single character string with NSBackspaceCharacter (defined in
49 // NSText.h as 0x08) and for the Forward Delete key, use NSDeleteCharacter
50 // (defined in NSText.h as 0x7F). Note that these are not the same characters
51 // you get from an NSEvent key-down event when pressing those keys.
52 if ([[self keyEquivalent] characterAtIndex:0] == NSBackspaceCharacter
53 && [eventString characterAtIndex:0] == NSDeleteCharacter) {
54 unichar chr = NSBackspaceCharacter;
55 eventString = [NSString stringWithCharacters:&chr length:1];
57 // Make sure "shift" is not removed from modifiers below.
58 eventModifiers |= NSFunctionKeyMask;
60 if ([[self keyEquivalent] characterAtIndex:0] == NSDeleteCharacter &&
61 [eventString characterAtIndex:0] == NSDeleteFunctionKey) {
62 unichar chr = NSDeleteCharacter;
63 eventString = [NSString stringWithCharacters:&chr length:1];
65 // Make sure "shift" is not removed from modifiers below.
66 eventModifiers |= NSFunctionKeyMask;
69 // cmd-opt-a gives some weird char as characters and "a" as
70 // charactersWithoutModifiers with an US layout, but an "a" as characters and
71 // a weird char as "charactersWithoutModifiers" with a cyrillic layout. Oh,
72 // Cocoa! Instead of getting the current layout from Text Input Services,
73 // and then requesting the kTISPropertyUnicodeKeyLayoutData and looking in
74 // there, let's try a pragmatic hack.
75 if ([eventString characterAtIndex:0] > 0x7f &&
76 [[event characters] length] > 0 &&
77 [[event characters] characterAtIndex:0] <= 0x7f)
78 eventString = [event characters];
80 // When both |characters| and |charactersIgnoringModifiers| are ascii, we
81 // want to use |characters| if it's a character and
82 // |charactersIgnoringModifiers| else (on dvorak, cmd-shift-z should fire
83 // "cmd-:" instead of "cmd-;", but on dvorak-qwerty, cmd-shift-z should fire
84 // cmd-shift-z instead of cmd-:).
85 if ([eventString characterAtIndex:0] <= 0x7f &&
86 [[event characters] length] > 0 &&
87 [[event characters] characterAtIndex:0] <= 0x7f &&
88 isalpha([[event characters] characterAtIndex:0]))
89 eventString = [event characters];
91 // Clear shift key for printable characters.
92 if ((eventModifiers & (NSNumericPadKeyMask | NSFunctionKeyMask)) == 0 &&
93 [[self keyEquivalent] characterAtIndex:0] != '\r')
94 eventModifiers &= ~NSShiftKeyMask;
96 // Clear all non-interesting modifiers
97 eventModifiers &= NSCommandKeyMask |
102 return [eventString isEqualToString:[self keyEquivalent]]
103 && eventModifiers == [self keyEquivalentModifierMask];