From 52533a5203233c543dd7e1a9c8ee324ec88ea571 Mon Sep 17 00:00:00 2001 From: "joshmoz@gmail.com" Date: Mon, 28 Jan 2008 22:11:06 -0800 Subject: [PATCH] fix performKeyEvent handling so we don't beep when commands are successful. b=376077 r=smichaud sr=roc --- widget/src/cocoa/nsChildView.h | 2 +- widget/src/cocoa/nsChildView.mm | 50 ++++++++++++++----- widget/src/cocoa/nsIMenuItem.h | 18 ++----- widget/src/cocoa/nsMenuBarX.h | 68 +++++++++++++++++++++++++- widget/src/cocoa/nsMenuBarX.mm | 23 +++++++++ widget/src/cocoa/nsMenuItemX.h | 4 +- widget/src/cocoa/nsMenuItemX.mm | 60 +++++++++-------------- xpfe/appshell/src/nsWebShellWindow.cpp | 2 + 8 files changed, 157 insertions(+), 70 deletions(-) diff --git a/widget/src/cocoa/nsChildView.h b/widget/src/cocoa/nsChildView.h index 355ff13198ca..61549356f2e4 100644 --- a/widget/src/cocoa/nsChildView.h +++ b/widget/src/cocoa/nsChildView.h @@ -283,7 +283,7 @@ public: NS_IMETHOD SetMenuBar(nsIMenuBar * aMenuBar); NS_IMETHOD ShowMenuBar(PRBool aShow); - virtual nsIMenuBar* GetMenuBar(); + NS_IMETHOD GetPreferredSize(PRInt32& aWidth, PRInt32& aHeight); NS_IMETHOD SetPreferredSize(PRInt32 aWidth, PRInt32 aHeight); diff --git a/widget/src/cocoa/nsChildView.mm b/widget/src/cocoa/nsChildView.mm index 8d12cf40088d..c54ce29dac8b 100644 --- a/widget/src/cocoa/nsChildView.mm +++ b/widget/src/cocoa/nsChildView.mm @@ -67,6 +67,7 @@ #include "nsCursorManager.h" #include "nsWindowMap.h" #include "nsCocoaUtils.h" +#include "nsMenuBarX.h" #include "gfxContext.h" #include "gfxQuartzSurface.h" @@ -823,12 +824,6 @@ NS_IMETHODIMP nsChildView::ShowMenuBar(PRBool aShow) } -nsIMenuBar* nsChildView::GetMenuBar() -{ - return nsnull; // subviews don't have menu bars -} - - // Override to set the cursor on the mac NS_IMETHODIMP nsChildView::SetCursor(nsCursor aCursor) { @@ -3221,6 +3216,7 @@ enum kInsertKeyCode = 0x72, // also help key kDeleteKeyCode = 0x75, // also forward delete key kTabKeyCode = 0x30, + kTildeKeyCode = 0x32, kBackspaceKeyCode = 0x33, kHomeKeyCode = 0x73, kEndKeyCode = 0x77, @@ -4059,18 +4055,46 @@ static BOOL keyUpAlreadySentKeyDown = NO; - (BOOL)performKeyEquivalent:(NSEvent*)theEvent { - // don't bother if we don't have a gecko widget or we're in composition - if (!mGeckoChild || nsTSMManager::IsComposing()) + // First we need to ignore certain system commands. If we don't we'll have + // to duplicate their functionality in Gecko, which as of this time we haven't. + // The only thing we ignore now is command-tilde, because NSApp handles that for us + // and we need the event to propagate to there. + unsigned int modifierFlags = [theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask; + if (modifierFlags & NSCommandKeyMask && [theEvent keyCode] == kTildeKeyCode) return NO; - // see if the menu system will handle the event - if ([[NSApp mainMenu] performKeyEquivalent:theEvent]) + // don't bother if we don't have a gecko widget or we're in composition + if (!mGeckoChild || nsTSMManager::IsComposing()) return YES; + // see if the menu system will handle the event + if ([[NSApp mainMenu] performKeyEquivalent:theEvent]) { + return YES; + } + else { + // On Mac OS X 10.5 NSMenu's performKeyEquivalent: method returns NO for disabled menu + // items that have a matching key equiv. We need to know if that was the case so we can + // stop here like we would on 10.4 (it returns YES in that case). Since we want to eat + // the event if that happens the system won't give the disabled command beep, do it here + // manually. + if (nsToolkit::OnLeopardOrLater()) { + id delegate = [[self window] delegate]; + if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) { + nsCocoaWindow* toplevelWindow = [delegate geckoWidget]; + if (toplevelWindow) { + nsMenuBarX* menuBar = static_cast(toplevelWindow->GetMenuBar()); + if (menuBar && menuBar->ContainsKeyEquiv(modifierFlags, [theEvent characters])) { + NSBeep(); + return YES; + } + } + } + } + } + // 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 - unsigned int modifierFlags = [theEvent modifierFlags]; if ((modifierFlags & NSFunctionKeyMask) || (modifierFlags & NSNumericPadKeyMask)) return NO; @@ -4083,7 +4107,9 @@ static BOOL keyUpAlreadySentKeyDown = NO; ConvertCocoaKeyEventToMacEvent(theEvent, macEvent); geckoEvent.nativeMsg = &macEvent; - return (BOOL)mGeckoChild->DispatchWindowEvent(geckoEvent); + mGeckoChild->DispatchWindowEvent(geckoEvent); + + return YES; } diff --git a/widget/src/cocoa/nsIMenuItem.h b/widget/src/cocoa/nsIMenuItem.h index 4e5be47c2c3e..7d648527f516 100644 --- a/widget/src/cocoa/nsIMenuItem.h +++ b/widget/src/cocoa/nsIMenuItem.h @@ -44,10 +44,10 @@ #include "nsIDOMElement.h" -// CC986E81-9F46-4AA2-B809-C544789E6F06 +// 33FA04E3-EAFE-4DD1-AFB3-B3BC8C712716 #define NS_IMENUITEM_IID \ -{ 0xCC986E81, 0x9F46, 0x4AA2, \ - { 0xB8, 0x09, 0xC5, 0x44, 0x78, 0x9E, 0x6F, 0x06 } } +{ 0x33FA04E3, 0xEAFE, 0x4DD1, \ + { 0xAF, 0xB3, 0xB3, 0xBC, 0x8C, 0x71, 0x27, 0x16 } } class nsIMenu; class nsIWidget; @@ -85,12 +85,6 @@ class nsIMenuItem : public nsISupports { */ NS_IMETHOD GetLabel(nsString &aText) = 0; - /** - * Set the Menu shortcut char - * - */ - NS_IMETHOD SetShortcutChar(const nsString &aText) = 0; - /** * Get the Menu shortcut char * @@ -145,12 +139,6 @@ class nsIMenuItem : public nsISupports { */ NS_IMETHOD DispatchDOMEvent(const nsString &eventName, PRBool *preventDefaultCalled) = 0; - /** - * - */ - NS_IMETHOD SetModifiers(PRUint8 aModifiers) = 0; - NS_IMETHOD GetModifiers(PRUint8 * aModifiers) = 0; - /** * Sets an appropriate icon for the menu item. */ diff --git a/widget/src/cocoa/nsMenuBarX.h b/widget/src/cocoa/nsMenuBarX.h index 2f9abd7ccb6d..cb376cb645aa 100644 --- a/widget/src/cocoa/nsMenuBarX.h +++ b/widget/src/cocoa/nsMenuBarX.h @@ -43,6 +43,7 @@ #include "nsIMutationObserver.h" #include "nsCOMArray.h" #include "nsHashtable.h" +#include "nsTHashtable.h" #include "nsWeakReference.h" #include "nsIContent.h" @@ -75,8 +76,66 @@ namespace MenuHelpersX @end +struct CocoaKeyEquivContainer { + CocoaKeyEquivContainer(const unsigned int modifiers, const NSString* string) + { + mModifiers = modifiers; + mString = [string retain]; + } + + ~CocoaKeyEquivContainer() + { + [mString release]; + } + + CocoaKeyEquivContainer(const CocoaKeyEquivContainer& other) + { + mModifiers = other.mModifiers; + mString = [other.mString retain]; + } + + CocoaKeyEquivContainer& operator=(CocoaKeyEquivContainer& other) + { + mModifiers = other.mModifiers; + if (mString) + [mString release]; + mString = [other.mString retain]; + return *this; + } + + unsigned int mModifiers; + NSString* mString; +}; + + +struct CocoaKeyEquivKey : public PLDHashEntryHdr { + typedef const CocoaKeyEquivContainer& KeyType; + typedef const CocoaKeyEquivContainer* KeyTypePointer; + + CocoaKeyEquivKey(KeyTypePointer aObj) : mObj(*aObj) { } + CocoaKeyEquivKey(const CocoaKeyEquivKey& other) : mObj(other.mObj) { } + ~CocoaKeyEquivKey() { } + + KeyType GetKey() const { return mObj; } + + PRBool KeyEquals(KeyTypePointer aKey) const { + return aKey->mModifiers == mObj.mModifiers && + [aKey->mString isEqualToString:mObj.mString]; + } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return [aKey->mString hash] ^ aKey->mModifiers; + } + enum { ALLOW_MEMMOVE = PR_FALSE }; +private: + const CocoaKeyEquivContainer mObj; +}; + + + // -// Native Mac menu bar wrapper +// Native menu bar wrapper // class nsMenuBarX : public nsIMenuBar, @@ -111,6 +170,7 @@ public: NS_IMETHOD Paint(); NS_IMETHOD SetNativeData(void* aData); NS_IMETHOD MenuConstruct(const nsMenuEvent & aMenuEvent, nsIWidget * aParentWindow, void * aMenuNode); + PRBool ContainsKeyEquiv(unsigned int modifiers, NSString* string); PRUint32 RegisterForCommand(nsIMenuItem* aItem); void UnregisterCommand(PRUint32 aCommandID); @@ -119,6 +179,8 @@ public: void UnregisterForContentChanges(nsIContent* aContent); nsChangeObserver* LookupContentChangeObserver(nsIContent* aContent); + void RegisterKeyEquivalent(unsigned int modifiers, NSString* string); + void UnregisterKeyEquivalent(unsigned int modifiers, NSString* string); protected: // Make our menubar conform to Aqua UI guidelines void AquifyMenuBar(); @@ -150,7 +212,9 @@ protected: nsIDocument* mDocument; // pointer to document NSMenu* mRootMenu; // root menu, representing entire menu bar - + + nsTHashtable mKeyEquivTable; + static EventHandlerUPP sCommandEventHandler; // carbon event handler for commands, shared }; diff --git a/widget/src/cocoa/nsMenuBarX.mm b/widget/src/cocoa/nsMenuBarX.mm index b306338261bb..b2145732d8f4 100644 --- a/widget/src/cocoa/nsMenuBarX.mm +++ b/widget/src/cocoa/nsMenuBarX.mm @@ -355,6 +355,8 @@ nsMenuBarX::MenuConstruct(const nsMenuEvent & aMenuEvent, nsIWidget* aParentWind NS_IMETHODIMP nsMenuBarX::Create(nsIWidget *aParent) { SetParent(aParent); + if (!mKeyEquivTable.Init()) + return NS_ERROR_OUT_OF_MEMORY; return NS_OK; } @@ -840,6 +842,27 @@ nsMenuBarX::LookupContentChangeObserver(nsIContent* aContent) } +void nsMenuBarX::RegisterKeyEquivalent(unsigned int modifiers, NSString* string) +{ + const CocoaKeyEquivContainer keyEquiv(modifiers, string); + mKeyEquivTable.PutEntry(keyEquiv); +} + + +void nsMenuBarX::UnregisterKeyEquivalent(unsigned int modifiers, NSString* string) +{ + CocoaKeyEquivContainer keyEquiv(modifiers, string); + mKeyEquivTable.RemoveEntry(keyEquiv); +} + + +PRBool nsMenuBarX::ContainsKeyEquiv(unsigned int modifiers, NSString* string) +{ + const CocoaKeyEquivContainer keyEquiv(modifiers, string); + return (mKeyEquivTable.GetEntry(keyEquiv) != nsnull); +} + + // Given a menu item, creates a unique 4-character command ID and // maps it to the item. Returns the id for use by the client. PRUint32 diff --git a/widget/src/cocoa/nsMenuItemX.h b/widget/src/cocoa/nsMenuItemX.h index 6307bfe80e6b..eae19a90216b 100644 --- a/widget/src/cocoa/nsMenuItemX.h +++ b/widget/src/cocoa/nsMenuItemX.h @@ -68,7 +68,6 @@ public: NS_IMETHOD Create(nsIMenu* aParent, const nsString & aLabel, EMenuItemType aItemType, nsMenuBarX* aMenuBar, nsIContent* aNode); NS_IMETHOD GetLabel(nsString &aText); - NS_IMETHOD SetShortcutChar(const nsString &aText); NS_IMETHOD GetShortcutChar(nsString &aText); NS_IMETHOD GetEnabled(PRBool *aIsEnabled); NS_IMETHOD SetChecked(PRBool aIsEnabled); @@ -79,14 +78,13 @@ public: NS_IMETHOD DoCommand(); NS_IMETHOD DispatchDOMEvent(const nsString &eventName, PRBool *preventDefaultCalled); - NS_IMETHOD SetModifiers(PRUint8 aModifiers); - NS_IMETHOD GetModifiers(PRUint8 * aModifiers); NS_IMETHOD SetupIcon(); NS_IMETHOD GetMenuItemContent(nsIContent ** aMenuItemContent); protected: void UncheckRadioSiblings(nsIContent* inCheckedElement); + void SetKeyEquiv(PRUint8 aModifiers, const nsString &aText); NSMenuItem* mNativeMenuItem; // strong ref, we own diff --git a/widget/src/cocoa/nsMenuItemX.mm b/widget/src/cocoa/nsMenuItemX.mm index 4ed378414499..10b1927f550f 100644 --- a/widget/src/cocoa/nsMenuItemX.mm +++ b/widget/src/cocoa/nsMenuItemX.mm @@ -151,13 +151,12 @@ NS_METHOD nsMenuItemX::Create(nsIMenu* aParent, const nsString & aLabel, EMenuIt nsCOMPtr keyContent(do_QueryInterface(keyElement)); nsAutoString keyChar(NS_LITERAL_STRING(" ")); keyContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::key, keyChar); - if (!keyChar.EqualsLiteral(" ")) - SetShortcutChar(keyChar); - + nsAutoString modifiersStr; keyContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::modifiers, modifiersStr); PRUint8 modifiers = MenuHelpersX::GeckoModifiersForNodeAttribute(modifiersStr); - SetModifiers(modifiers); + + SetKeyEquiv(modifiers, keyChar); } } } @@ -296,39 +295,6 @@ NS_IMETHODIMP nsMenuItemX::DispatchDOMEvent(const nsString &eventName, PRBool *p return NS_OK; } - -NS_METHOD nsMenuItemX::GetModifiers(PRUint8 * aModifiers) -{ - *aModifiers = mModifiers; - return NS_OK; -} - - -NS_METHOD nsMenuItemX::SetModifiers(PRUint8 aModifiers) -{ - mModifiers = aModifiers; - - // set up shortcut key modifiers on native menu item - unsigned int macModifiers = MenuHelpersX::MacModifiersForGeckoModifiers(mModifiers); - [mNativeMenuItem setKeyEquivalentModifierMask:macModifiers]; - - return NS_OK; -} - - -NS_METHOD nsMenuItemX::SetShortcutChar(const nsString &aText) -{ - mKeyEquivalent = aText; - - // set up shortcut key on native menu item - NSString *keyEquivalent = [[NSString stringWithCharacters:(unichar*)mKeyEquivalent.get() - length:mKeyEquivalent.Length()] lowercaseString]; - if (![keyEquivalent isEqualToString:@" "]) - [mNativeMenuItem setKeyEquivalent:keyEquivalent]; - - return NS_OK; -} - NS_METHOD nsMenuItemX::GetShortcutChar(nsString &aText) { @@ -367,6 +333,26 @@ nsMenuItemX::UncheckRadioSiblings(nsIContent* inCheckedContent) } +void nsMenuItemX::SetKeyEquiv(PRUint8 aModifiers, const nsString &aText) +{ + mMenuBar->UnregisterKeyEquivalent([mNativeMenuItem keyEquivalentModifierMask], [mNativeMenuItem keyEquivalent]); + + mModifiers = aModifiers; + unsigned int macModifiers = MenuHelpersX::MacModifiersForGeckoModifiers(mModifiers); + [mNativeMenuItem setKeyEquivalentModifierMask:macModifiers]; + + mKeyEquivalent = aText; + NSString *keyEquivalent = [[NSString stringWithCharacters:(unichar*)mKeyEquivalent.get() + length:mKeyEquivalent.Length()] lowercaseString]; + if ([keyEquivalent isEqualToString:@" "]) + [mNativeMenuItem setKeyEquivalent:@""]; + else + [mNativeMenuItem setKeyEquivalent:keyEquivalent]; + + mMenuBar->RegisterKeyEquivalent([mNativeMenuItem keyEquivalentModifierMask], [mNativeMenuItem keyEquivalent]); +} + + // // nsChangeObserver // diff --git a/xpfe/appshell/src/nsWebShellWindow.cpp b/xpfe/appshell/src/nsWebShellWindow.cpp index 643a459b6e8b..c937f0eeb01d 100644 --- a/xpfe/appshell/src/nsWebShellWindow.cpp +++ b/xpfe/appshell/src/nsWebShellWindow.cpp @@ -531,6 +531,8 @@ static void LoadNativeMenus(nsIDOMDocument *aDOMDoc, nsIWidget *aParentWindow) if (!pnsMenuBar) return; + pnsMenuBar->Create(aParentWindow); + // fake event nsMenuEvent fake(PR_TRUE, 0, nsnull); pnsMenuBar->MenuConstruct(fake, aParentWindow, menubarNode);