1// Copyright (c) 2012 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/ui/cocoa/browser_command_executor.h"
9#import "chrome/browser/ui/cocoa/browser_window_controller_private.h"
10#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
11#include "chrome/browser/global_keyboard_shortcuts_mac.h"
12#import "content/public/browser/render_widget_host_view_mac_base.h"
13
14typedef 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  // Some extension commands have higher priority than web content, and some
65  // have lower priority. Regardless of whether the event is being
66  // redispatched, let the extension system try to handle the event.
67  NSWindow* window = event.window;
68  if (window) {
69    BrowserWindowController* controller = [window windowController];
70    if ([controller respondsToSelector:@selector(handledByExtensionCommand:
71                                                                  priority:)]) {
72      ui::AcceleratorManager::HandlerPriority priority =
73          redispatchingEvent_ ? ui::AcceleratorManager::kNormalPriority
74                              : ui::AcceleratorManager::kHighPriority;
75      if ([controller handledByExtensionCommand:event priority:priority])
76        return YES;
77    }
78  }
79
80  if (redispatchingEvent_)
81    return NO;
82
83  // Give the web site a chance to handle the event. If it doesn't want to
84  // handle it, it will call us back with one of the |handle*| methods above.
85  NSResponder* r = [self firstResponder];
86  if ([r conformsToProtocol:@protocol(RenderWidgetHostViewMacBase)])
87    return [r performKeyEquivalent:event];
88
89  // If the delegate does not implement the BrowserCommandExecutor protocol,
90  // then we don't need to handle browser specific shortcut keys.
91  if (![[self delegate] conformsToProtocol:@protocol(BrowserCommandExecutor)])
92    return [super performKeyEquivalent:event];
93
94  // Handle per-window shortcuts like cmd-1, but do not handle browser-level
95  // shortcuts like cmd-left (else, cmd-left would do history navigation even
96  // if e.g. the Omnibox has focus).
97  if ([self handleExtraWindowKeyboardShortcut:event])
98    return YES;
99
100  if ([super performKeyEquivalent:event])
101    return YES;
102
103  // Handle per-window shortcuts like Esc after giving everybody else a chance
104  // to handle them
105  return [self handleDelayedWindowKeyboardShortcut:event];
106}
107
108- (BOOL)redispatchKeyEvent:(NSEvent*)event {
109  DCHECK(event);
110  NSEventType eventType = [event type];
111  if (eventType != NSKeyDown &&
112      eventType != NSKeyUp &&
113      eventType != NSFlagsChanged) {
114    NOTREACHED();
115    return YES;  // Pretend it's been handled in an effort to limit damage.
116  }
117
118  // Ordinarily, the event's window should be this window. However, when
119  // switching between normal and fullscreen mode, we switch out the window, and
120  // the event's window might be the previous window (or even an earlier one if
121  // the renderer is running slowly and several mode switches occur). In this
122  // rare case, we synthesize a new key event so that its associate window
123  // (number) is our own.
124  if ([event window] != self)
125    event = [self keyEventForWindow:self fromKeyEvent:event];
126
127  // Redispatch the event.
128  eventHandled_ = YES;
129  redispatchingEvent_ = YES;
130  [NSApp sendEvent:event];
131  redispatchingEvent_ = NO;
132
133  // If the event was not handled by [NSApp sendEvent:], the sendEvent:
134  // method below will be called, and because |redispatchingEvent_| is YES,
135  // |eventHandled_| will be set to NO.
136  return eventHandled_;
137}
138
139- (void)sendEvent:(NSEvent*)event {
140  if (!redispatchingEvent_)
141    [super sendEvent:event];
142  else
143    eventHandled_ = NO;
144}
145
146- (NSEvent*)keyEventForWindow:(NSWindow*)window fromKeyEvent:(NSEvent*)event {
147  NSEventType eventType = [event type];
148
149  // Convert the event's location from the original window's coordinates into
150  // our own.
151  NSPoint eventLoc = [event locationInWindow];
152  eventLoc = [[event window] convertBaseToScreen:eventLoc];
153  eventLoc = [self convertScreenToBase:eventLoc];
154
155  // Various things *only* apply to key down/up.
156  BOOL eventIsARepeat = NO;
157  NSString* eventCharacters = nil;
158  NSString* eventUnmodCharacters = nil;
159  if (eventType == NSKeyDown || eventType == NSKeyUp) {
160    eventIsARepeat = [event isARepeat];
161    eventCharacters = [event characters];
162    eventUnmodCharacters = [event charactersIgnoringModifiers];
163  }
164
165  // This synthesis may be slightly imperfect: we provide nil for the context,
166  // since I (viettrungluu) am sceptical that putting in the original context
167  // (if one is given) is valid.
168  return [NSEvent keyEventWithType:eventType
169                          location:eventLoc
170                     modifierFlags:[event modifierFlags]
171                         timestamp:[event timestamp]
172                      windowNumber:[window windowNumber]
173                           context:nil
174                        characters:eventCharacters
175       charactersIgnoringModifiers:eventUnmodCharacters
176                         isARepeat:eventIsARepeat
177                           keyCode:[event keyCode]];
178}
179
180@end  // ChromeEventProcessingWindow
181