summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa/nsmenuitem_additions.mm
blob: 2a4ccde59cbfc8a6067b96b7abc2e9626adf276d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#import "chrome/browser/cocoa/nsmenuitem_additions.h"

#include <Carbon/Carbon.h>

#include "base/logging.h"

@implementation NSMenuItem(ChromeAdditions)

- (BOOL)cr_firesForKeyEvent:(NSEvent*)event {
  DCHECK([event type] == NSKeyDown);
  if (![self isEnabled])
    return NO;

  // In System Preferences->Keyboard->Keyboard Shortcuts, it is possible to add
  // arbitrary keyboard shortcuts to applications. It is not documented how this
  // works in detail, but |NSMenuItem| has a method |userKeyEquivalent| that
  // sounds related.
  // However, it looks like |userKeyEquivalent| is equal to |keyEquivalent| when
  // a user shortcut is set in system preferences, i.e. Cocoa automatically
  // sets/overwrites |keyEquivalent| as well. Hence, this method can ignore
  // |userKeyEquivalent| and check |keyEquivalent| only.

  // Menu item key equivalents are nearly all stored without modifiers. The
  // exception is shift, which is included in the key and not in the modifiers
  // for printable characters (but not for stuff like arrow keys etc).
  NSString* eventString = [event charactersIgnoringModifiers];
  NSUInteger eventModifiers =
      [event modifierFlags] & NSDeviceIndependentModifierFlagsMask;

  if ([eventString length] == 0 || [[self keyEquivalent] length] == 0)
    return NO;

  // Turns out esc never fires unless cmd or ctrl is down.
  if ([event keyCode] == kVK_Escape &&
      (eventModifiers & (NSControlKeyMask | NSCommandKeyMask)) == 0)
    return NO;

  // From the |NSMenuItem setKeyEquivalent:| documentation:
  //
  // If you want to specify the Backspace key as the key equivalent for a menu
  // item, use a single character string with NSBackspaceCharacter (defined in
  // NSText.h as 0x08) and for the Forward Delete key, use NSDeleteCharacter
  // (defined in NSText.h as 0x7F). Note that these are not the same characters
  // you get from an NSEvent key-down event when pressing those keys.
  if ([[self keyEquivalent] characterAtIndex:0] == NSBackspaceCharacter
      && [eventString characterAtIndex:0] == NSDeleteCharacter) {
    unichar chr = NSBackspaceCharacter;
    eventString = [NSString stringWithCharacters:&chr length:1];

    // Make sure "shift" is not removed from modifiers below.
    eventModifiers |= NSFunctionKeyMask;
  }
  if ([[self keyEquivalent] characterAtIndex:0] == NSDeleteCharacter &&
      [eventString characterAtIndex:0] == NSDeleteFunctionKey) {
    unichar chr = NSDeleteCharacter;
    eventString = [NSString stringWithCharacters:&chr length:1];

    // Make sure "shift" is not removed from modifiers below.
    eventModifiers |= NSFunctionKeyMask;
  }

  // cmd-opt-a gives some weird char as characters and "a" as
  // charactersWithoutModifiers with an US layout, but an "a" as characters and
  // a weird char as "charactersWithoutModifiers" with a cyrillic layout. Oh,
  // Cocoa! Instead of getting the current layout from Text Input Services,
  // and then requesting the kTISPropertyUnicodeKeyLayoutData and looking in
  // there, let's try a pragmatic hack.
  if ([eventString characterAtIndex:0] > 0x7f &&
      [[event characters] length] > 0 &&
      [[event characters] characterAtIndex:0] <= 0x7f)
    eventString = [event characters];

  // When both |characters| and |charactersIgnoringModifiers| are ascii, we
  // want to use |characters| if it's a character and
  // |charactersIgnoringModifiers| else (on dvorak, cmd-shift-z should fire
  // "cmd-:" instead of "cmd-;", but on dvorak-qwerty, cmd-shift-z should fire
  // cmd-shift-z instead of cmd-:).
  if ([eventString characterAtIndex:0] <= 0x7f &&
      [[event characters] length] > 0 &&
      [[event characters] characterAtIndex:0] <= 0x7f &&
      isalpha([[event characters] characterAtIndex:0]))
    eventString = [event characters];

  // Clear shift key for printable characters.
  if ((eventModifiers & (NSNumericPadKeyMask | NSFunctionKeyMask)) == 0 &&
      [[self keyEquivalent] characterAtIndex:0] != '\r')
    eventModifiers &= ~NSShiftKeyMask;

  // Clear all non-interesting modifiers
  eventModifiers &= NSCommandKeyMask |
                    NSControlKeyMask |
                    NSAlternateKeyMask |
                    NSShiftKeyMask;

  return [eventString isEqualToString:[self keyEquivalent]]
      && eventModifiers == [self keyEquivalentModifierMask];
}

@end