From 3675c24e79e872dd57b3c7d6f9d55e07d3d23258 Mon Sep 17 00:00:00 2001 From: "pete.zha%sun.com" Date: Wed, 24 Apr 2002 07:15:35 +0000 Subject: [PATCH] bug 92491:press letters to select item in XUL menu, menulist r=aaronl sr=hyatt Check in for Kyle Yuan's(kyle.yuan@sun.com) fix git-svn-id: svn://10.0.0.236/trunk@119694 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/layout/xul/base/public/nsIMenuFrame.h | 3 +- mozilla/layout/xul/base/src/nsIMenuParent.h | 3 +- .../layout/xul/base/src/nsMenuBarFrame.cpp | 13 +- mozilla/layout/xul/base/src/nsMenuBarFrame.h | 4 +- .../layout/xul/base/src/nsMenuBarListener.cpp | 4 +- mozilla/layout/xul/base/src/nsMenuFrame.cpp | 4 +- mozilla/layout/xul/base/src/nsMenuFrame.h | 2 +- .../layout/xul/base/src/nsMenuListener.cpp | 2 +- .../layout/xul/base/src/nsMenuPopupFrame.cpp | 174 ++++++++++++++---- .../layout/xul/base/src/nsMenuPopupFrame.h | 9 +- 10 files changed, 169 insertions(+), 49 deletions(-) diff --git a/mozilla/layout/xul/base/public/nsIMenuFrame.h b/mozilla/layout/xul/base/public/nsIMenuFrame.h index 30643013e1e..f7d6f377f4f 100644 --- a/mozilla/layout/xul/base/public/nsIMenuFrame.h +++ b/mozilla/layout/xul/base/public/nsIMenuFrame.h @@ -44,6 +44,7 @@ class nsIMenuParent; class nsIDOMElement; +class nsIDOMKeyEvent; enum nsMenuType { eMenuType_Normal = 0, @@ -69,7 +70,7 @@ public: NS_IMETHOD Escape(PRBool& aHandledFlag) = 0; NS_IMETHOD Enter() = 0; - NS_IMETHOD ShortcutNavigation(PRUint32 aLetter, PRBool& aHandledFlag) = 0; + NS_IMETHOD ShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, PRBool& aHandledFlag) = 0; NS_IMETHOD KeyboardNavigation(PRUint32 aDirection, PRBool& aHandledFlag) = 0; NS_IMETHOD GetMenuParent(nsIMenuParent** aMenuParent) = 0; diff --git a/mozilla/layout/xul/base/src/nsIMenuParent.h b/mozilla/layout/xul/base/src/nsIMenuParent.h index fa26c32af80..ecd8f356685 100644 --- a/mozilla/layout/xul/base/src/nsIMenuParent.h +++ b/mozilla/layout/xul/base/src/nsIMenuParent.h @@ -45,6 +45,7 @@ { 0xd407bf61, 0x3efa, 0x11d3, { 0x97, 0xfa, 0x0, 0x40, 0x5, 0x53, 0xee, 0xf0 } } class nsIMenuFrame; +class nsIDOMKeyEvent; class nsIMenuParent : public nsISupports { @@ -73,7 +74,7 @@ public: // Used to move up, down, left, and right in menus. NS_IMETHOD KeyboardNavigation(PRUint32 aDirection, PRBool& aHandledFlag) = 0; - NS_IMETHOD ShortcutNavigation(PRUint32 aLetter, PRBool& aHandledFlag) = 0; + NS_IMETHOD ShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, PRBool& aHandledFlag) = 0; // Called when the ESC key is held down to close levels of menus. NS_IMETHOD Escape(PRBool& aHandledFlag) = 0; // Called to execute a menu item. diff --git a/mozilla/layout/xul/base/src/nsMenuBarFrame.cpp b/mozilla/layout/xul/base/src/nsMenuBarFrame.cpp index b74e5b1f34b..e251fc7b2a3 100644 --- a/mozilla/layout/xul/base/src/nsMenuBarFrame.cpp +++ b/mozilla/layout/xul/base/src/nsMenuBarFrame.cpp @@ -246,8 +246,11 @@ static void GetInsertionPoint(nsIPresShell* aShell, nsIFrame* aFrame, nsIFrame* } nsIMenuFrame* -nsMenuBarFrame::FindMenuWithShortcut(PRUint32 aLetter) +nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent) { + PRUint32 charCode; + aKeyEvent->GetCharCode(&charCode); + // Enumerate over our list of frames. nsIFrame* immediateParent = nsnull; nsCOMPtr shell; @@ -270,7 +273,7 @@ nsMenuBarFrame::FindMenuWithShortcut(PRUint32 aLetter) current->GetAttr(kNameSpaceID_None, nsXULAtoms::accesskey, shortcutKey); if (!shortcutKey.IsEmpty()) { // We've got something. - PRUnichar letter = PRUnichar(aLetter); // throw away the high-zero-fill + PRUnichar letter = PRUnichar(charCode); // throw away the high-zero-fill if ( shortcutKey.Equals(Substring(&letter, &letter+1), nsCaseInsensitiveStringComparator()) ) { // We match! @@ -300,20 +303,20 @@ nsMenuBarFrame::FindMenuWithShortcut(PRUint32 aLetter) } NS_IMETHODIMP -nsMenuBarFrame::ShortcutNavigation(PRUint32 aLetter, PRBool& aHandledFlag) +nsMenuBarFrame::ShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, PRBool& aHandledFlag) { if (mCurrentMenu) { PRBool isOpen = PR_FALSE; mCurrentMenu->MenuIsOpen(isOpen); if (isOpen) { // No way this applies to us. Give it to our child. - mCurrentMenu->ShortcutNavigation(aLetter, aHandledFlag); + mCurrentMenu->ShortcutNavigation(aKeyEvent, aHandledFlag); return NS_OK; } } // This applies to us. Let's see if one of the shortcuts applies - nsIMenuFrame* result = FindMenuWithShortcut(aLetter); + nsIMenuFrame* result = FindMenuWithShortcut(aKeyEvent); if (result) { // We got one! aHandledFlag = PR_TRUE; diff --git a/mozilla/layout/xul/base/src/nsMenuBarFrame.h b/mozilla/layout/xul/base/src/nsMenuBarFrame.h index 584fa16aca4..097f29d8728 100644 --- a/mozilla/layout/xul/base/src/nsMenuBarFrame.h +++ b/mozilla/layout/xul/base/src/nsMenuBarFrame.h @@ -112,14 +112,14 @@ public: // Used to move up, down, left, and right in menus. NS_IMETHOD KeyboardNavigation(PRUint32 aDirection, PRBool& aHandledFlag); - NS_IMETHOD ShortcutNavigation(PRUint32 aLetter, PRBool& aHandledFlag); + NS_IMETHOD ShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, PRBool& aHandledFlag); // Called when the ESC key is held down to close levels of menus. NS_IMETHOD Escape(PRBool& aHandledFlag); // Called to execute a menu item. NS_IMETHOD Enter(); // Used to handle ALT+key combos - nsIMenuFrame* FindMenuWithShortcut(PRUint32 aLetter); + nsIMenuFrame* FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent); PRBool IsValidItem(nsIContent* aContent); PRBool IsDisabled(nsIContent* aContent); diff --git a/mozilla/layout/xul/base/src/nsMenuBarListener.cpp b/mozilla/layout/xul/base/src/nsMenuBarListener.cpp index d1464c3ef1d..42e25eda669 100644 --- a/mozilla/layout/xul/base/src/nsMenuBarListener.cpp +++ b/mozilla/layout/xul/base/src/nsMenuBarListener.cpp @@ -232,10 +232,8 @@ nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent) // 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. - keyEvent->GetCharCode(&theChar); - PRBool active = PR_FALSE; - mMenuBarFrame->ShortcutNavigation(theChar, active); + mMenuBarFrame->ShortcutNavigation(keyEvent, active); if (active) { if (nsevent) { diff --git a/mozilla/layout/xul/base/src/nsMenuFrame.cpp b/mozilla/layout/xul/base/src/nsMenuFrame.cpp index 6cfad8e35c1..3f4744733c2 100644 --- a/mozilla/layout/xul/base/src/nsMenuFrame.cpp +++ b/mozilla/layout/xul/base/src/nsMenuFrame.cpp @@ -1220,12 +1220,12 @@ nsMenuFrame::RePositionPopup(nsBoxLayoutState& aState) } NS_IMETHODIMP -nsMenuFrame::ShortcutNavigation(PRUint32 aLetter, PRBool& aHandledFlag) +nsMenuFrame::ShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, PRBool& aHandledFlag) { nsIFrame* frame = mPopupFrames.FirstChild(); if (frame) { nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame; - popup->ShortcutNavigation(aLetter, aHandledFlag); + popup->ShortcutNavigation(aKeyEvent, aHandledFlag); } return NS_OK; diff --git a/mozilla/layout/xul/base/src/nsMenuFrame.h b/mozilla/layout/xul/base/src/nsMenuFrame.h index f8d7433207e..7901698fdc8 100644 --- a/mozilla/layout/xul/base/src/nsMenuFrame.h +++ b/mozilla/layout/xul/base/src/nsMenuFrame.h @@ -155,7 +155,7 @@ public: NS_IMETHOD Escape(PRBool& aHandledFlag); NS_IMETHOD Enter(); - NS_IMETHOD ShortcutNavigation(PRUint32 aLetter, PRBool& aHandledFlag); + NS_IMETHOD ShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, PRBool& aHandledFlag); NS_IMETHOD KeyboardNavigation(PRUint32 aDirection, PRBool& aHandledFlag); NS_IMETHOD SetParent(const nsIFrame* aParent); diff --git a/mozilla/layout/xul/base/src/nsMenuListener.cpp b/mozilla/layout/xul/base/src/nsMenuListener.cpp index 637d6aae364..db4740d16eb 100644 --- a/mozilla/layout/xul/base/src/nsMenuListener.cpp +++ b/mozilla/layout/xul/base/src/nsMenuListener.cpp @@ -242,7 +242,7 @@ nsMenuListener::KeyPress(nsIDOMEvent* aKeyEvent) // A letter was pressed. We want to see if a shortcut gets matched. If // so, we'll know the menu got activated. keyEvent->GetCharCode(&theChar); - mMenuParent->ShortcutNavigation(theChar, handled); + mMenuParent->ShortcutNavigation(keyEvent, handled); } } diff --git a/mozilla/layout/xul/base/src/nsMenuPopupFrame.cpp b/mozilla/layout/xul/base/src/nsMenuPopupFrame.cpp index 9a34561fe6b..9c8d880db9a 100644 --- a/mozilla/layout/xul/base/src/nsMenuPopupFrame.cpp +++ b/mozilla/layout/xul/base/src/nsMenuPopupFrame.cpp @@ -1444,6 +1444,8 @@ NS_IMETHODIMP nsMenuPopupFrame::SetCurrentMenuItem(nsIMenuFrame* aMenuItem) NS_IMETHODIMP nsMenuPopupFrame::Escape(PRBool& aHandledFlag) { + mIncrementalString = NS_LITERAL_STRING(""); + if (!mCurrentMenu) return NS_OK; @@ -1466,6 +1468,8 @@ nsMenuPopupFrame::Escape(PRBool& aHandledFlag) NS_IMETHODIMP nsMenuPopupFrame::Enter() { + mIncrementalString = NS_LITERAL_STRING(""); + // Give it to the child. if (mCurrentMenu) mCurrentMenu->Enter(); @@ -1474,8 +1478,13 @@ nsMenuPopupFrame::Enter() } nsIMenuFrame* -nsMenuPopupFrame::FindMenuWithShortcut(PRUint32 aLetter) +nsMenuPopupFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, PRBool& doAction) { + PRUint32 charCode, keyCode; + aKeyEvent->GetCharCode(&charCode); + aKeyEvent->GetKeyCode(&keyCode); + + doAction = PR_FALSE; // Enumerate over our list of frames. nsIFrame* immediateParent = nsnull; @@ -1485,6 +1494,61 @@ nsMenuPopupFrame::FindMenuWithShortcut(PRUint32 aLetter) if (!immediateParent) immediateParent = this; + PRUint32 matchCount = 0, matchShortcutCount = 0; + PRBool foundActive = PR_FALSE; + PRBool isMenu = PR_FALSE; + PRBool isShortcut; + nsIMenuFrame* frameBefore = nsnull; + nsIMenuFrame* frameAfter = nsnull; + nsIMenuFrame* frameShortcut = nsnull; + + nsCOMPtr parentContent; + mContent->GetParent(*getter_AddRefs(parentContent)); + if (parentContent) { + nsCOMPtr tag; + parentContent->GetTag(*getter_AddRefs(tag)); + // toolbarbutton is for the "Bookmarks" button on the toolbar + if (tag == nsXULAtoms::menu || tag == nsXULAtoms::toolbarbutton) + isMenu = PR_TRUE; + } + + static DOMTimeStamp lastKeyTime = 0; + DOMTimeStamp keyTime; + aKeyEvent->GetTimeStamp(&keyTime); + + if (charCode == 0) { + if (keyCode == NS_VK_BACK) { + if (!isMenu && !mIncrementalString.IsEmpty()) { + mIncrementalString.SetLength(mIncrementalString.Length() - 1); + return nsnull; + } + else { +#ifdef XP_WIN + nsCOMPtr soundInterface = do_CreateInstance("@mozilla.org/sound;1"); + if (soundInterface) + soundInterface->Beep(); +#endif // #ifdef XP_WIN + } + } + return nsnull; + } + else { + nsAutoString pressKey(NS_STATIC_CAST(PRUnichar, charCode)); + // UpperCase to compare-no-case + ToUpperCase(pressKey); + if (isMenu || // Menu supports only first-letter navigation + keyTime - lastKeyTime > INC_TYP_INTERVAL) // Interval too long, treat as new typing + mIncrementalString = pressKey; + else { + if (mIncrementalString.Length() != 1 || + GetCharAt(mIncrementalString, 0) != GetCharAt(pressKey, 0)) + // If user typed the same key more than once, we should do a cycled one-key navigation + mIncrementalString.do_AppendFromElement(GetCharAt(pressKey, 0)); + } + } + + lastKeyTime = keyTime; + nsIFrame* currFrame; // NOTE: If you crashed here due to a bogus |immediateParent| it is // possible that the menu whose shortcut is being looked up has @@ -1493,73 +1557,119 @@ nsMenuPopupFrame::FindMenuWithShortcut(PRUint32 aLetter) // immediateParent->FirstChild(mPresContext, nsnull, &currFrame); + // We start searching from first child. This process is divided into two parts + // -- before current and after current -- by the current item while (currFrame) { nsCOMPtr current; currFrame->GetContent(getter_AddRefs(current)); // See if it's a menu item. if (IsValidItem(current)) { + nsAutoString activeKey, textKey; // Get the shortcut attribute. - nsAutoString shortcutKey; - current->GetAttr(kNameSpaceID_None, nsXULAtoms::accesskey, shortcutKey); - if (shortcutKey.Length() > 0) { - // We've got something. - char tempChar[2]; - tempChar[0] = aLetter; - tempChar[1] = 0; - - if (shortcutKey.EqualsIgnoreCase(tempChar)) { - // We match! - nsCOMPtr menuFrame = do_QueryInterface(currFrame); - if (menuFrame) - return menuFrame.get(); + current->GetAttr(kNameSpaceID_None, nsXULAtoms::accesskey, textKey); + if (textKey.IsEmpty()) { // No shortcut, try first letter + isShortcut = PR_FALSE; + current->GetAttr(kNameSpaceID_None, nsXULAtoms::label, textKey); + if (textKey.IsEmpty()) // No label, try another attribute (value) + current->GetAttr(kNameSpaceID_None, nsXULAtoms::value, textKey); + } + else + isShortcut = PR_TRUE; + + ToUpperCase(textKey); + if (Substring(textKey, 0, mIncrementalString.Length()) == mIncrementalString) { + // mIncrementalString is a prefix of textKey + nsCOMPtr menuFrame = do_QueryInterface(currFrame); + if (menuFrame) { + // There is one match + matchCount++; + if (isShortcut) { + // There is one shortcut-key match + matchShortcutCount++; + // Record the matched item. If there is only one matched shortcut item, do it + frameShortcut = menuFrame.get(); + } + if (!foundActive) { + // It's a first candidate item located before/on the current item + if (!frameBefore) + frameBefore = menuFrame.get(); + } + else { + // It's a first candidate item located after the current item + if (!frameAfter) + frameAfter = menuFrame.get(); + } + } + else return nsnull; + } + + // Get the active status + current->GetAttr(kNameSpaceID_None, nsXULAtoms::menuactive, activeKey); + if (activeKey == NS_LITERAL_STRING("true")) { + foundActive = PR_TRUE; + if (mIncrementalString.Length() > 1) { + // If there is more than one char typed, the current item has highest priority, + // otherwise the item next to current has highest priority + nsCOMPtr menuFrame = do_QueryInterface(currFrame); + if (menuFrame && menuFrame.get() == frameBefore) { + return frameBefore; + } } } } currFrame->GetNextSibling(&currFrame); } + doAction = (isMenu && (matchCount == 1 || matchShortcutCount == 1)); + + if (matchShortcutCount == 1) // We have one matched shortcut item + return frameShortcut; + if (frameAfter) // If we have matched item after the current, use it + return frameAfter; + else if (frameBefore) // If we haven't, use the item before the current + return frameBefore; + + // If we don't match anything, rollback the last typing + mIncrementalString.SetLength(mIncrementalString.Length() - 1); + // didn't find a matching menu item #ifdef XP_WIN - // behavior on Windows - this item is in a menu popup off of the - // menu bar, so beep and do nothing else - nsCOMPtr parentContent; - mContent->GetParent(*getter_AddRefs(parentContent)); - if (parentContent) { - nsCOMPtr tag; - parentContent->GetTag(*getter_AddRefs(tag)); - if (tag == nsXULAtoms::menu) { - nsCOMPtr soundInterface = do_CreateInstance("@mozilla.org/sound;1"); - if (soundInterface) - soundInterface->Beep(); - } - } + // behavior on Windows - this item is in a menu popup off of the + // menu bar, so beep and do nothing else + if (isMenu) { + nsCOMPtr soundInterface = do_CreateInstance("@mozilla.org/sound;1"); + if (soundInterface) + soundInterface->Beep(); + } #endif // #ifdef XP_WIN return nsnull; } NS_IMETHODIMP -nsMenuPopupFrame::ShortcutNavigation(PRUint32 aLetter, PRBool& aHandledFlag) +nsMenuPopupFrame::ShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, PRBool& aHandledFlag) { if (mCurrentMenu) { PRBool isOpen = PR_FALSE; mCurrentMenu->MenuIsOpen(isOpen); if (isOpen) { // No way this applies to us. Give it to our child. - mCurrentMenu->ShortcutNavigation(aLetter, aHandledFlag); + mCurrentMenu->ShortcutNavigation(aKeyEvent, aHandledFlag); return NS_OK; } } // This applies to us. Let's see if one of the shortcuts applies - nsIMenuFrame* result = FindMenuWithShortcut(aLetter); + PRBool action; + nsIMenuFrame* result = FindMenuWithShortcut(aKeyEvent, action); if (result) { // We got one! aHandledFlag = PR_TRUE; SetCurrentMenuItem(result); - result->Enter(); + if (action) + result->Enter(); } return NS_OK; @@ -1568,6 +1678,8 @@ nsMenuPopupFrame::ShortcutNavigation(PRUint32 aLetter, PRBool& aHandledFlag) NS_IMETHODIMP nsMenuPopupFrame::KeyboardNavigation(PRUint32 aDirection, PRBool& aHandledFlag) { + mIncrementalString = NS_LITERAL_STRING(""); + // This method only gets called if we're open. if (!mCurrentMenu && (aDirection == NS_VK_RIGHT || aDirection == NS_VK_LEFT)) { // We've been opened, but we haven't had anything selected. diff --git a/mozilla/layout/xul/base/src/nsMenuPopupFrame.h b/mozilla/layout/xul/base/src/nsMenuPopupFrame.h index 31d2b8c8290..70c0145065d 100644 --- a/mozilla/layout/xul/base/src/nsMenuPopupFrame.h +++ b/mozilla/layout/xul/base/src/nsMenuPopupFrame.h @@ -58,6 +58,9 @@ #include "nsITimer.h" #include "nsITimerCallback.h" +#define INC_TYP_INTERVAL 2500 // 2.5s. If the interval of two typings is shorter than this, + // treat as a continue typing + nsresult NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsIFrame** aResult) ; class nsIViewManager; @@ -143,12 +146,12 @@ public: nsIFrame* aFrame, PRInt32 aXPos, PRInt32 aYPos); NS_IMETHOD KeyboardNavigation(PRUint32 aDirection, PRBool& aHandledFlag); - NS_IMETHOD ShortcutNavigation(PRUint32 aLetter, PRBool& aHandledFlag); + NS_IMETHOD ShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, PRBool& aHandledFlag); NS_IMETHOD Escape(PRBool& aHandledFlag); NS_IMETHOD Enter(); - nsIMenuFrame* FindMenuWithShortcut(PRUint32 aLetter); + nsIMenuFrame* FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, PRBool& doAction); PRBool IsValidItem(nsIContent* aContent); PRBool IsDisabled(nsIContent* aContent); @@ -218,6 +221,8 @@ protected: PRPackedBool mShouldAutoPosition; // Should SyncViewWithFrame be allowed to auto position popup? PRPackedBool mShouldRollup; // Should this menupopup be allowed to dismiss automatically? + nsString mIncrementalString; // for incremental typing navigation + }; // class nsMenuPopupFrame #endif