From f0d97b8c5553ab85a47346ea178dba196f2672d3 Mon Sep 17 00:00:00 2001 From: "masayuki@d-toybox.com" Date: Mon, 14 Apr 2008 21:16:24 -0700 Subject: [PATCH] Bug 359638 accesskeys are incorrectly shifted again (i.e. accesskey=. is broken) and also for b=398264, b=401086, b=414130, b=427797, b=427932, b=427995 r=karlt+ere+josh, sr=roc, a1.9=mconnor --- content/base/public/nsContentUtils.h | 30 ++++ content/base/src/nsContentUtils.cpp | 118 +++++++++++++ content/events/src/nsEventStateManager.cpp | 79 +++++---- content/events/src/nsEventStateManager.h | 3 + content/xbl/src/nsXBLEventHandler.cpp | 62 ++++--- content/xbl/src/nsXBLEventHandler.h | 3 + content/xbl/src/nsXBLPrototypeHandler.cpp | 19 ++- content/xbl/src/nsXBLPrototypeHandler.h | 14 +- content/xbl/src/nsXBLWindowKeyHandler.cpp | 52 +++++- content/xbl/src/nsXBLWindowKeyHandler.h | 8 +- layout/xul/base/src/nsMenuBarFrame.cpp | 41 +++-- layout/xul/base/src/nsMenuBarListener.cpp | 16 +- widget/public/nsGUIEvent.h | 14 ++ widget/src/cocoa/nsChildView.mm | 68 +++++++- widget/src/gtk2/nsNativeKeyBindings.cpp | 55 +++++-- widget/src/gtk2/nsNativeKeyBindings.h | 5 + widget/src/gtk2/nsWindow.cpp | 182 ++++++++++----------- widget/src/windows/nsKeyboardLayout.cpp | 24 +++ widget/src/windows/nsKeyboardLayout.h | 3 + widget/src/windows/nsWindow.cpp | 124 +++++++------- widget/src/windows/nsWindow.h | 8 +- 21 files changed, 671 insertions(+), 257 deletions(-) diff --git a/content/base/public/nsContentUtils.h b/content/base/public/nsContentUtils.h index b15f6952225..7d9df7b166c 100644 --- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -57,6 +57,7 @@ #include "nsIScriptRuntime.h" #include "nsIScriptGlobalObject.h" #include "nsIDOMEvent.h" +#include "nsTArray.h" struct nsNativeKeyEvent; // Don't include nsINativeKeyBindings.h here: it will force strange compilation error! @@ -124,6 +125,15 @@ struct EventNameMapping { PRInt32 mType; }; +struct nsShortcutCandidate { + nsShortcutCandidate(PRUint32 aCharCode, PRBool aIgnoreShift) : + mCharCode(aCharCode), mIgnoreShift(aIgnoreShift) + { + } + PRUint32 mCharCode; + PRBool mIgnoreShift; +}; + class nsContentUtils { public: @@ -1158,6 +1168,26 @@ public: nsNativeKeyEvent* aNativeEvent, PRBool aGetCharCode); + /** + * Get the candidates for accelkeys for aDOMEvent. + * + * @param aDOMEvent [in] the input event for accelkey handling. + * @param aCandidates [out] the candidate shortcut key combination list. + * the first item is most preferred. + */ + static void GetAccelKeyCandidates(nsIDOMEvent* aDOMEvent, + nsTArray& aCandidates); + + /** + * Get the candidates for accesskeys for aDOMEvent. + * + * @param aNativeKeyEvent [in] the input event for accesskey handling. + * @param aCandidates [out] the candidate access key list. + * the first item is most preferred. + */ + static void GetAccessKeyCandidates(nsKeyEvent* aNativeKeyEvent, + nsTArray& aCandidates); + /** * Hide any XUL popups associated with aDocument, including any documents * displayed in child frames. diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index 2e7bec7b15c..aeec6c15c8a 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -3999,6 +3999,124 @@ nsContentUtils::DOMEventToNativeKeyEvent(nsIDOMEvent* aDOMEvent, return PR_TRUE; } +/* static */ +void +nsContentUtils::GetAccelKeyCandidates(nsIDOMEvent* aDOMEvent, + nsTArray& aCandidates) +{ + NS_PRECONDITION(aCandidates.IsEmpty(), "aCandidates must be empty"); + + nsAutoString eventType; + aDOMEvent->GetType(eventType); + // Don't process if aDOMEvent is not a keypress event. + if (!eventType.EqualsLiteral("keypress")) + return; + + nsKeyEvent* nativeKeyEvent = + static_cast(GetNativeEvent(aDOMEvent)); + if (nativeKeyEvent) { + // nsShortcutCandidate::mCharCode is a candidate charCode. + // nsShoftcutCandidate::mIgnoreShift means the mCharCode should be tried to + // execute a command with/without shift key state. If this is TRUE, the + // shifted key state should be ignored. Otherwise, don't ignore the state. + // the priority of the charCodes are (shift key is not pressed): + // 0: charCode/PR_FALSE, + // 1: unshiftedCharCodes[0]/PR_FALSE, 2: unshiftedCharCodes[1]/PR_FALSE... + // the priority of the charCodes are (shift key is pressed): + // 0: charCode/PR_FALSE, + // 1: shiftedCharCodes[0]/PR_FALSE, 2: shiftedCharCodes[0]/PR_TRUE, + // 3: shiftedCharCodes[1]/PR_FALSE, 4: shiftedCharCodes[1]/PR_TRUE... + if (nativeKeyEvent->charCode) { + nsShortcutCandidate key(nativeKeyEvent->charCode, PR_FALSE); + aCandidates.AppendElement(key); + } + + if (!nativeKeyEvent->isShift) { + for (PRUint32 i = 0; + i < nativeKeyEvent->alternativeCharCodes.Length(); ++i) { + PRUint32 ch = + nativeKeyEvent->alternativeCharCodes[0].mUnshiftedCharCode; + if (!ch || ch == nativeKeyEvent->charCode) + continue; + + nsShortcutCandidate key(ch, PR_FALSE); + aCandidates.AppendElement(key); + } + } else { + for (PRUint32 i = 0; + i < nativeKeyEvent->alternativeCharCodes.Length(); ++i) { + PRUint32 ch = nativeKeyEvent->alternativeCharCodes[i].mShiftedCharCode; + if (!ch) + continue; + + if (ch != nativeKeyEvent->charCode) { + nsShortcutCandidate key(ch, PR_FALSE); + aCandidates.AppendElement(key); + } + + // If the char is an alphabet, the shift key state should not be + // ignored. E.g., Ctrl+Shift+C should not execute Ctrl+C. + // And checking the charCode is same as unshiftedCharCode too. + // E.g., for Ctrl+Shift+(Plus of Numpad) should not run Ctrl+Plus. + PRUint32 unshiftCh = + nativeKeyEvent->alternativeCharCodes[i].mUnshiftedCharCode; + if (ch == unshiftCh || + (IS_IN_BMP(ch) && IS_IN_BMP(unshiftCh) && + ToLowerCase(PRUnichar(ch)) == ToLowerCase(PRUnichar(unshiftCh)))) + continue; + + // Setting the alternative charCode candidates for retry without shift + // key state only when the shift key is pressed. + nsShortcutCandidate key(ch, PR_TRUE); + aCandidates.AppendElement(key); + } + } + } else { + nsCOMPtr key(do_QueryInterface(aDOMEvent)); + PRUint32 charCode; + key->GetCharCode(&charCode); + if (charCode) { + nsShortcutCandidate key(charCode, PR_FALSE); + aCandidates.AppendElement(key); + } + } +} + +/* static */ +void +nsContentUtils::GetAccessKeyCandidates(nsKeyEvent* aNativeKeyEvent, + nsTArray& aCandidates) +{ + NS_PRECONDITION(aCandidates.IsEmpty(), "aCandidates must be empty"); + + // return the lower cased charCode candidates for access keys. + // the priority of the charCodes are: + // 0: charCode, 1: unshiftedCharCodes[0], 2: shiftedCharCodes[0] + // 3: unshiftedCharCodes[1], 4: shiftedCharCodes[1],... + if (aNativeKeyEvent->charCode) { + PRUint32 ch = aNativeKeyEvent->charCode; + if (IS_IN_BMP(ch)) + ch = ToLowerCase(PRUnichar(ch)); + aCandidates.AppendElement(ch); + } + for (PRUint32 i = 0; + i < aNativeKeyEvent->alternativeCharCodes.Length(); ++i) { + PRUint32 ch[2] = + { aNativeKeyEvent->alternativeCharCodes[i].mUnshiftedCharCode, + aNativeKeyEvent->alternativeCharCodes[i].mShiftedCharCode }; + for (PRUint32 j = 0; j < 2; ++j) { + if (!ch[j]) + continue; + if (IS_IN_BMP(ch[j])) + ch[j] = ToLowerCase(PRUnichar(ch[j])); + // Don't append the charCode that was already appended. + if (aCandidates.IndexOf(ch[j]) == aCandidates.NoIndex) + aCandidates.AppendElement(ch[j]); + } + } + return; +} + /* static */ void nsContentUtils::AddScriptBlocker() diff --git a/content/events/src/nsEventStateManager.cpp b/content/events/src/nsEventStateManager.cpp index 7b34f96f564..4859ab4253c 100644 --- a/content/events/src/nsEventStateManager.cpp +++ b/content/events/src/nsEventStateManager.cpp @@ -1441,7 +1441,7 @@ GetAccessModifierMask(nsISupports* aDocShell) } static PRBool -IsAccessKeyTarget(nsIContent* aContent, nsIFrame* aFrame, nsString& aKey) +IsAccessKeyTarget(nsIContent* aContent, nsIFrame* aFrame, nsAString& aKey) { if (!aFrame) return PR_FALSE; @@ -1465,6 +1465,45 @@ IsAccessKeyTarget(nsIContent* aContent, nsIFrame* aFrame, nsString& aKey) return PR_FALSE; } +PRBool +nsEventStateManager::ExecuteAccessKey(nsTArray& aAccessCharCodes, + PRBool aIsTrustedEvent) +{ + PRInt32 count, start = -1; + if (mCurrentFocus) { + start = mAccessKeys.IndexOf(mCurrentFocus); + if (start == -1 && mCurrentFocus->GetBindingParent()) + start = mAccessKeys.IndexOf(mCurrentFocus->GetBindingParent()); + } + nsIContent *content; + nsIFrame *frame; + PRInt32 length = mAccessKeys.Count(); + for (PRUint32 i = 0; i < aAccessCharCodes.Length(); ++i) { + PRUint32 ch = aAccessCharCodes[i]; + nsAutoString accessKey; + AppendUCS4ToUTF16(ch, accessKey); + for (count = 1; count <= length; ++count) { + content = mAccessKeys[(start + count) % length]; + frame = mPresContext->PresShell()->GetPrimaryFrameFor(content); + if (IsAccessKeyTarget(content, frame, accessKey)) { + PRBool shouldActivate = sKeyCausesActivation; + while (shouldActivate && ++count <= length) { + nsIContent *oc = mAccessKeys[(start + count) % length]; + nsIFrame *of = mPresContext->PresShell()->GetPrimaryFrameFor(oc); + if (IsAccessKeyTarget(oc, of, accessKey)) + shouldActivate = PR_FALSE; + } + if (shouldActivate) + content->PerformAccesskey(shouldActivate, aIsTrustedEvent); + else if (frame && frame->IsFocusable()) + ChangeFocusWith(content, eEventFocusedByKey); + return PR_TRUE; + } + } + } + return PR_FALSE; +} + void nsEventStateManager::HandleAccessKey(nsPresContext* aPresContext, nsKeyEvent *aEvent, @@ -1476,37 +1515,15 @@ nsEventStateManager::HandleAccessKey(nsPresContext* aPresContext, nsCOMPtr pcContainer = aPresContext->GetContainer(); // Alt or other accesskey modifier is down, we may need to do an accesskey - PRInt32 length = mAccessKeys.Count(); - if (length > 0 && aModifierMask == GetAccessModifierMask(pcContainer)) { + if (mAccessKeys.Count() > 0 && + aModifierMask == GetAccessModifierMask(pcContainer)) { // Someone registered an accesskey. Find and activate it. - nsAutoString accKey(aEvent->charCode); - PRInt32 count, start = -1; - if (mCurrentFocus) { - start = mAccessKeys.IndexOf(mCurrentFocus); - if (start == -1 && mCurrentFocus->GetBindingParent()) - start = mAccessKeys.IndexOf(mCurrentFocus->GetBindingParent()); - } - nsIContent *content; - nsIFrame *frame; - for (count = 1; count <= length; ++count) { - content = mAccessKeys[(start + count) % length]; - frame = mPresContext->PresShell()->GetPrimaryFrameFor(content); - if (IsAccessKeyTarget(content, frame, accKey)) { - PRBool shouldActivate = sKeyCausesActivation; - while (shouldActivate && ++count <= length) { - nsIContent *oc = mAccessKeys[(start + count) % length]; - nsIFrame *of = mPresContext->PresShell()->GetPrimaryFrameFor(oc); - if (IsAccessKeyTarget(oc, of, accKey)) - shouldActivate = PR_FALSE; - } - if (shouldActivate) - content->PerformAccesskey(shouldActivate, - NS_IS_TRUSTED_EVENT(aEvent)); - else if (frame && frame->IsFocusable()) - ChangeFocusWith(content, eEventFocusedByKey); - *aStatus = nsEventStatus_eConsumeNoDefault; - break; - } + PRBool isTrusted = NS_IS_TRUSTED_EVENT(aEvent); + nsAutoTArray accessCharCodes; + nsContentUtils::GetAccessKeyCandidates(aEvent, accessCharCodes); + if (ExecuteAccessKey(accessCharCodes, isTrusted)) { + *aStatus = nsEventStatus_eConsumeNoDefault; + return; } } diff --git a/content/events/src/nsEventStateManager.h b/content/events/src/nsEventStateManager.h index 1200d43c8cf..566bfd77777 100644 --- a/content/events/src/nsEventStateManager.h +++ b/content/events/src/nsEventStateManager.h @@ -268,6 +268,9 @@ protected: ProcessingAccessKeyState aAccessKeyState, PRInt32 aModifierMask); + PRBool ExecuteAccessKey(nsTArray& aAccessCharCodes, + PRBool aIsTrustedEvent); + //--------------------------------------------- // DocShell Focus Traversal Methods //--------------------------------------------- diff --git a/content/xbl/src/nsXBLEventHandler.cpp b/content/xbl/src/nsXBLEventHandler.cpp index e2ebed8c82e..e280dda9407 100644 --- a/content/xbl/src/nsXBLEventHandler.cpp +++ b/content/xbl/src/nsXBLEventHandler.cpp @@ -49,7 +49,9 @@ #include "nsGkAtoms.h" #include "nsXBLPrototypeHandler.h" #include "nsIDOMNSEvent.h" +#include "nsGUIEvent.h" #include "nsContentUtils.h" +#include "nsUnicharUtils.h" nsXBLEventHandler::nsXBLEventHandler(nsXBLPrototypeHandler* aHandler) : mProtoHandler(aHandler) @@ -119,6 +121,36 @@ nsXBLKeyEventHandler::~nsXBLKeyEventHandler() NS_IMPL_ISUPPORTS1(nsXBLKeyEventHandler, nsIDOMEventListener) +PRBool +nsXBLKeyEventHandler::ExecuteMatchedHandlers(nsIDOMKeyEvent* aKeyEvent, + PRUint32 aCharCode, + PRBool aIgnoreShiftKey) +{ + nsCOMPtr domNSEvent = do_QueryInterface(aKeyEvent); + PRBool trustedEvent = PR_FALSE; + if (domNSEvent) + domNSEvent->GetIsTrusted(&trustedEvent); + + nsCOMPtr target; + aKeyEvent->GetCurrentTarget(getter_AddRefs(target)); + nsCOMPtr piTarget = do_QueryInterface(target); + + PRBool executed = PR_FALSE; + for (PRUint32 i = 0; i < mProtoHandlers.Count(); ++i) { + nsXBLPrototypeHandler* handler = static_cast + (mProtoHandlers[i]); + PRBool hasAllowUntrustedAttr = handler->HasAllowUntrustedAttr(); + if ((trustedEvent || + (hasAllowUntrustedAttr && handler->AllowUntrustedEvents()) || + (!hasAllowUntrustedAttr && !mIsBoundToChrome)) && + handler->KeyEventMatched(aKeyEvent, aCharCode, aIgnoreShiftKey)) { + handler->ExecuteHandler(piTarget, aKeyEvent); + executed = PR_TRUE; + } + } + return executed; +} + NS_IMETHODIMP nsXBLKeyEventHandler::HandleEvent(nsIDOMEvent* aEvent) { @@ -133,31 +165,21 @@ nsXBLKeyEventHandler::HandleEvent(nsIDOMEvent* aEvent) return NS_OK; } - nsCOMPtr target; - aEvent->GetCurrentTarget(getter_AddRefs(target)); - nsCOMPtr piTarget = do_QueryInterface(target); - nsCOMPtr key(do_QueryInterface(aEvent)); - nsCOMPtr domNSEvent = do_QueryInterface(aEvent); - PRBool trustedEvent = PR_FALSE; - if (domNSEvent) { - domNSEvent->GetIsTrusted(&trustedEvent); + nsAutoTArray accessKeys; + nsContentUtils::GetAccelKeyCandidates(aEvent, accessKeys); + + if (accessKeys.IsEmpty()) { + ExecuteMatchedHandlers(key, 0, PR_FALSE); + return NS_OK; } - PRUint32 i; - for (i = 0; i < count; ++i) { - nsXBLPrototypeHandler* handler = static_cast - (mProtoHandlers[i]); - PRBool hasAllowUntrustedAttr = handler->HasAllowUntrustedAttr(); - if ((trustedEvent || - (hasAllowUntrustedAttr && handler->AllowUntrustedEvents()) || - (!hasAllowUntrustedAttr && !mIsBoundToChrome)) && - handler->KeyEventMatched(key)) { - handler->ExecuteHandler(piTarget, aEvent); - } + for (PRUint32 i = 0; i < accessKeys.Length(); ++i) { + if (ExecuteMatchedHandlers(key, accessKeys[i].mCharCode, + accessKeys[i].mIgnoreShift)) + return NS_OK; } - return NS_OK; } diff --git a/content/xbl/src/nsXBLEventHandler.h b/content/xbl/src/nsXBLEventHandler.h index 1af7c093d5a..6c984dfb0e1 100644 --- a/content/xbl/src/nsXBLEventHandler.h +++ b/content/xbl/src/nsXBLEventHandler.h @@ -46,6 +46,7 @@ class nsIAtom; class nsIContent; class nsIDOM3EventTarget; +class nsIDOMKeyEvent; class nsPIDOMEventTarget; class nsXBLPrototypeHandler; @@ -121,6 +122,8 @@ public: } private: nsXBLKeyEventHandler(); + PRBool ExecuteMatchedHandlers(nsIDOMKeyEvent* aEvent, PRUint32 aCharCode, + PRBool aIgnoreShiftKey); nsVoidArray mProtoHandlers; nsCOMPtr mEventType; diff --git a/content/xbl/src/nsXBLPrototypeHandler.cpp b/content/xbl/src/nsXBLPrototypeHandler.cpp index 7d847ca1fc2..bb328c27a8c 100644 --- a/content/xbl/src/nsXBLPrototypeHandler.cpp +++ b/content/xbl/src/nsXBLPrototypeHandler.cpp @@ -596,7 +596,9 @@ nsXBLPrototypeHandler::GetController(nsPIDOMEventTarget* aTarget) } PRBool -nsXBLPrototypeHandler::KeyEventMatched(nsIDOMKeyEvent* aKeyEvent) +nsXBLPrototypeHandler::KeyEventMatched(nsIDOMKeyEvent* aKeyEvent, + PRUint32 aCharCode, + PRBool aIgnoreShiftKey) { if (mDetail == -1) return PR_TRUE; // No filters set up. It's generic. @@ -605,8 +607,12 @@ nsXBLPrototypeHandler::KeyEventMatched(nsIDOMKeyEvent* aKeyEvent) PRUint32 code; if (mMisc) { - aKeyEvent->GetCharCode(&code); - code = ToLowerCase(PRUnichar(code)); + if (aCharCode) + code = aCharCode; + else + aKeyEvent->GetCharCode(&code); + if (IS_IN_BMP(code)) + code = ToLowerCase(PRUnichar(code)); } else aKeyEvent->GetKeyCode(&code); @@ -614,7 +620,7 @@ nsXBLPrototypeHandler::KeyEventMatched(nsIDOMKeyEvent* aKeyEvent) if (code != PRUint32(mDetail)) return PR_FALSE; - return ModifiersMatchMask(aKeyEvent); + return ModifiersMatchMask(aKeyEvent, aIgnoreShiftKey); } PRBool @@ -992,7 +998,8 @@ nsXBLPrototypeHandler::ReportKeyConflict(const PRUnichar* aKey, const PRUnichar* } PRBool -nsXBLPrototypeHandler::ModifiersMatchMask(nsIDOMUIEvent* aEvent) +nsXBLPrototypeHandler::ModifiersMatchMask(nsIDOMUIEvent* aEvent, + PRBool aIgnoreShiftKey) { nsCOMPtr key(do_QueryInterface(aEvent)); nsCOMPtr mouse(do_QueryInterface(aEvent)); @@ -1004,7 +1011,7 @@ nsXBLPrototypeHandler::ModifiersMatchMask(nsIDOMUIEvent* aEvent) return PR_FALSE; } - if (mKeyMask & cShiftMask) { + if (mKeyMask & cShiftMask && !aIgnoreShiftKey) { key ? key->GetShiftKey(&keyPresent) : mouse->GetShiftKey(&keyPresent); if (keyPresent != ((mKeyMask & cShift) != 0)) return PR_FALSE; diff --git a/content/xbl/src/nsXBLPrototypeHandler.h b/content/xbl/src/nsXBLPrototypeHandler.h index 0ccaf7b493d..e1b4ead037e 100644 --- a/content/xbl/src/nsXBLPrototypeHandler.h +++ b/content/xbl/src/nsXBLPrototypeHandler.h @@ -91,14 +91,19 @@ public: ~nsXBLPrototypeHandler(); - PRBool KeyEventMatched(nsIDOMKeyEvent* aKeyEvent); + // if aCharCode is not zero, it is used instead of the charCode of aKeyEvent. + PRBool KeyEventMatched(nsIDOMKeyEvent* aKeyEvent, + PRUint32 aCharCode = 0, + PRBool aIgnoreShiftKey = PR_FALSE); inline PRBool KeyEventMatched(nsIAtom* aEventType, - nsIDOMKeyEvent* aEvent) + nsIDOMKeyEvent* aEvent, + PRUint32 aCharCode = 0, + PRBool aIgnoreShiftKey = PR_FALSE) { if (aEventType != mEventName) return PR_FALSE; - return KeyEventMatched(aEvent); + return KeyEventMatched(aEvent, aCharCode, aIgnoreShiftKey); } PRBool MouseEventMatched(nsIDOMMouseEvent* aMouseEvent); @@ -171,7 +176,8 @@ protected: void ReportKeyConflict(const PRUnichar* aKey, const PRUnichar* aModifiers, nsIContent* aElement, const char *aMessageName); void GetEventType(nsAString& type); - PRBool ModifiersMatchMask(nsIDOMUIEvent* aEvent); + PRBool ModifiersMatchMask(nsIDOMUIEvent* aEvent, + PRBool aIgnoreShiftKey = PR_FALSE); nsresult DispatchXBLCommand(nsPIDOMEventTarget* aTarget, nsIDOMEvent* aEvent); nsresult DispatchXULKeyCommand(nsIDOMEvent* aEvent); nsresult EnsureEventHandler(nsIScriptGlobalObject* aGlobal, diff --git a/content/xbl/src/nsXBLWindowKeyHandler.cpp b/content/xbl/src/nsXBLWindowKeyHandler.cpp index 55affb55e81..fe1e1b5f4d7 100644 --- a/content/xbl/src/nsXBLWindowKeyHandler.cpp +++ b/content/xbl/src/nsXBLWindowKeyHandler.cpp @@ -70,6 +70,7 @@ #include "nsIPresShell.h" #include "nsIPrivateDOMEvent.h" #include "nsISelectionController.h" +#include "nsGUIEvent.h" static nsINativeKeyBindings *sNativeEditorBindings = nsnull; @@ -216,6 +217,16 @@ BuildHandlerChain(nsIContent* aContent, nsXBLPrototypeHandler** aResult) nsIContent *key = aContent->GetChildAt(j); if (key->NodeInfo()->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) { + // Check whether the key element has empty value at key/char attribute. + // Such element is used by localizers for alternative shortcut key + // definition on the locale. See bug 426501. + nsAutoString valKey, valChar; + PRBool attrExists = + key->GetAttr(kNameSpaceID_None, nsGkAtoms::key, valKey) || + key->GetAttr(kNameSpaceID_None, nsGkAtoms::charcode, valChar); + if (attrExists && valKey.IsEmpty() && valChar.IsEmpty()) + continue; + nsXBLPrototypeHandler* handler = new nsXBLPrototypeHandler(key); if (!handler) @@ -405,11 +416,13 @@ nsresult nsXBLWindowKeyHandler::KeyPress(nsIDOMEvent* aKeyEvent) // PRBool nsXBLWindowKeyHandler::EventMatched(nsXBLPrototypeHandler* inHandler, - nsIAtom* inEventType, nsIDOMEvent* inEvent) + nsIAtom* inEventType, nsIDOMEvent* inEvent, + PRUint32 aCharCode, PRBool aIgnoreShiftKey) { nsCOMPtr keyEvent(do_QueryInterface(inEvent)); if (keyEvent) - return inHandler->KeyEventMatched(inEventType, keyEvent); + return inHandler->KeyEventMatched(inEventType, keyEvent, aCharCode, + aIgnoreShiftKey); return PR_FALSE; } @@ -458,7 +471,7 @@ nsXBLWindowKeyHandler::IsEditor() } // -// WalkHandlersInternal +// WalkHandlersInternal and WalkHandlersAndExecute // // Given a particular DOM event and a pointer to the first handler in the list, // scan through the list to find something to handle the event and then make it @@ -468,10 +481,34 @@ nsresult nsXBLWindowKeyHandler::WalkHandlersInternal(nsIDOMEvent* aEvent, nsIAtom* aEventType, nsXBLPrototypeHandler* aHandler) +{ + nsAutoTArray accessKeys; + nsContentUtils::GetAccelKeyCandidates(aEvent, accessKeys); + + if (accessKeys.IsEmpty()) { + WalkHandlersAndExecute(aEvent, aEventType, aHandler, 0, PR_FALSE); + return NS_OK; + } + + for (PRUint32 i = 0; i < accessKeys.Length(); ++i) { + nsShortcutCandidate &key = accessKeys[i]; + if (WalkHandlersAndExecute(aEvent, aEventType, aHandler, + key.mCharCode, key.mIgnoreShift)) + return NS_OK; + } + return NS_OK; +} + +PRBool +nsXBLWindowKeyHandler::WalkHandlersAndExecute(nsIDOMEvent* aEvent, + nsIAtom* aEventType, + nsXBLPrototypeHandler* aHandler, + PRUint32 aCharCode, + PRBool aIgnoreShiftKey) { nsresult rv; nsCOMPtr privateEvent(do_QueryInterface(aEvent)); - + // Try all of the handlers until we find one that matches the event. for (nsXBLPrototypeHandler *currHandler = aHandler; currHandler; currHandler = currHandler->GetNextHandler()) { @@ -482,7 +519,8 @@ nsXBLWindowKeyHandler::WalkHandlersInternal(nsIDOMEvent* aEvent, return NS_OK; } - if (!EventMatched(currHandler, aEventType, aEvent)) + if (!EventMatched(currHandler, aEventType, aEvent, + aCharCode, aIgnoreShiftKey)) continue; // try the next one // Before executing this handler, check that it's not disabled, @@ -541,11 +579,11 @@ nsXBLWindowKeyHandler::WalkHandlersInternal(nsIDOMEvent* aEvent, rv = currHandler->ExecuteHandler(piTarget, aEvent); if (NS_SUCCEEDED(rv)) { - return NS_OK; + return PR_TRUE; } } - return NS_OK; + return PR_FALSE; } already_AddRefed diff --git a/content/xbl/src/nsXBLWindowKeyHandler.h b/content/xbl/src/nsXBLWindowKeyHandler.h index 2cf27153e63..245d805b822 100644 --- a/content/xbl/src/nsXBLWindowKeyHandler.h +++ b/content/xbl/src/nsXBLWindowKeyHandler.h @@ -80,13 +80,19 @@ protected: nsIAtom* aEventType, nsXBLPrototypeHandler* aHandler); + // walk the handlers for aEvent, aCharCode and aIgnoreShiftKey + PRBool WalkHandlersAndExecute(nsIDOMEvent* aEvent, nsIAtom* aEventType, + nsXBLPrototypeHandler* aHandler, + PRUint32 aCharCode, PRBool aIgnoreShiftKey); + // lazily load the handlers. Overridden to handle being attached // to a particular element rather than the document nsresult EnsureHandlers(PRBool *aIsEditor); // check if the given handler cares about the given key event PRBool EventMatched(nsXBLPrototypeHandler* inHandler, nsIAtom* inEventType, - nsIDOMEvent* inEvent); + nsIDOMEvent* inEvent, PRUint32 aCharCode, + PRBool aIgnoreShiftKey); // are we working with editor or browser? PRBool IsEditor() ; diff --git a/layout/xul/base/src/nsMenuBarFrame.cpp b/layout/xul/base/src/nsMenuBarFrame.cpp index 2dcf604e11c..2b7ef3f104f 100644 --- a/layout/xul/base/src/nsMenuBarFrame.cpp +++ b/layout/xul/base/src/nsMenuBarFrame.cpp @@ -64,6 +64,8 @@ #include "nsISound.h" #include "nsWidgetsCID.h" #endif +#include "nsContentUtils.h" +#include "nsUTF8Utils.h" // @@ -209,8 +211,17 @@ nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent) { PRUint32 charCode; aKeyEvent->GetCharCode(&charCode); - if (!charCode) // no character was pressed so just return - return nsnull; + + nsAutoTArray accessKeys; + nsEvent* nativeEvent = nsContentUtils::GetNativeEvent(aKeyEvent); + nsKeyEvent* nativeKeyEvent = static_cast(nativeEvent); + if (nativeKeyEvent) + nsContentUtils::GetAccessKeyCandidates(nativeKeyEvent, accessKeys); + if (accessKeys.IsEmpty() && charCode) + accessKeys.AppendElement(charCode); + + if (accessKeys.IsEmpty()) + return nsnull; // no character was pressed so just return // Enumerate over our list of frames. nsIFrame* immediateParent = nsnull; @@ -218,29 +229,39 @@ nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent) if (!immediateParent) immediateParent = this; + // Find a most preferred accesskey which should be returned. + nsIFrame* foundMenu = nsnull; + PRUint32 foundIndex = accessKeys.NoIndex; nsIFrame* currFrame = immediateParent->GetFirstChild(nsnull); while (currFrame) { nsIContent* current = currFrame->GetContent(); - + // See if it's a menu item. if (nsXULPopupManager::IsValidMenuItem(PresContext(), current, PR_FALSE)) { // Get the shortcut attribute. nsAutoString shortcutKey; current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, shortcutKey); if (!shortcutKey.IsEmpty()) { - // We've got something. - PRUnichar letter = PRUnichar(charCode); // throw away the high-zero-fill - if ( shortcutKey.Equals(Substring(&letter, &letter+1), - nsCaseInsensitiveStringComparator()) ) { - // We match! - return (currFrame->GetType() == nsGkAtoms::menuFrame) ? - static_cast(currFrame) : nsnull; + ToLowerCase(shortcutKey); + nsAutoString::const_iterator start, end; + shortcutKey.BeginReading(start); + shortcutKey.EndReading(end); + PRUint32 ch = UTF16CharEnumerator::NextChar(start, end); + PRUint32 index = accessKeys.IndexOf(ch); + if (index != accessKeys.NoIndex && + (foundIndex == kNotFound || index < foundIndex)) { + foundMenu = currFrame; + foundIndex = index; } } } currFrame = currFrame->GetNextSibling(); } + if (foundMenu) { + return (foundMenu->GetType() == nsGkAtoms::menuFrame) ? + static_cast(foundMenu) : nsnull; + } // didn't find a matching menu item #ifdef XP_WIN diff --git a/layout/xul/base/src/nsMenuBarListener.cpp b/layout/xul/base/src/nsMenuBarListener.cpp index 99635e38558..3f6172712cb 100644 --- a/layout/xul/base/src/nsMenuBarListener.cpp +++ b/layout/xul/base/src/nsMenuBarListener.cpp @@ -234,14 +234,22 @@ nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent) keyEvent->GetKeyCode(&keyCode); keyEvent->GetCharCode(&charCode); + PRBool hasAccessKeyCandidates = charCode != 0; + if (!hasAccessKeyCandidates) { + nsEvent* nativeEvent = nsContentUtils::GetNativeEvent(aKeyEvent); + nsKeyEvent* nativeKeyEvent = static_cast(nativeEvent); + if (nativeKeyEvent) { + nsAutoTArray keys; + nsContentUtils::GetAccessKeyCandidates(nativeKeyEvent, keys); + hasAccessKeyCandidates = !keys.IsEmpty(); + } + } + // Clear the access key flag unless we are pressing the access key. if (keyCode != (PRUint32)mAccessKey) mAccessKeyDown = PR_FALSE; - // If charCode == 0, then it is not a printable character. - // Don't attempt to handle accesskey for non-printable characters. - if (IsAccessKeyPressed(keyEvent) && charCode) - { + if (IsAccessKeyPressed(keyEvent) && hasAccessKeyCandidates) { // Do shortcut navigation. // A letter was pressed. We want to see if a shortcut gets matched. If // so, we'll know the menu got activated. diff --git a/widget/public/nsGUIEvent.h b/widget/public/nsGUIEvent.h index 3876b575de3..2399bd40139 100644 --- a/widget/public/nsGUIEvent.h +++ b/widget/public/nsGUIEvent.h @@ -56,6 +56,7 @@ #include "nsIDOMKeyEvent.h" #include "nsWeakPtr.h" #include "nsIWidget.h" +#include "nsTArray.h" class nsIRenderingContext; class nsIRegion; @@ -700,6 +701,16 @@ public: * Keyboard event */ +struct nsAlternativeCharCode { + nsAlternativeCharCode(PRUint32 aUnshiftedCharCode, + PRUint32 aShiftedCharCode) : + mUnshiftedCharCode(aUnshiftedCharCode), mShiftedCharCode(aShiftedCharCode) + { + } + PRUint32 mUnshiftedCharCode; + PRUint32 mShiftedCharCode; +}; + class nsKeyEvent : public nsInputEvent { public: @@ -713,6 +724,9 @@ public: PRUint32 keyCode; /// OS translated Unicode char PRUint32 charCode; + // OS translated Unicode chars which are used for accesskey and accelkey + // handling. The handlers will try from first character to last character. + nsTArray alternativeCharCodes; // indicates whether the event signifies a printable character PRBool isChar; }; diff --git a/widget/src/cocoa/nsChildView.mm b/widget/src/cocoa/nsChildView.mm index cb7698fcd05..a46b622f673 100644 --- a/widget/src/cocoa/nsChildView.mm +++ b/widget/src/cocoa/nsChildView.mm @@ -3879,17 +3879,73 @@ static PRBool IsNormalCharInputtingEvent(const nsKeyEvent& aEvent) outGeckoEvent->charCode = 0; outGeckoEvent->keyCode = 0; // not set for key press events - NSString* unmodifiedChars = [aKeyEvent charactersIgnoringModifiers]; - if ([unmodifiedChars length] > 0) - outGeckoEvent->charCode = [unmodifiedChars characterAtIndex:0]; + NSString* chars = [aKeyEvent characters]; + if ([chars length] > 0) + outGeckoEvent->charCode = [chars characterAtIndex:0]; // convert control-modified charCode to raw charCode (with appropriate case) if (outGeckoEvent->isControl && outGeckoEvent->charCode <= 26) outGeckoEvent->charCode += (outGeckoEvent->isShift) ? ('A' - 1) : ('a' - 1); - // gecko also wants charCode to be in the appropriate case - if (outGeckoEvent->isShift && (outGeckoEvent->charCode >= 'a' && outGeckoEvent->charCode <= 'z')) - outGeckoEvent->charCode -= 32; // convert to uppercase + // If Ctrl or Command is pressed, we should set shiftCharCode and + // unshiftCharCode for accessKeys and accelKeys. + if ((outGeckoEvent->isControl || outGeckoEvent->isMeta) && + !outGeckoEvent->isAlt) { + SInt16 keyLayoutID = + ::GetScriptVariable(::GetScriptManagerVariable(smKeyScript), + smScriptKeys); + Handle handle = ::GetResource('uchr', keyLayoutID); + PRUint32 unshiftedChar = 0; + PRUint32 shiftedChar = 0; + PRUint32 shiftedCmdChar = 0; + if (handle) { + UInt32 kbType = ::LMGetKbdType(); + UInt32 deadKeyState = 0; + UniCharCount len; + UniChar chars[1]; + OSStatus err; + err = ::UCKeyTranslate((UCKeyboardLayout*)*handle, + [aKeyEvent keyCode], + kUCKeyActionDown, 0, + kbType, 0, &deadKeyState, 1, &len, chars); + if (noErr == err && len > 0) + unshiftedChar = chars[0]; + deadKeyState = 0; + err = ::UCKeyTranslate((UCKeyboardLayout*)*handle, [aKeyEvent keyCode], + kUCKeyActionDown, shiftKey >> 8, + kbType, 0, &deadKeyState, 1, &len, chars); + if (noErr == err && len > 0) + shiftedChar = chars[0]; + deadKeyState = 0; + err = ::UCKeyTranslate((UCKeyboardLayout*)*handle, [aKeyEvent keyCode], + kUCKeyActionDown, (cmdKey | shiftKey) >> 8, + kbType, 0, &deadKeyState, 1, &len, chars); + if (noErr == err && len > 0) + shiftedCmdChar = chars[0]; + } else if (handle = (char**)::GetScriptManagerVariable(smKCHRCache)) { + UInt32 state = 0; + UInt32 keyCode = [aKeyEvent keyCode]; + unshiftedChar = ::KeyTranslate(handle, keyCode, &state) & charCodeMask; + keyCode = [aKeyEvent keyCode] | shiftKey; + shiftedChar = ::KeyTranslate(handle, keyCode, &state) & charCodeMask; + keyCode = [aKeyEvent keyCode] | shiftKey | cmdKey; + shiftedCmdChar = ::KeyTranslate(handle, keyCode, &state) & charCodeMask; + } + // If the current keyboad layout is switchable by Cmd key + // (e.g., Dvorak-QWERTY layout), we should not append the alternative + // char codes to unshiftedCharCodes and shiftedCharCodes. + // Because then, the alternative char codes might execute wrong item. + // Therefore, we should check whether the unshiftedChar and shiftedCmdChar + // are same. Because Cmd+Shift+'foo' returns unshifted 'foo'. So, they + // should be same for this case. + // Note that we cannot support the combination of Cmd and Shift needed + // char. (E.g., Cmd++ in US keyboard layout.) + if ((unshiftedChar || shiftedChar) && + (!outGeckoEvent->isMeta || unshiftedChar == shiftedCmdChar)) { + nsAlternativeCharCode altCharCodes(unshiftedChar, shiftedChar); + outGeckoEvent->alternativeCharCodes.AppendElement(altCharCodes); + } + } } else { NSString* characters = nil; diff --git a/widget/src/gtk2/nsNativeKeyBindings.cpp b/widget/src/gtk2/nsNativeKeyBindings.cpp index 1c7a43ab86a..0e658986dc3 100644 --- a/widget/src/gtk2/nsNativeKeyBindings.cpp +++ b/widget/src/gtk2/nsNativeKeyBindings.cpp @@ -280,6 +280,47 @@ nsNativeKeyBindings::KeyPress(const nsNativeKeyEvent& aEvent, else keyCode = DOMKeyCodeToGdkKeyCode(aEvent.keyCode); + if (KeyPressInternal(aEvent, aCallback, aCallbackData, keyCode)) + return PR_TRUE; + + nsKeyEvent *nativeKeyEvent = static_cast(aEvent.nativeEvent); + if (!nativeKeyEvent || nativeKeyEvent->eventStructType != NS_KEY_EVENT && + nativeKeyEvent->message != NS_KEY_PRESS) + return PR_FALSE; + + for (PRUint32 i = 0; i < nativeKeyEvent->alternativeCharCodes.Length(); ++i) { + PRUint32 ch = nativeKeyEvent->isShift ? + nativeKeyEvent->alternativeCharCodes[i].mShiftedCharCode : + nativeKeyEvent->alternativeCharCodes[i].mUnshiftedCharCode; + if (ch && ch != aEvent.charCode) { + keyCode = gdk_unicode_to_keyval(ch); + if (KeyPressInternal(aEvent, aCallback, aCallbackData, keyCode)) + return PR_TRUE; + } + } + +/* gtk_bindings_activate_event is preferable, but it has unresolved bug: http://bugzilla.gnome.org/show_bug.cgi?id=162726 +Also gtk_bindings_activate may work with some non-shortcuts operations (todo: check it) +See bugs 411005 406407 + + Code, which should be used after fixing http://bugzilla.gnome.org/show_bug.cgi?id=162726: + const nsGUIEvent *guiEvent = static_cast(aEvent.nativeEvent); + if (guiEvent && + (guiEvent->message == NS_KEY_PRESS || guiEvent->message == NS_KEY_UP || guiEvent->message == NS_KEY_DOWN) && + guiEvent->nativeMsg) + gtk_bindings_activate_event(GTK_OBJECT(mNativeTarget), + static_cast(guiEvent->nativeMsg)); +*/ + + return PR_FALSE; +} + +PRBool +nsNativeKeyBindings::KeyPressInternal(const nsNativeKeyEvent& aEvent, + DoCommandCallback aCallback, + void *aCallbackData, + PRUint32 aKeyCode) +{ int modifiers = 0; if (aEvent.altKey) modifiers |= GDK_MOD1_MASK; @@ -295,20 +336,8 @@ nsNativeKeyBindings::KeyPress(const nsNativeKeyEvent& aEvent, gHandled = PR_FALSE; gtk_bindings_activate(GTK_OBJECT(mNativeTarget), - keyCode, GdkModifierType(modifiers)); + aKeyCode, GdkModifierType(modifiers)); -/* gtk_bindings_activate_event is preferable, but it has unresolved bug: http://bugzilla.gnome.org/show_bug.cgi?id=162726 -Also gtk_bindings_activate may work with some non-shortcuts operations (todo: check it) -See bugs 411005 406407 - - Code, which should be used after fixing http://bugzilla.gnome.org/show_bug.cgi?id=162726: - const nsGUIEvent *guiEvent = static_cast(aEvent.nativeEvent); - if (guiEvent && - (guiEvent->message == NS_KEY_PRESS || guiEvent->message == NS_KEY_UP || guiEvent->message == NS_KEY_DOWN) && - guiEvent->nativeMsg) - gtk_bindings_activate_event(GTK_OBJECT(mNativeTarget), - static_cast(guiEvent->nativeMsg)); -*/ gCurrentCallback = nsnull; gCurrentCallbackData = nsnull; diff --git a/widget/src/gtk2/nsNativeKeyBindings.h b/widget/src/gtk2/nsNativeKeyBindings.h index b9ff54aa60e..682ca5e8552 100644 --- a/widget/src/gtk2/nsNativeKeyBindings.h +++ b/widget/src/gtk2/nsNativeKeyBindings.h @@ -93,6 +93,11 @@ public: private: ~nsNativeKeyBindings() NS_HIDDEN; + PRBool KeyPressInternal(const nsNativeKeyEvent& aEvent, + DoCommandCallback aCallback, + void *aCallbackData, + PRUint32 aKeyCode); + GtkWidget *mNativeTarget; }; diff --git a/widget/src/gtk2/nsWindow.cpp b/widget/src/gtk2/nsWindow.cpp index a9e517fa53b..15a7dcd3859 100644 --- a/widget/src/gtk2/nsWindow.cpp +++ b/widget/src/gtk2/nsWindow.cpp @@ -2276,14 +2276,6 @@ nsWindow::OnContainerFocusOutEvent(GtkWidget *aWidget, GdkEventFocus *aEvent) LOGFOCUS(("Done with container focus out [%p]\n", (void *)this)); } -inline PRBool -is_latin_shortcut_key(guint aKeyval) -{ - return ((GDK_0 <= aKeyval && aKeyval <= GDK_9) || - (GDK_A <= aKeyval && aKeyval <= GDK_Z) || - (GDK_a <= aKeyval && aKeyval <= GDK_z)); -} - PRBool nsWindow::DispatchCommandEvent(nsIAtom* aCommand) { @@ -2293,6 +2285,45 @@ nsWindow::DispatchCommandEvent(nsIAtom* aCommand) return TRUE; } +static PRUint32 +GetCharCodeFor(const GdkEventKey *aEvent, GdkModifierType aShiftState, + gint aGroup) +{ + guint keyval; + if (gdk_keymap_translate_keyboard_state(NULL, + aEvent->hardware_keycode, + aShiftState, aGroup, + &keyval, NULL, NULL, NULL)) { + GdkEventKey tmpEvent = *aEvent; + tmpEvent.state = guint(aShiftState); + tmpEvent.keyval = keyval; + tmpEvent.group = aGroup; + return nsConvertCharCodeToUnicode(&tmpEvent); + } + return 0; +} + +static gint +GetKeyLevel(GdkEventKey *aEvent) +{ + gint level; + if (!gdk_keymap_translate_keyboard_state(NULL, + aEvent->hardware_keycode, + GdkModifierType(aEvent->state), + aEvent->group, + NULL, NULL, &level, NULL)) + return -1; + return level; +} + +static PRBool +IsBasicLatinLetterOrNumeral(PRUint32 aChar) +{ + return (aChar >= 'a' && aChar <= 'z') || + (aChar >= 'A' && aChar <= 'Z') || + (aChar >= '0' && aChar <= '9'); +} + gboolean nsWindow::OnKeyPressEvent(GtkWidget *aWidget, GdkEventKey *aEvent) { @@ -2382,91 +2413,60 @@ nsWindow::OnKeyPressEvent(GtkWidget *aWidget, GdkEventKey *aEvent) event.charCode = nsConvertCharCodeToUnicode(aEvent); if (event.charCode) { event.keyCode = 0; - // if the control, meta, or alt key is down, then we should leave - // the isShift flag alone (probably not a printable character) - // if none of the other modifier keys are pressed then we need to - // clear isShift so the character can be inserted in the editor - - if (event.isControl || event.isAlt || event.isMeta) { - GdkEventKey tmpEvent = *aEvent; - - // Fix for bug 69230: - // if modifier key is pressed and key pressed is not latin character, - // we should try other keyboard layouts to find out correct latin - // character corresponding to pressed key; - // that way shortcuts like Ctrl+C will work no matter what - // keyboard layout is selected - // We don't try to fix up punctuation accelerators here, - // because their location differs between latin layouts - if (!is_latin_shortcut_key(event.charCode)) { - // We have a non-latin char, try other keyboard groups - GdkKeymapKey *keys; - guint *keyvals; - gint n_entries; - PRUint32 latinCharCode; - gint level; - - if (gdk_keymap_translate_keyboard_state(NULL, - tmpEvent.hardware_keycode, - (GdkModifierType)tmpEvent.state, - tmpEvent.group, - NULL, NULL, &level, NULL) - && gdk_keymap_get_entries_for_keycode(NULL, - tmpEvent.hardware_keycode, - &keys, &keyvals, - &n_entries)) { - gint n; - for (n=0; ngroup); + PRBool isLatin = (altCharCodes.mUnshiftedCharCode <= 0xFF); + // shifted charcode of current keyboard layout. + altCharCodes.mShiftedCharCode = + GetCharCodeFor(aEvent, GDK_SHIFT_MASK, aEvent->group); + isLatin = isLatin && (altCharCodes.mShiftedCharCode <= 0xFF); + if (altCharCodes.mUnshiftedCharCode || + altCharCodes.mShiftedCharCode) { + event.alternativeCharCodes.AppendElement(altCharCodes); } - // make Ctrl+uppercase functional as same as Ctrl+lowercase - // when Ctrl+uppercase(eg.Ctrl+C) is pressed,convert the charCode - // from uppercase to lowercase(eg.Ctrl+c),so do Alt and Meta Key - // It is hack code for bug 61355, there is same code snip for - // Windows platform in widget/src/windows/nsWindow.cpp: See bug 16486 - // Note: if Shift is pressed at the same time, do not to_lower() - // Because Ctrl+Shift has different function with Ctrl - if (!event.isShift && - event.charCode >= GDK_A && - event.charCode <= GDK_Z) - event.charCode = gdk_keyval_to_lower(event.charCode); - - // Keep the characters unshifted for shortcuts and accesskeys and - // make sure that numbers are always passed as such (among others: - // bugs 50255 and 351310) - if (!event.isControl && event.isShift && - (event.charCode < GDK_0 || event.charCode > GDK_9)) { - GdkKeymapKey k = { tmpEvent.hardware_keycode, tmpEvent.group, 0 }; - tmpEvent.keyval = gdk_keymap_lookup_key(gdk_keymap_get_default(), &k); - PRUint32 unshiftedCharCode = nsConvertCharCodeToUnicode(&tmpEvent); - if (unshiftedCharCode) - event.charCode = unshiftedCharCode; - } + if (!isLatin) { + // Next, find latin inputtable keyboard layout. + GdkKeymapKey *keys; + gint count; + gint minGroup = -1; + if (gdk_keymap_get_entries_for_keyval(NULL, GDK_a, + &keys, &count)) { + // find the minimum number group for latin inputtable layout + for (gint i = 0; i < count && minGroup != 0; ++i) { + if (keys[i].level != 0 && keys[i].level != 1) + continue; + if (minGroup >= 0 && keys[i].group > minGroup) + continue; + minGroup = keys[i].group; + } + g_free(keys); + } + if (minGroup >= 0) { + // unshifted charcode of found keyboard layout. + PRUint32 ch = + GetCharCodeFor(aEvent, GdkModifierType(0), minGroup); + altCharCodes.mUnshiftedCharCode = + IsBasicLatinLetterOrNumeral(ch) ? ch : 0; + // shifted charcode of found keyboard layout. + ch = GetCharCodeFor(aEvent, GDK_SHIFT_MASK, minGroup); + altCharCodes.mShiftedCharCode = + IsBasicLatinLetterOrNumeral(ch) ? ch : 0; + if (altCharCodes.mUnshiftedCharCode || + altCharCodes.mShiftedCharCode) { + event.alternativeCharCodes.AppendElement(altCharCodes); + } + } + } } } diff --git a/widget/src/windows/nsKeyboardLayout.cpp b/widget/src/windows/nsKeyboardLayout.cpp index 5400df95105..ae807729e00 100644 --- a/widget/src/windows/nsKeyboardLayout.cpp +++ b/widget/src/windows/nsKeyboardLayout.cpp @@ -324,6 +324,30 @@ PRUint32 KeyboardLayout::GetUniChars (PRUint16* aUniChars, PRUint8* aShiftStates #endif } +PRUint32 +KeyboardLayout::GetUniCharsWithShiftState(PRUint8 aVirtualKey, + PRUint8 aShiftStates, + PRUint16* aUniChars, + PRUint32 aMaxChars) const +{ +#ifndef WINCE + PRInt32 key = GetKeyIndex(aVirtualKey); + if (key < 0) + return 0; + PRUint8 finalShiftState; + PRUint16 uniChars[5]; + PRUint32 numOfBaseChars = + mVirtualKeys[key].GetUniChars(aShiftStates, uniChars, &finalShiftState); + PRUint32 chars = PR_MIN(numOfBaseChars, aMaxChars); + + memcpy(aUniChars, uniChars, chars * sizeof (PRUint16)); + + return chars; +#else + return 0; +#endif +} + void KeyboardLayout::LoadLayout () { #ifndef WINCE diff --git a/widget/src/windows/nsKeyboardLayout.h b/widget/src/windows/nsKeyboardLayout.h index f92e8584fca..66a42be22d6 100644 --- a/widget/src/windows/nsKeyboardLayout.h +++ b/widget/src/windows/nsKeyboardLayout.h @@ -180,6 +180,9 @@ public: void LoadLayout (); void OnKeyDown (PRUint8 aVirtualKey); PRUint32 GetUniChars (PRUint16* aUniChars, PRUint8* aShiftStates, PRUint32 aMaxChars) const; + PRUint32 GetUniCharsWithShiftState(PRUint8 aVirtualKey, PRUint8 aShiftStates, + PRUint16* aUniChars, + PRUint32 aMaxChars) const; }; #endif diff --git a/widget/src/windows/nsWindow.cpp b/widget/src/windows/nsWindow.cpp index 45ddf72bfed..11eb152d568 100644 --- a/widget/src/windows/nsWindow.cpp +++ b/widget/src/windows/nsWindow.cpp @@ -3040,7 +3040,10 @@ UINT nsWindow::MapFromNativeToDOM(UINT aNativeKeyCode) // OnKey // //------------------------------------------------------------------------- -PRBool nsWindow::DispatchKeyEvent(PRUint32 aEventType, WORD aCharCode, UINT aVirtualCharCode, +PRBool nsWindow::DispatchKeyEvent(PRUint32 aEventType, WORD aCharCode, + PRUint32 aUnshiftedCharCode, + PRUint32 aShiftedCharCode, + UINT aVirtualCharCode, LPARAM aKeyData, PRUint32 aFlags) { nsKeyEvent event(PR_TRUE, aEventType, this); @@ -3050,6 +3053,10 @@ PRBool nsWindow::DispatchKeyEvent(PRUint32 aEventType, WORD aCharCode, UINT aVir event.flags |= aFlags; event.charCode = aCharCode; + if (aUnshiftedCharCode || aShiftedCharCode) { + nsAlternativeCharCode altCharCodes(aUnshiftedCharCode, aShiftedCharCode); + event.alternativeCharCodes.AppendElement(altCharCodes); + } event.keyCode = aVirtualCharCode; #ifdef KE_DEBUG @@ -3133,7 +3140,7 @@ BOOL nsWindow::OnKeyDown(UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData) //printf("In OnKeyDown virt: %d scan: %d\n", DOMKeyCode, aScanCode); #endif - BOOL noDefault = DispatchKeyEvent(NS_KEY_DOWN, 0, DOMKeyCode, aKeyData); + BOOL noDefault = DispatchKeyEvent(NS_KEY_DOWN, 0, 0, 0, DOMKeyCode, aKeyData); // If we won't be getting a WM_CHAR, WM_SYSCHAR or WM_DEADCHAR, synthesize a keypress // for almost all keys @@ -3233,9 +3240,13 @@ BOOL nsWindow::OnKeyDown(UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData) if (gKbdLayout.IsDeadKey ()) return PR_FALSE; - PRUint8 shiftStates [5]; - PRUint16 uniChars [5]; + PRUint8 shiftStates[5]; + PRUint16 uniChars[5]; + PRUint16 shiftedChars[5] = {0, 0, 0, 0, 0}; + PRUint16 unshiftedChars[5] = {0, 0, 0, 0, 0}; PRUint32 numOfUniChars = 0; + PRUint32 numOfShiftedChars = 0; + PRUint32 numOfUnshiftedChars = 0; PRUint32 numOfShiftStates = 0; switch (aVirtualKeyCode) { @@ -3258,67 +3269,55 @@ BOOL nsWindow::OnKeyDown(UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData) numOfUniChars = 1; break; default: - if (KeyboardLayout::IsPrintableCharKey (aVirtualKeyCode)) - numOfUniChars = numOfShiftStates = gKbdLayout.GetUniChars (uniChars, shiftStates, NS_ARRAY_LENGTH (uniChars)); + if (KeyboardLayout::IsPrintableCharKey(aVirtualKeyCode)) { + numOfUniChars = numOfShiftStates = + gKbdLayout.GetUniChars(uniChars, shiftStates, + NS_ARRAY_LENGTH(uniChars)); + } - if (mIsControlDown ^ mIsAltDown) - { - // XXX - // For both Alt+key and Ctrl+key combinations we return the latin characters A..Z and - // numbers 0..9, ignoring the real characters returned by active keyboard layout. - // This is required to make sure that all shortcut keys (e.g. Ctrl+c, Ctrl+1, Alt+f) - // work the same way no matter what keyboard layout you are using. - - if ((NS_VK_0 <= DOMKeyCode && DOMKeyCode <= NS_VK_9) || - (NS_VK_A <= DOMKeyCode && DOMKeyCode <= NS_VK_Z)) - { - uniChars [0] = DOMKeyCode; - numOfUniChars = 1; - numOfShiftStates = 0; - - // For letters take the Shift state into account - if (!mIsShiftDown && - NS_VK_A <= DOMKeyCode && DOMKeyCode <= NS_VK_Z) - uniChars [0] += 0x20; - } - else if (!anyCharMessagesRemoved && DOMKeyCode != aVirtualKeyCode) { - switch (DOMKeyCode) { - case NS_VK_ADD: - uniChars [0] = '+'; numOfUniChars = 1; break; - case NS_VK_SUBTRACT: - uniChars [0] = '-'; numOfUniChars = 1; break; - case NS_VK_SEMICOLON: - // XXXmnakano I don't know whether this is correct. - uniChars [0] = ';'; - uniChars [1] = ':'; - numOfUniChars = 2; - break; - default: - NS_ERROR("implement me!"); - } - } + if (mIsControlDown ^ mIsAltDown) { + numOfUnshiftedChars = + gKbdLayout.GetUniCharsWithShiftState(aVirtualKeyCode, 0, + unshiftedChars, NS_ARRAY_LENGTH(unshiftedChars)); + numOfShiftedChars = + gKbdLayout.GetUniCharsWithShiftState(aVirtualKeyCode, eShift, + shiftedChars, NS_ARRAY_LENGTH(shiftedChars)); } } - if (numOfUniChars) - { - for (PRUint32 cnt = 0; cnt < numOfUniChars; cnt++) - { - if (cnt < numOfShiftStates) - { - // If key in combination with Alt and/or Ctrl produces a different character than without them - // then do not report these flags because it is separate keyboard layout shift state. - // If dead-key and base character does not produce a valid composite character then both produced - // dead-key character and following base character may have different modifier flags, too. - mIsShiftDown = (shiftStates [cnt] & eShift) != 0; - mIsControlDown = (shiftStates [cnt] & eCtrl) != 0; - mIsAltDown = (shiftStates [cnt] & eAlt) != 0; + if (numOfUniChars > 0 || numOfShiftedChars > 0 || numOfUnshiftedChars > 0) { + PRUint32 num = PR_MAX(numOfUniChars, + PR_MAX(numOfShiftedChars, numOfUnshiftedChars)); + PRUint32 skipUniChars = num - numOfUniChars; + PRUint32 skipShiftedChars = num - numOfShiftedChars; + PRUint32 skipUnshiftedChars = num - numOfUnshiftedChars; + UINT keyCode = numOfUniChars == 0 ? DOMKeyCode : 0; + for (PRUint32 cnt = 0; cnt < num; cnt++) { + PRUint16 uniChar, shiftedChar, unshiftedChar; + uniChar = shiftedChar = unshiftedChar = 0; + if (skipUniChars <= cnt) { + if (cnt - skipUniChars < numOfShiftStates) { + // If key in combination with Alt and/or Ctrl produces a different + // character than without them then do not report these flags + // because it is separate keyboard layout shift state. If dead-key + // and base character does not produce a valid composite character + // then both produced dead-key character and following base + // character may have different modifier flags, too. + mIsShiftDown = (shiftStates[cnt - skipUniChars] & eShift) != 0; + mIsControlDown = (shiftStates[cnt - skipUniChars] & eCtrl) != 0; + mIsAltDown = (shiftStates[cnt - skipUniChars] & eAlt) != 0; + } + uniChar = uniChars[cnt - skipUniChars]; } - - DispatchKeyEvent(NS_KEY_PRESS, uniChars [cnt], 0, aKeyData, extraFlags); + if (skipShiftedChars <= cnt) + shiftedChar = shiftedChars[cnt - skipShiftedChars]; + if (skipUnshiftedChars <= cnt) + unshiftedChar = unshiftedChars[cnt - skipUnshiftedChars]; + DispatchKeyEvent(NS_KEY_PRESS, uniChar, unshiftedChar, + shiftedChar, keyCode, aKeyData, extraFlags); } } else - DispatchKeyEvent(NS_KEY_PRESS, 0, DOMKeyCode, aKeyData, extraFlags); + DispatchKeyEvent(NS_KEY_PRESS, 0, 0, 0, DOMKeyCode, aKeyData, extraFlags); return noDefault; } @@ -3335,7 +3334,7 @@ BOOL nsWindow::OnKeyUp( UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData) #endif aVirtualKeyCode = sIMEIsComposing ? aVirtualKeyCode : MapFromNativeToDOM(aVirtualKeyCode); - BOOL result = DispatchKeyEvent(NS_KEY_UP, 0, aVirtualKeyCode, aKeyData); + BOOL result = DispatchKeyEvent(NS_KEY_UP, 0, 0, 0, aVirtualKeyCode, aKeyData); return result; } @@ -3412,7 +3411,8 @@ BOOL nsWindow::OnChar(UINT charCode, LPARAM keyData, PRUint32 aFlags) uniChar = towlower(uniChar); } - PRBool result = DispatchKeyEvent(NS_KEY_PRESS, uniChar, charCode, 0, aFlags); + PRBool result = DispatchKeyEvent(NS_KEY_PRESS, uniChar, 0, 0, + charCode, 0, aFlags); mIsAltDown = saveIsAltDown; mIsControlDown = saveIsControlDown; return result; @@ -6535,7 +6535,7 @@ BOOL nsWindow::OnIMEChar(BYTE aByte1, BYTE aByte2, LPARAM aKeyState) // We need to return TRUE here so that Windows doesn't // send two WM_CHAR msgs - DispatchKeyEvent(NS_KEY_PRESS, uniChar, 0, 0); + DispatchKeyEvent(NS_KEY_PRESS, uniChar, 0, 0, 0, 0); return PR_TRUE; } @@ -6815,7 +6815,7 @@ BOOL nsWindow::OnIMENotify(WPARAM aIMN, LPARAM aData, LRESULT *oResult) mIsControlDown = PR_FALSE; mIsAltDown = PR_TRUE; - DispatchKeyEvent(NS_KEY_PRESS, 0, 192, 0); // XXX hack hack hack + DispatchKeyEvent(NS_KEY_PRESS, 0, 0, 0, 192, 0); // XXX hack hack hack if (aIMN == IMN_SETOPENSTATUS) sIMEIsStatusChanged = PR_TRUE; } diff --git a/widget/src/windows/nsWindow.h b/widget/src/windows/nsWindow.h index c749d2594a2..4071debebb7 100644 --- a/widget/src/windows/nsWindow.h +++ b/widget/src/windows/nsWindow.h @@ -331,8 +331,12 @@ protected: nsRect& aEventResult, nsRect& aResult); - virtual PRBool DispatchKeyEvent(PRUint32 aEventType, WORD aCharCode, UINT aVirtualCharCode, - LPARAM aKeyCode, PRUint32 aFlags = 0); + virtual PRBool DispatchKeyEvent(PRUint32 aEventType, WORD aCharCode, + PRUint32 aUnshiftedCharCode, + PRUint32 aShiftedCharCodes, + UINT aVirtualCharCode, + LPARAM aKeyCode, + PRUint32 aFlags = 0); virtual PRBool DispatchFocus(PRUint32 aEventType, PRBool isMozWindowTakingFocus); virtual PRBool OnScroll(UINT scrollCode, int cPos);