Home | History | Annotate | Download | only in cocoa
      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