Skip to content

Commit

Permalink
Bug 584965: Improve key event handling for Cocoa widgets, follow Coco…
Browse files Browse the repository at this point in the history
…a event propagation model properly. Fixes bug 582052 and bug 379199. r=smichaud
  • Loading branch information
Josh Aas committed Aug 17, 2010
1 parent 32ebc9a commit 42255a3
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 256 deletions.
2 changes: 2 additions & 0 deletions widget/src/cocoa/nsChildView.h
Expand Up @@ -225,6 +225,8 @@ extern "C" long TSMProcessRawKeyEvent(EventRef carbonEvent);
- (void)lockFocus;
- (void) _surfaceNeedsUpdate:(NSNotification*)notification;

- (BOOL)isPluginView;

// Simple gestures support
//
// XXX - The swipeWithEvent, beginGestureWithEvent, magnifyWithEvent,
Expand Down
188 changes: 27 additions & 161 deletions widget/src/cocoa/nsChildView.mm
Expand Up @@ -119,6 +119,9 @@
extern CGError CGSGetWindowLevel(const CGSConnection cid, CGSWindow wid, CGWindowLevel *level);
}

// defined in nsMenuBarX.mm
extern NSMenu* sApplicationMenu; // Application menu shared by all menubars

// these are defined in nsCocoaWindow.mm
extern PRBool gConsumeRollupEvent;

Expand Down Expand Up @@ -179,8 +182,6 @@ - (BOOL)isRectObscuredBySubview:(NSRect)inRect;

- (void)processPendingRedraws;

- (PRBool)processKeyDownEvent:(NSEvent*)theEvent keyEquiv:(BOOL)isKeyEquiv;

- (void)maybeInitContextMenuTracking;

+ (NSEvent*)makeNewCocoaEventWithType:(NSEventType)type fromEvent:(NSEvent*)theEvent;
Expand Down Expand Up @@ -4059,14 +4060,14 @@ static PRBool IsSpecialGeckoKey(UInt32 macKeyCode)
static PRBool IsNormalCharInputtingEvent(const nsKeyEvent& aEvent)
{
// this is not character inputting event, simply.
if (!aEvent.isChar || !aEvent.charCode)
if (!aEvent.isChar || !aEvent.charCode || aEvent.isMeta)
return PR_FALSE;
// if this is unicode char inputting event, we don't need to check
// ctrl/alt/command keys
if (aEvent.charCode > 0x7F)
return PR_TRUE;
// ASCII chars should be inputted without ctrl/alt/command keys
return !aEvent.isControl && !aEvent.isAlt && !aEvent.isMeta;
return !aEvent.isControl && !aEvent.isAlt;
}

// Basic conversion for cocoa to gecko events, common to all conversions.
Expand Down Expand Up @@ -5010,7 +5011,7 @@ + (NSEvent*)makeNewCocoaEventWithType:(NSEventType)type fromEvent:(NSEvent*)theE
// We only send Carbon plugin events with NS_KEY_DOWN gecko events, and only send
// Cocoa plugin events with NS_KEY_PRESS gecko events. This is because we want to
// send repeat key down events to Cocoa plugins but not Carbon plugins.
- (PRBool)processKeyDownEvent:(NSEvent*)theEvent keyEquiv:(BOOL)isKeyEquiv
- (PRBool)processKeyDownEvent:(NSEvent*)theEvent
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;

Expand Down Expand Up @@ -5094,19 +5095,17 @@ - (PRBool)processKeyDownEvent:(NSEvent*)theEvent keyEquiv:(BOOL)isKeyEquiv
}

