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
This commit is contained in:
pete.zha%sun.com
2002-04-24 07:15:35 +00:00
parent 0087cabe3e
commit 3675c24e79
10 changed files with 169 additions and 49 deletions

View File

@@ -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;

View File

@@ -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.

View File

@@ -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<nsIPresShell> 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;

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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<nsIContent> parentContent;
mContent->GetParent(*getter_AddRefs(parentContent));
if (parentContent) {
nsCOMPtr<nsIAtom> 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<nsISound> 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)
// <http://bugzilla.mozilla.org/show_bug.cgi?id=126675#c32>
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<nsIContent> 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<nsIMenuFrame> 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<nsIMenuFrame> 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<nsIMenuFrame> 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<nsIContent> parentContent;
mContent->GetParent(*getter_AddRefs(parentContent));
if (parentContent) {
nsCOMPtr<nsIAtom> tag;
parentContent->GetTag(*getter_AddRefs(tag));
if (tag == nsXULAtoms::menu) {
nsCOMPtr<nsISound> 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<nsISound> 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.

View File

@@ -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