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. 4 5 #import "chrome/browser/ui/cocoa/nsmenuitem_additions.h" 6 7 #include <Carbon/Carbon.h> 8 9 #include "base/logging.h" 10 11 @implementation NSMenuItem(ChromeAdditions) 12 13 - (BOOL)cr_firesForKeyEvent:(NSEvent*)event { 14 if (![self isEnabled]) 15 return NO; 16 return [self cr_firesForKeyEventIfEnabled:event]; 17 } 18 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 24 // sounds related. 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. 29 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; 36 37 if ([eventString length] == 0 || [[self keyEquivalent] length] == 0) 38 return NO; 39 40 // Turns out esc never fires unless cmd or ctrl is down. 41 if ([event keyCode] == kVK_Escape && 42 (eventModifiers & (NSControlKeyMask | NSCommandKeyMask)) == 0) 43 return NO; 44 45 // From the |NSMenuItem setKeyEquivalent:| documentation: 46 // 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]; 56 57 // Make sure "shift" is not removed from modifiers below. 58 eventModifiers |= NSFunctionKeyMask; 59 } 60 if ([[self keyEquivalent] characterAtIndex:0] == NSDeleteCharacter && 61 [eventString characterAtIndex:0] == NSDeleteFunctionKey) { 62 unichar chr = NSDeleteCharacter; 63 eventString = [NSString stringWithCharacters:&chr length:1]; 64 65 // Make sure "shift" is not removed from modifiers below. 66 eventModifiers |= NSFunctionKeyMask; 67 } 68 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]; 79 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]; 90 91 // Clear shift key for printable characters. 92 if ((eventModifiers & (NSNumericPadKeyMask | NSFunctionKeyMask)) == 0 && 93 [[self keyEquivalent] characterAtIndex:0] != '\r') 94 eventModifiers &= ~NSShiftKeyMask; 95 96 // Clear all non-interesting modifiers 97 eventModifiers &= NSCommandKeyMask | 98 NSControlKeyMask | 99 NSAlternateKeyMask | 100 NSShiftKeyMask; 101 102 return [eventString isEqualToString:[self keyEquivalent]] 103 && eventModifiers == [self keyEquivalentModifierMask]; 104 } 105 106 @end 107