// Let Cocoa interpret the key events, caching IsIMEComposing first.
// We don't do it if this came from performKeyEquivalent because
// interpretKeyEvents isn't set up to handle those key combinations.
PRBool wasComposing = mGeckoChild->TextInputHandler()->IsIMEComposing();
PRBool interpretKeyEventsCalled = PR_FALSE;
if (!isKeyEquiv &&
(mGeckoChild->TextInputHandler()->IsIMEEnabled() ||
mGeckoChild->TextInputHandler()->IsASCIICapableOnly())) {
if (mGeckoChild->TextInputHandler()->IsIMEEnabled() ||
mGeckoChild->TextInputHandler()->IsASCIICapableOnly()) {
[super interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
interpretKeyEventsCalled = PR_TRUE;
}

if (!mGeckoChild)
return (mKeyDownHandled || mKeyPressHandled);;
if (!mGeckoChild) {
return (mKeyDownHandled || mKeyPressHandled);
}

if (!mKeyPressSent && nonDeadKeyPress && !wasComposing &&
!mGeckoChild->TextInputHandler()->IsIMEComposing()) {
Expand Down Expand Up @@ -5180,6 +5179,15 @@ - (void)activatePluginTSMDoc
}
#endif // NP_NO_CARBON

// This is a private API that Cocoa uses.
// Cocoa will call this after the menu system returns "NO" for "performKeyEquivalent:".
// We want all they key events we can get so just return YES. In particular, this fixes
// ctrl-tab - we don't get a "keyDown:" call for that without this.
- (BOOL)_wantsKeyDownForEvent:(NSEvent*)event
{
return YES;
}

- (void)keyDown:(NSEvent*)theEvent
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
Expand Down Expand Up @@ -5244,7 +5252,13 @@ - (void)keyDown:(NSEvent*)theEvent
#endif
}

[self processKeyDownEvent:theEvent keyEquiv:NO];
PRBool handled = [self processKeyDownEvent:theEvent];

// We always allow keyboard events to propagate to keyDown: but if they are not
// handled we give special Application menu items a chance to act.
if (!handled && sApplicationMenu) {
[sApplicationMenu performKeyEquivalent:theEvent];
}

NS_OBJC_END_TRY_ABORT_BLOCK;
}
Expand Down Expand Up @@ -5327,154 +5341,6 @@ - (void)keyUp:(NSEvent*)theEvent
NS_OBJC_END_TRY_ABORT_BLOCK;
}

- (BOOL)performKeyEquivalent:(NSEvent*)theEvent
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;

// don't do anything if we don't have a gecko widget
if (!mGeckoChild)
return NO;

nsAutoRetainCocoaObject kungFuDeathGrip(self);

// If we're not the first responder and the first responder is an NSView
// object, pass the event on. Otherwise (if, for example, the first
// responder is an NSWindow object) we should trust the OS to have called
// us correctly.
id firstResponder = [[self window] firstResponder];
if (firstResponder != self) {
// Special handling if the other first responder is a ChildView.
if ([firstResponder isKindOfClass:[ChildView class]])
return [(ChildView *)firstResponder performKeyEquivalent:theEvent];
if ([firstResponder isKindOfClass:[NSView class]])
return [super performKeyEquivalent:theEvent];
}

// don't process if we're composing, but don't consume the event
if (mGeckoChild->TextInputHandler()->IsIMEComposing()) {
return NO;
}

