Home | History | Annotate | Download | only in cocoa
      1 // Copyright (c) 2010 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/chrome_event_processing_window.h"
      6 
      7 #include "base/logging.h"
      8 #import "chrome/browser/renderer_host/render_widget_host_view_mac.h"
      9 #import "chrome/browser/ui/cocoa/browser_command_executor.h"
     10 #import "chrome/browser/ui/cocoa/browser_frame_view.h"
     11 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
     12 #include "chrome/browser/global_keyboard_shortcuts_mac.h"
     13 
     14 typedef int (*KeyToCommandMapper)(bool, bool, bool, bool, int, unichar);
     15 
     16 @interface ChromeEventProcessingWindow ()
     17 // Duplicate the given key event, but changing the associated window.
     18 - (NSEvent*)keyEventForWindow:(NSWindow*)window fromKeyEvent:(NSEvent*)event;
     19 @end
     20 
     21 @implementation ChromeEventProcessingWindow
     22 
     23 - (BOOL)handleExtraKeyboardShortcut:(NSEvent*)event fromTable:
     24     (KeyToCommandMapper)commandForKeyboardShortcut {
     25   // Extract info from |event|.
     26   NSUInteger modifers = [event modifierFlags];
     27   const bool cmdKey = modifers & NSCommandKeyMask;
     28   const bool shiftKey = modifers & NSShiftKeyMask;
     29   const bool cntrlKey = modifers & NSControlKeyMask;
     30   const bool optKey = modifers & NSAlternateKeyMask;
     31   const unichar keyCode = [event keyCode];
     32   const unichar keyChar = KeyCharacterForEvent(event);
     33 
     34   int cmdNum = commandForKeyboardShortcut(cmdKey, shiftKey, cntrlKey, optKey,
     35       keyCode, keyChar);
     36 
     37   if (cmdNum != -1) {
     38     id executor = [self delegate];
     39     // A bit of sanity.
     40     DCHECK([executor conformsToProtocol:@protocol(BrowserCommandExecutor)]);
     41     DCHECK([executor respondsToSelector:@selector(executeCommand:)]);
     42     [executor executeCommand:cmdNum];
     43     return YES;
     44   }
     45   return NO;
     46 }
     47 
     48 - (BOOL)handleExtraWindowKeyboardShortcut:(NSEvent*)event {
     49   return [self handleExtraKeyboardShortcut:event
     50                                  fromTable:CommandForWindowKeyboardShortcut];
     51 }
     52 
     53 - (BOOL)handleDelayedWindowKeyboardShortcut:(NSEvent*)event {
     54   return [self handleExtraKeyboardShortcut:event
     55                          fromTable:CommandForDelayedWindowKeyboardShortcut];
     56 }
     57 
     58 - (BOOL)handleExtraBrowserKeyboardShortcut:(NSEvent*)event {
     59   return [self handleExtraKeyboardShortcut:event
     60                                  fromTable:CommandForBrowserKeyboardShortcut];
     61 }
     62 
     63 - (BOOL)performKeyEquivalent:(NSEvent*)event {
     64   if (redispatchingEvent_)
     65     return NO;
     66 
     67   // Give the web site a chance to handle the event. If it doesn't want to
     68   // handle it, it will call us back with one of the |handle*| methods above.
     69   NSResponder* r = [self firstResponder];
     70   if ([r isKindOfClass:[RenderWidgetHostViewCocoa class]])
     71     return [r performKeyEquivalent:event];
     72 
     73   // If the delegate does not implement the BrowserCommandExecutor protocol,
     74   // then we don't need to handle browser specific shortcut keys.
     75   if (![[self delegate] conformsToProtocol:@protocol(BrowserCommandExecutor)])
     76     return [super performKeyEquivalent:event];
     77 
     78   // Handle per-window shortcuts like cmd-1, but do not handle browser-level
     79   // shortcuts like cmd-left (else, cmd-left would do history navigation even
     80   // if e.g. the Omnibox has focus).
     81   if ([self handleExtraWindowKeyboardShortcut:event])
     82     return YES;
     83 
     84   if ([super performKeyEquivalent:event])
     85     return YES;
     86 
     87   // Handle per-window shortcuts like Esc after giving everybody else a chance
     88   // to handle them
     89   return [self handleDelayedWindowKeyboardShortcut:event];
     90 }
     91 
     92 - (BOOL)redispatchKeyEvent:(NSEvent*)event {
     93   DCHECK(event);
     94   NSEventType eventType = [event type];
     95   if (eventType != NSKeyDown &&
     96       eventType != NSKeyUp &&
     97       eventType != NSFlagsChanged) {
     98     NOTREACHED();
     99     return YES;  // Pretend it's been handled in an effort to limit damage.
    100   }
    101 
    102   // Ordinarily, the event's window should be this window. However, when
    103   // switching between normal and fullscreen mode, we switch out the window, and
    104   // the event's window might be the previous window (or even an earlier one if
    105   // the renderer is running slowly and several mode switches occur). In this
    106   // rare case, we synthesize a new key event so that its associate window
    107   // (number) is our own.
    108   if ([event window] != self)
    109     event = [self keyEventForWindow:self fromKeyEvent:event];
    110 
    111   // Redispatch the event.
    112   eventHandled_ = YES;
    113   redispatchingEvent_ = YES;
    114   [NSApp sendEvent:event];
    115   redispatchingEvent_ = NO;
    116 
    117   // If the event was not handled by [NSApp sendEvent:], the sendEvent:
    118   // method below will be called, and because |redispatchingEvent_| is YES,
    119   // |eventHandled_| will be set to NO.
    120   return eventHandled_;
    121 }
    122 
    123 - (void)sendEvent:(NSEvent*)event {
    124   if (!redispatchingEvent_)
    125     [super sendEvent:event];
    126   else
    127     eventHandled_ = NO;
    128 }
    129 
    130 - (NSEvent*)keyEventForWindow:(NSWindow*)window fromKeyEvent:(NSEvent*)event {
    131   NSEventType eventType = [event type];
    132 
    133   // Convert the event's location from the original window's coordinates into
    134   // our own.
    135   NSPoint eventLoc = [event locationInWindow];
    136   eventLoc = [[event window] convertBaseToScreen:eventLoc];
    137   eventLoc = [self convertScreenToBase:eventLoc];
    138 
    139   // Various things *only* apply to key down/up.
    140   BOOL eventIsARepeat = NO;
    141   NSString* eventCharacters = nil;
    142   NSString* eventUnmodCharacters = nil;
    143   if (eventType == NSKeyDown || eventType == NSKeyUp) {
    144     eventIsARepeat = [event isARepeat];
    145     eventCharacters = [event characters];
    146     eventUnmodCharacters = [event charactersIgnoringModifiers];
    147   }
    148 
    149   // This synthesis may be slightly imperfect: we provide nil for the context,
    150   // since I (viettrungluu) am sceptical that putting in the original context
    151   // (if one is given) is valid.
    152   return [NSEvent keyEventWithType:eventType
    153                           location:eventLoc
    154                      modifierFlags:[event modifierFlags]
    155                          timestamp:[event timestamp]
    156                       windowNumber:[window windowNumber]
    157                            context:nil
    158                         characters:eventCharacters
    159        charactersIgnoringModifiers:eventUnmodCharacters
    160                          isARepeat:eventIsARepeat
    161                            keyCode:[event keyCode]];
    162 }
    163 
    164 @end  // ChromeEventProcessingWindow
    165