blob: 43d0feb3e9e780e7edcab49776bfd9e51770bc2d [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/chrome_event_processing_window.h"
#include "base/logging.h"
#import "chrome/browser/ui/cocoa/browser_command_executor.h"
#import "chrome/browser/ui/cocoa/browser_window_controller_private.h"
#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
#include "chrome/browser/global_keyboard_shortcuts_mac.h"
#import "content/public/browser/render_widget_host_view_mac_base.h"
typedef int (*KeyToCommandMapper)(bool, bool, bool, bool, int, unichar);
@interface ChromeEventProcessingWindow ()
// Duplicate the given key event, but changing the associated window.
- (NSEvent*)keyEventForWindow:(NSWindow*)window fromKeyEvent:(NSEvent*)event;
@end
@implementation ChromeEventProcessingWindow
- (BOOL)handleExtraKeyboardShortcut:(NSEvent*)event fromTable:
(KeyToCommandMapper)commandForKeyboardShortcut {
// Extract info from |event|.
NSUInteger modifers = [event modifierFlags];
const bool cmdKey = modifers & NSCommandKeyMask;
const bool shiftKey = modifers & NSShiftKeyMask;
const bool cntrlKey = modifers & NSControlKeyMask;
const bool optKey = modifers & NSAlternateKeyMask;
const unichar keyCode = [event keyCode];
const unichar keyChar = KeyCharacterForEvent(event);
int cmdNum = commandForKeyboardShortcut(cmdKey, shiftKey, cntrlKey, optKey,
keyCode, keyChar);
if (cmdNum != -1) {
id executor = [self delegate];
// A bit of sanity.
DCHECK([executor conformsToProtocol:@protocol(BrowserCommandExecutor)]);
DCHECK([executor respondsToSelector:@selector(executeCommand:)]);
[executor executeCommand:cmdNum];
return YES;
}
return NO;
}
- (BOOL)handleExtraWindowKeyboardShortcut:(NSEvent*)event {
return [self handleExtraKeyboardShortcut:event
fromTable:CommandForWindowKeyboardShortcut];
}
- (BOOL)handleDelayedWindowKeyboardShortcut:(NSEvent*)event {
return [self handleExtraKeyboardShortcut:event
fromTable:CommandForDelayedWindowKeyboardShortcut];
}
- (BOOL)handleExtraBrowserKeyboardShortcut:(NSEvent*)event {
return [self handleExtraKeyboardShortcut:event
fromTable:CommandForBrowserKeyboardShortcut];
}
- (BOOL)performKeyEquivalent:(NSEvent*)event {
// Some extension commands have higher priority than web content, and some
// have lower priority. Regardless of whether the event is being
// redispatched, let the extension system try to handle the event.
NSWindow* window = event.window;
if (window) {
BrowserWindowController* controller = [window windowController];
if ([controller respondsToSelector:@selector(handledByExtensionCommand:
priority:)]) {
ui::AcceleratorManager::HandlerPriority priority =
redispatchingEvent_ ? ui::AcceleratorManager::kNormalPriority
: ui::AcceleratorManager::kHighPriority;
if ([controller handledByExtensionCommand:event priority:priority])
return YES;
}
}
if (redispatchingEvent_)
return NO;
// Give the web site a chance to handle the event. If it doesn't want to
// handle it, it will call us back with one of the |handle*| methods above.
NSResponder* r = [self firstResponder];
if ([r conformsToProtocol:@protocol(RenderWidgetHostViewMacBase)])
return [r performKeyEquivalent:event];
// If the delegate does not implement the BrowserCommandExecutor protocol,
// then we don't need to handle browser specific shortcut keys.
if (![[self delegate] conformsToProtocol:@protocol(BrowserCommandExecutor)])
return [super performKeyEquivalent:event];
// Handle per-window shortcuts like cmd-1, but do not handle browser-level
// shortcuts like cmd-left (else, cmd-left would do history navigation even
// if e.g. the Omnibox has focus).
if ([self handleExtraWindowKeyboardShortcut:event])
return YES;
if ([super performKeyEquivalent:event])
return YES;
// Handle per-window shortcuts like Esc after giving everybody else a chance
// to handle them
return [self handleDelayedWindowKeyboardShortcut:event];
}
- (BOOL)redispatchKeyEvent:(NSEvent*)event {
DCHECK(event);
NSEventType eventType = [event type];
if (eventType != NSKeyDown &&
eventType != NSKeyUp &&
eventType != NSFlagsChanged) {
NOTREACHED();
return YES; // Pretend it's been handled in an effort to limit damage.
}
// Ordinarily, the event's window should be this window. However, when
// switching between normal and fullscreen mode, we switch out the window, and
// the event's window might be the previous window (or even an earlier one if
// the renderer is running slowly and several mode switches occur). In this
// rare case, we synthesize a new key event so that its associate window
// (number) is our own.
if ([event window] != self)
event = [self keyEventForWindow:self fromKeyEvent:event];
// Redispatch the event.
eventHandled_ = YES;
redispatchingEvent_ = YES;
[NSApp sendEvent:event];
redispatchingEvent_ = NO;
// If the event was not handled by [NSApp sendEvent:], the sendEvent:
// method below will be called, and because |redispatchingEvent_| is YES,
// |eventHandled_| will be set to NO.
return eventHandled_;
}
- (void)sendEvent:(NSEvent*)event {
if (!redispatchingEvent_)
[super sendEvent:event];
else
eventHandled_ = NO;
}
- (NSEvent*)keyEventForWindow:(NSWindow*)window fromKeyEvent:(NSEvent*)event {
NSEventType eventType = [event type];
// Convert the event's location from the original window's coordinates into
// our own.
NSPoint eventLoc = [event locationInWindow];
eventLoc = [[event window] convertBaseToScreen:eventLoc];
eventLoc = [self convertScreenToBase:eventLoc];
// Various things *only* apply to key down/up.
BOOL eventIsARepeat = NO;
NSString* eventCharacters = nil;
NSString* eventUnmodCharacters = nil;
if (eventType == NSKeyDown || eventType == NSKeyUp) {
eventIsARepeat = [event isARepeat];
eventCharacters = [event characters];
eventUnmodCharacters = [event charactersIgnoringModifiers];
}
// This synthesis may be slightly imperfect: we provide nil for the context,
// since I (viettrungluu) am sceptical that putting in the original context
// (if one is given) is valid.
return [NSEvent keyEventWithType:eventType
location:eventLoc
modifierFlags:[event modifierFlags]
timestamp:[event timestamp]
windowNumber:[window windowNumber]
context:nil
characters:eventCharacters
charactersIgnoringModifiers:eventUnmodCharacters
isARepeat:eventIsARepeat
keyCode:[event keyCode]];
}
@end // ChromeEventProcessingWindow