UInt32 modifierFlags = [theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;

// Set to true if embedding menus handled the event when a plugin has focus.
// We give menus a crack at handling commands before Gecko in the plugin case.
BOOL handledByEmbedding = NO;

// Perform native menu UI feedback even if we stop the event from propagating to it normally.
// Recall that the menu system won't actually execute any commands for keyboard command invocations.
//
// If this is a plugin, we do actually perform the action on keyboard commands. See bug 428047.
// If the action on plugins here changes the first responder, don't continue.
NSMenu* mainMenu = [NSApp mainMenu];
if (mIsPluginView) {
if ([mainMenu isKindOfClass:[GeckoNSMenu class]]) {
// Maintain a list of cmd+key combinations that we never act on (in the
// browser) when the keyboard focus is in a plugin. What a particular
// cmd+key combo means here (to the browser) is governed by browser.dtd,
// which "contains the browser main menu items".
PRBool dontActOnKeyEquivalent = PR_FALSE;
if (modifierFlags == NSCommandKeyMask) {
NSString *unmodchars = [theEvent charactersIgnoringModifiers];
if ([unmodchars length] == 1) {
if ([unmodchars characterAtIndex:0] ==
nsMenuBarX::GetLocalizedAccelKey("key_selectAll"))
dontActOnKeyEquivalent = PR_TRUE;
}
}
if (dontActOnKeyEquivalent) {
[(GeckoNSMenu*)mainMenu performMenuUserInterfaceEffectsForEvent:theEvent];
} else {
[(GeckoNSMenu*)mainMenu actOnKeyEquivalent:theEvent];
}
}
else {
// This is probably an embedding situation. If the native menu handle the event
// then return YES from pKE no matter what Gecko or the plugin does.
handledByEmbedding = [mainMenu performKeyEquivalent:theEvent];
}
if ([[self window] firstResponder] != self)
return YES;
}
else {
if ([mainMenu isKindOfClass:[GeckoNSMenu class]])
[(GeckoNSMenu*)mainMenu performMenuUserInterfaceEffectsForEvent:theEvent];
}

// With Cmd key or Ctrl+Tab or Ctrl+Esc, keyDown will be never called.
// Therefore, we need to call processKeyDownEvent from performKeyEquivalent.
UInt32 keyCode = [theEvent keyCode];
PRBool keyDownNeverFiredEvent = (modifierFlags & NSCommandKeyMask) ||
((modifierFlags & NSControlKeyMask) &&
(keyCode == kEscapeKeyCode || keyCode == kTabKeyCode));

// don't handle this if certain modifiers are down - those should
// be sent as normal key up/down events and cocoa will do so automatically
// if we reject here
if (!keyDownNeverFiredEvent &&
(modifierFlags & (NSFunctionKeyMask| NSNumericPadKeyMask)))
return handledByEmbedding;

// Control and option modifiers are used when changing input sources in the
// input menu. We need to send such key events via "keyDown:", which will
// happen if we return NO here. This only applies to Mac OS X 10.5 and higher,
// previous OS versions just call "keyDown:" and not "performKeyEquivalent:"
// for such events.
if (!keyDownNeverFiredEvent &&
(modifierFlags & (NSControlKeyMask | NSAlternateKeyMask)))
return handledByEmbedding;

// At this point we're about to hand the event off to "processKeyDownEvent:keyEquiv:".
// Don't bother if this is a Cocoa plugin event, just handle it directly.
if (mGeckoChild && mIsPluginView && mPluginEventModel == NPEventModelCocoa) {
// Reset complex text input request.
mPluginComplexTextInputRequested = NO;

// Send key down event.
nsGUIEvent pluginEvent(PR_TRUE, NS_NON_RETARGETED_PLUGIN_EVENT, mGeckoChild);
NPCocoaEvent cocoaEvent;
ConvertCocoaKeyEventToNPCocoaEvent(theEvent, cocoaEvent);
pluginEvent.pluginEvent = &cocoaEvent;
mGeckoChild->DispatchWindowEvent(pluginEvent);
if (!mGeckoChild)
return YES;

if (!mPluginComplexTextInputRequested) {
// Ideally we'd cancel any TSM composition here.
return YES;
}

// We send these events to Carbon TSM but not to the ComplexTextInputPanel.
// We assume that events coming in through pKE: are only relevant to TSM.
#ifndef NP_NO_CARBON
[self activatePluginTSMDoc];
// We use the active TSM document to pass a pointer to ourselves (the
// currently focused ChildView) to PluginKeyEventsHandler(). Because this
// pointer is weak, we should retain and release ourselves around the call
// to TSMProcessRawKeyEvent().
nsAutoRetainCocoaObject kungFuDeathGrip(self);
::TSMSetDocumentProperty(mPluginTSMDoc, kFocusedChildViewTSMDocPropertyTag,
sizeof(ChildView *), &self);
::TSMProcessRawKeyEvent([theEvent _eventRef]);
::TSMRemoveDocumentProperty(mPluginTSMDoc, kFocusedChildViewTSMDocPropertyTag);
#endif

return YES;
}

if ([theEvent type] == NSKeyDown) {
// We trust the Gecko handled status for cmd key events. See bug 417466 for more info.
if (modifierFlags & NSCommandKeyMask)
return ([self processKeyDownEvent:theEvent keyEquiv:YES] || handledByEmbedding);
else
[self processKeyDownEvent:theEvent keyEquiv:YES];
}

return YES;

NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
}

- (void)flagsChanged:(NSEvent*)theEvent
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
Expand Down
8 changes: 8 additions & 0 deletions widget/src/cocoa/nsCocoaWindow.mm
Expand Up @@ -2034,6 +2034,14 @@ - (void)invalidateShadow
mScheduledShadowInvalidation = NO;
}

- (void) doCommandBySelector:(SEL)aSelector
{
// We override this so that it won't beep if it can't act.
// We want to control the beeping for missing or disabled
// commands ourselves.
[self tryToPerform:aSelector with:nil];
}

@end

// This class allows us to have a "unified toolbar" style window. It works like this:
Expand Down
6 changes: 1 addition & 5 deletions widget/src/cocoa/nsMenuBarX.h
Expand Up @@ -62,15 +62,11 @@ class nsNativeMenuServiceX : public nsINativeMenuService
NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode);
};

// Objective-C class used to allow us to have keyboard commands
// look like they are doing something but actually do nothing.
// Objective-C class used to allow us to intervene with keyboard event handling.
// We allow mouse actions to work normally.
@interface GeckoNSMenu : NSMenu
{
}
- (BOOL)performKeyEquivalent:(NSEvent*)theEvent;
- (void)actOnKeyEquivalent:(NSEvent*)theEvent;
- (void)performMenuUserInterfaceEffectsForEvent:(NSEvent*)theEvent;
@end

// Objective-C class used as action target for menu items
Expand Down

0 comments on commit 42255a3

Please sign in to comment.