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