diff --git a/content/base/src/nsGkAtomList.h b/content/base/src/nsGkAtomList.h index 9f0cd128fa5..b5a007b54af 100755 --- a/content/base/src/nsGkAtomList.h +++ b/content/base/src/nsGkAtomList.h @@ -488,6 +488,7 @@ GK_ATOM(menugenerated, "menugenerated") GK_ATOM(menuitem, "menuitem") GK_ATOM(menulist, "menulist") GK_ATOM(menupopup, "menupopup") +GK_ATOM(menutobedisplayed, "menutobedisplayed") GK_ATOM(message, "message") GK_ATOM(meta, "meta") GK_ATOM(method, "method") @@ -519,7 +520,6 @@ GK_ATOM(never, "never") GK_ATOM(_new, "new") GK_ATOM(nextBidi, "NextBidi") GK_ATOM(no, "no") -GK_ATOM(noautohide, "noautohide") GK_ATOM(nobr, "nobr") GK_ATOM(node, "node") GK_ATOM(nodeSet, "node-set") @@ -634,7 +634,6 @@ GK_ATOM(pageincrement, "pageincrement") GK_ATOM(pagex, "pagex") GK_ATOM(pagey, "pagey") GK_ATOM(palettename, "palettename") -GK_ATOM(panel, "panel") GK_ATOM(param, "param") GK_ATOM(parameter, "parameter") GK_ATOM(parent, "parent") @@ -1391,15 +1390,12 @@ GK_ATOM(legendFrame, "LegendFrame") GK_ATOM(letterFrame, "LetterFrame") GK_ATOM(lineFrame, "LineFrame") GK_ATOM(listControlFrame,"ListControlFrame") -GK_ATOM(menuBarFrame,"MenuBarFrame") -GK_ATOM(menuFrame,"MenuFrame") GK_ATOM(menuPopupFrame,"MenuPopupFrame") GK_ATOM(objectFrame, "ObjectFrame") GK_ATOM(pageFrame, "PageFrame") GK_ATOM(pageBreakFrame, "PageBreakFrame") GK_ATOM(pageContentFrame, "PageContentFrame") GK_ATOM(placeholderFrame, "PlaceholderFrame") -GK_ATOM(popupSetFrame, "PopupSetFrame") GK_ATOM(positionedInlineFrame, "PositionedInlineFrame") GK_ATOM(canvasFrame, "CanvasFrame") GK_ATOM(rootFrame, "RootFrame") diff --git a/content/xul/content/Makefile.in b/content/xul/content/Makefile.in index 4281407d982..deed0d222d3 100644 --- a/content/xul/content/Makefile.in +++ b/content/xul/content/Makefile.in @@ -43,7 +43,7 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk MODULE = xul -DIRS = src test +DIRS = public src test include $(topsrcdir)/config/rules.mk diff --git a/layout/xul/base/src/nsIMenuParent.h b/layout/xul/base/src/nsIMenuParent.h index 70741c243d1..991d2313bc1 100644 --- a/layout/xul/base/src/nsIMenuParent.h +++ b/layout/xul/base/src/nsIMenuParent.h @@ -39,53 +39,145 @@ #ifndef nsIMenuParent_h___ #define nsIMenuParent_h___ -class nsMenuFrame; + +// {33f700c8-976a-4cdb-8f6c-d9f4cfee8366} +#define NS_IMENUPARENT_IID \ +{ 0x33f700c8, 0x976a, 0x4cdb, { 0x8f, 0x6c, 0xd9, 0xf4, 0xcf, 0xee, 0x83, 0x66 } } + +class nsIMenuFrame; +class nsIDOMKeyEvent; /* - * nsIMenuParent is an interface implemented by nsMenuBarFrame and nsMenuPopupFrame - * as both serve as parent frames to nsMenuFrame. - * - * Don't implement this interface on other classes unless you also fix up references, - * as this interface is directly cast to and from nsMenuBarFrame and nsMenuPopupFrame. + * nsIMenuParent is implemented on frames and thus should not be + * refcounted. Eventually it should not inherit from nsISupports. */ -class nsIMenuParent { +/** + * nsNavigationDirection: an enum expressing navigation through the menus in + * terms which are independent of the directionality of the chrome. The + * terminology, derived from XSL-FO and CSS3 (e.g. + * http://www.w3.org/TR/css3-text/#TextLayout), is BASE (Before, After, Start, + * End), with the addition of First and Last (mapped to Home and End + * respectively). + * + * In languages such as English where the inline progression is left-to-right + * and the block progression is top-to-bottom (lr-tb), these terms will map out + * as in the following diagram + * + * --- inline progression ---> + * + * First | + * ... | + * Before | + * +--------+ block + * Start | | End progression + * +--------+ | + * After | + * ... | + * Last V + * + */ + +enum nsNavigationDirection { + eNavigationDirection_Last, + eNavigationDirection_First, + eNavigationDirection_Start, + eNavigationDirection_Before, + eNavigationDirection_End, + eNavigationDirection_After +}; + +#define NS_DIRECTION_IS_INLINE(dir) (dir == eNavigationDirection_Start || \ + dir == eNavigationDirection_End) +#define NS_DIRECTION_IS_BLOCK(dir) (dir == eNavigationDirection_Before || \ + dir == eNavigationDirection_After) +#define NS_DIRECTION_IS_BLOCK_TO_EDGE(dir) (dir == eNavigationDirection_First || \ + dir == eNavigationDirection_Last) + +/** + * DirectionFromKeyCode_lr_tb: an array that maps keycodes to values of + * nsNavigationDirection for left-to-right and top-to-bottom flow orientation + */ +static nsNavigationDirection DirectionFromKeyCode_lr_tb [6] = { + eNavigationDirection_Last, // NS_VK_END + eNavigationDirection_First, // NS_VK_HOME + eNavigationDirection_Start, // NS_VK_LEFT + eNavigationDirection_Before, // NS_VK_UP + eNavigationDirection_End, // NS_VK_RIGHT + eNavigationDirection_After // NS_VK_DOWN +}; + +/** + * DirectionFromKeyCode_rl_tb: an array that maps keycodes to values of + * nsNavigationDirection for right-to-left and top-to-bottom flow orientation + */ +static nsNavigationDirection DirectionFromKeyCode_rl_tb [6] = { + eNavigationDirection_Last, // NS_VK_END + eNavigationDirection_First, // NS_VK_HOME + eNavigationDirection_End, // NS_VK_LEFT + eNavigationDirection_Before, // NS_VK_UP + eNavigationDirection_Start, // NS_VK_RIGHT + eNavigationDirection_After // NS_VK_DOWN +}; + +#ifdef IBMBIDI +#define NS_DIRECTION_FROM_KEY_CODE(direction, keycode) \ + NS_ASSERTION(keycode >= NS_VK_END && keycode <= NS_VK_DOWN, \ + "Illegal key code"); \ + const nsStyleVisibility* vis = GetStyleVisibility(); \ + if (vis->mDirection == NS_STYLE_DIRECTION_RTL) \ + direction = DirectionFromKeyCode_rl_tb[keycode - NS_VK_END]; \ + else \ + direction = DirectionFromKeyCode_lr_tb[keycode - NS_VK_END]; +#else +#define NS_DIRECTION_FROM_KEY_CODE(direction, keycode) \ + direction = DirectionFromKeyCode_lr_tb[keycode - NS_VK_END]; +#endif + +class nsIMenuParent : public nsISupports { public: - // returns the menu frame of the currently active item within the menu - virtual nsMenuFrame *GetCurrentMenuItem() = 0; - // sets the currently active menu frame. - NS_IMETHOD SetCurrentMenuItem(nsMenuFrame* aMenuItem) = 0; - // indicate that the current menu frame is being destroyed, so clear the - // current menu item - virtual void CurrentMenuIsBeingDestroyed() = 0; - // deselects the current item and closes its popup if any, then selects the - // new item aMenuItem. For a menubar, if another menu is already open, the - // new menu aMenuItem is opened. In this case, if aSelectFirstItem is true, - // select the first item in it. For menupoups, the menu is not opened and - // the aSelectFirstItem argument is not used. - NS_IMETHOD ChangeMenuItem(nsMenuFrame* aMenuItem, PRBool aSelectFirstItem) = 0; + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMENUPARENT_IID) - // returns true if the menupopup is open. For menubars, returns false. - virtual PRBool IsOpen() = 0; - // returns true if the menubar is currently active. For menupopups, returns false. - virtual PRBool IsActive() = 0; - // returns true if this is a menubar. If false, it is a popup - virtual PRBool IsMenuBar() = 0; - // returns true if this is a menu, which has a tag of menupopup or popup. - // Otherwise, this returns false - virtual PRBool IsMenu() = 0; - // returns true if this is a context menu - virtual PRBool IsContextMenu() = 0; + virtual nsIMenuFrame *GetCurrentMenuItem() = 0; + NS_IMETHOD SetCurrentMenuItem(nsIMenuFrame* aMenuItem) = 0; + virtual nsIMenuFrame *GetNextMenuItem(nsIMenuFrame* aStart) = 0; + virtual nsIMenuFrame *GetPreviousMenuItem(nsIMenuFrame* aStart) = 0; - // indicate that the menubar should become active or inactive NS_IMETHOD SetActive(PRBool aActiveFlag) = 0; + NS_IMETHOD GetIsActive(PRBool& isActive) = 0; + NS_IMETHOD GetWidget(nsIWidget **aWidget) = 0; + + NS_IMETHOD IsMenuBar(PRBool& isMenuBar) = 0; + NS_IMETHOD ConsumeOutsideClicks(PRBool& aConsumeOutsideClicks) = 0; + NS_IMETHOD ClearRecentlyRolledUp() = 0; + NS_IMETHOD RecentlyRolledUp(nsIMenuFrame *aMenuFrame, PRBool *aJustRolledUp) = 0; - // notify that the menu has been closed and any active state should be - // cleared. This should return true if the menu should be deselected - // by the caller. - virtual PRBool MenuClosed() = 0; + NS_IMETHOD DismissChain() = 0; + NS_IMETHOD HideChain() = 0; + NS_IMETHOD KillPendingTimers() = 0; + NS_IMETHOD CancelPendingTimers() = 0; + + NS_IMETHOD AttachedDismissalListener() = 0; + + NS_IMETHOD InstallKeyboardNavigator() = 0; + NS_IMETHOD RemoveKeyboardNavigator() = 0; + + // Used to move up, down, left, and right in menus. + NS_IMETHOD KeyboardNavigation(PRUint32 aKeyCode, 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. + NS_IMETHOD Enter() = 0; + + NS_IMETHOD SetIsContextMenu(PRBool aIsContextMenu) = 0; + NS_IMETHOD GetIsContextMenu(PRBool& aIsContextMenu) = 0; + + NS_IMETHOD GetParentPopup(nsIMenuParent** aResult) = 0; }; +NS_DEFINE_STATIC_IID_ACCESSOR(nsIMenuParent, NS_IMENUPARENT_IID) + #endif diff --git a/layout/xul/base/src/nsIRootBox.h b/layout/xul/base/src/nsIRootBox.h index d6693b4ca23..2ab183d4862 100644 --- a/layout/xul/base/src/nsIRootBox.h +++ b/layout/xul/base/src/nsIRootBox.h @@ -41,14 +41,14 @@ #define nsIRootBox_h___ #include "nsISupports.h" -class nsPopupSetFrame; +class nsIFrame; class nsIContent; class nsIPresShell; -// {9777EC2A-9A46-4D01-8CEB-B9CEB2C262A5} +// {2256d568-3f5a-42ec-b932-3d0f78551a1a} #define NS_IROOTBOX_IID \ -{ 0x9777EC2A, 0x9A46, 0x4D01, \ - { 0x8C, 0xEB, 0xB9, 0xCE, 0xB2, 0xC2, 0x62, 0xA5 } } +{ 0x2256d568, 0x3f5a, 0x42ec, \ + { 0xb9, 0x32, 0x3d, 0x0f, 0x78, 0x55, 0x1a, 0x1a } } class nsIRootBox : public nsISupports { @@ -56,8 +56,8 @@ class nsIRootBox : public nsISupports { public: NS_DECLARE_STATIC_IID_ACCESSOR(NS_IROOTBOX_IID) - virtual nsPopupSetFrame* GetPopupSetFrame() = 0; - virtual void SetPopupSetFrame(nsPopupSetFrame* aPopupSet) = 0; + virtual nsIFrame* GetPopupSetFrame() = 0; + virtual void SetPopupSetFrame(nsIFrame* aPopupSet)=0; virtual nsIContent* GetDefaultTooltip() = 0; virtual void SetDefaultTooltip(nsIContent* aTooltip) = 0; diff --git a/layout/xul/base/src/nsMenuBarFrame.cpp b/layout/xul/base/src/nsMenuBarFrame.cpp index 63dd3d2eda7..0faa9d239a7 100644 --- a/layout/xul/base/src/nsMenuBarFrame.cpp +++ b/layout/xul/base/src/nsMenuBarFrame.cpp @@ -37,9 +37,11 @@ * * ***** END LICENSE BLOCK ***** */ +#include "nsMenuListener.h" #include "nsMenuBarFrame.h" #include "nsIServiceManager.h" #include "nsIContent.h" +#include "nsContentUtils.h" #include "prtypes.h" #include "nsIAtom.h" #include "nsPresContext.h" @@ -77,18 +79,51 @@ NS_NewMenuBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) return new (aPresShell) nsMenuBarFrame (aPresShell, aContext); } +NS_IMETHODIMP_(nsrefcnt) +nsMenuBarFrame::AddRef(void) +{ + return NS_OK; +} + +NS_IMETHODIMP_(nsrefcnt) +nsMenuBarFrame::Release(void) +{ + return NS_OK; +} + + +// +// QueryInterface +// +NS_INTERFACE_MAP_BEGIN(nsMenuBarFrame) + NS_INTERFACE_MAP_ENTRY(nsIMenuParent) +NS_INTERFACE_MAP_END_INHERITING(nsBoxFrame) + + // // nsMenuBarFrame cntr // nsMenuBarFrame::nsMenuBarFrame(nsIPresShell* aShell, nsStyleContext* aContext): nsBoxFrame(aShell, aContext), mMenuBarListener(nsnull), + mKeyboardNavigator(nsnull), mIsActive(PR_FALSE), mTarget(nsnull), mCaretWasVisible(PR_FALSE) { } // cntr +nsMenuBarFrame::~nsMenuBarFrame() +{ + /* The menubar can still be active at this point under unusual circumstances. + (say, while switching skins (which tears down all frames including + this one) after having made a menu selection (say, Edit->Preferences, + to get to the skin switching UI)). SetActive(PR_FALSE) releases + mKeyboardNavigator, which is by now pointing to a deleted frame. + */ + SetActive(PR_FALSE); +} + NS_IMETHODIMP nsMenuBarFrame::Init(nsIContent* aContent, nsIFrame* aParent, @@ -121,6 +156,20 @@ nsMenuBarFrame::Init(nsIContent* aContent, return rv; } +NS_IMETHODIMP +nsMenuBarFrame::IsOpen() +{ + PRBool isOpen = PR_FALSE; + if(mCurrentMenu) { + mCurrentMenu->MenuIsOpen(isOpen); + if (isOpen) { + return PR_TRUE; + } + } + return PR_FALSE; +} + + NS_IMETHODIMP nsMenuBarFrame::SetActive(PRBool aActiveFlag) { @@ -128,15 +177,6 @@ nsMenuBarFrame::SetActive(PRBool aActiveFlag) if (mIsActive == aActiveFlag) return NS_OK; - if (!aActiveFlag) { - // if there is a request to deactivate the menu bar, check to see whether - // there is a menu popup open for the menu bar. In this case, don't - // deactivate the menu bar. - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm && pm->IsPopupOpenForMenuParent(this)) - return NS_OK; - } - mIsActive = aActiveFlag; if (mIsActive) { InstallKeyboardNavigator(); @@ -199,22 +239,22 @@ nsMenuBarFrame::SetActive(PRBool aActiveFlag) NS_NAMED_LITERAL_STRING(active, "DOMMenuBarActive"); NS_NAMED_LITERAL_STRING(inactive, "DOMMenuBarInactive"); - FireDOMEvent(mIsActive ? active : inactive, mContent); + FireDOMEventSynch(mIsActive ? active : inactive); return NS_OK; } -nsMenuFrame* +void nsMenuBarFrame::ToggleMenuActiveState() { if (mIsActive) { // Deactivate the menu bar SetActive(PR_FALSE); if (mCurrentMenu) { - nsMenuFrame* closeframe = mCurrentMenu; - closeframe->SelectMenu(PR_FALSE); + // Deactivate the menu. + mCurrentMenu->OpenMenu(PR_FALSE); + mCurrentMenu->SelectMenu(PR_FALSE); mCurrentMenu = nsnull; - return closeframe; } } else { @@ -228,7 +268,7 @@ nsMenuBarFrame::ToggleMenuActiveState() // Set the active menu to be the top left item (e.g., the File menu). // We use an attribute called "menuactive" to track the current // active menu. - nsMenuFrame* firstFrame = nsXULPopupManager::GetNextMenuItem(this, nsnull, PR_FALSE); + nsIMenuFrame* firstFrame = GetNextMenuItem(nsnull); if (firstFrame) { firstFrame->SelectMenu(PR_TRUE); @@ -236,13 +276,10 @@ nsMenuBarFrame::ToggleMenuActiveState() mCurrentMenu = firstFrame; } } - - return nsnull; } -static void -GetInsertionPoint(nsIPresShell* aShell, nsIFrame* aFrame, nsIFrame* aChild, - nsIFrame** aResult) +static void GetInsertionPoint(nsIPresShell* aShell, nsIFrame* aFrame, nsIFrame* aChild, + nsIFrame** aResult) { nsIContent* child = nsnull; if (aChild) @@ -250,7 +287,7 @@ GetInsertionPoint(nsIPresShell* aShell, nsIFrame* aFrame, nsIFrame* aChild, aShell->FrameConstructor()->GetInsertionPoint(aFrame, child, aResult); } -nsMenuFrame* +nsIMenuFrame* nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent) { PRUint32 charCode; @@ -268,7 +305,7 @@ nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent) nsIContent* current = currFrame->GetContent(); // See if it's a menu item. - if (nsXULPopupManager::IsValidMenuItem(PresContext(), current, PR_FALSE)) { + if (IsValidItem(current)) { // Get the shortcut attribute. nsAutoString shortcutKey; current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, shortcutKey); @@ -278,8 +315,11 @@ nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent) if ( shortcutKey.Equals(Substring(&letter, &letter+1), nsCaseInsensitiveStringComparator()) ) { // We match! - return (currFrame->GetType() == nsGkAtoms::menuFrame) ? - NS_STATIC_CAST(nsMenuFrame *, currFrame) : nsnull; + nsIMenuFrame *menuFrame; + if (NS_FAILED(CallQueryInterface(currFrame, &menuFrame))) { + menuFrame = nsnull; + } + return menuFrame; } } } @@ -295,36 +335,263 @@ nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent) soundInterface->Beep(); } - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) - pm->Rollup(); - - SetCurrentMenuItem(nsnull); - SetActive(PR_FALSE); - + DismissChain(); #endif // #ifdef XP_WIN return nsnull; } -/* virtual */ nsMenuFrame* +NS_IMETHODIMP +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(aKeyEvent, aHandledFlag); + return NS_OK; + } + } + + // This applies to us. Let's see if one of the shortcuts applies + nsIMenuFrame* result = FindMenuWithShortcut(aKeyEvent); + if (result) { + // We got one! + nsWeakFrame weakFrame(this); + nsIFrame* frame = nsnull; + CallQueryInterface(result, &frame); + nsWeakFrame weakResult(frame); + aHandledFlag = PR_TRUE; + SetActive(PR_TRUE); + if (weakFrame.IsAlive()) { + SetCurrentMenuItem(result); + } + if (weakResult.IsAlive()) { + result->OpenMenu(PR_TRUE); + if (weakResult.IsAlive()) { + result->SelectFirstItem(); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMenuBarFrame::KeyboardNavigation(PRUint32 aKeyCode, PRBool& aHandledFlag) +{ + nsNavigationDirection theDirection; + NS_DIRECTION_FROM_KEY_CODE(theDirection, aKeyCode); + if (!mCurrentMenu) + return NS_OK; + + nsWeakFrame weakFrame(this); + PRBool isContainer = PR_FALSE; + PRBool isOpen = PR_FALSE; + mCurrentMenu->MenuIsContainer(isContainer); + mCurrentMenu->MenuIsOpen(isOpen); + + aHandledFlag = PR_FALSE; + + if (isOpen) { + // Let the child menu try to handle it. + mCurrentMenu->KeyboardNavigation(aKeyCode, aHandledFlag); + } + + if (aHandledFlag) + return NS_OK; + + if NS_DIRECTION_IS_INLINE(theDirection) { + + nsIMenuFrame* nextItem = (theDirection == eNavigationDirection_End) ? + GetNextMenuItem(mCurrentMenu) : + GetPreviousMenuItem(mCurrentMenu); + + nsIFrame* nextFrame = nsnull; + if (nextItem) { + CallQueryInterface(nextItem, &nextFrame); + } + nsWeakFrame weakNext(nextFrame); + SetCurrentMenuItem(nextItem); + if (weakNext.IsAlive()) { + PRBool nextIsOpen; + nextItem->MenuIsOpen(nextIsOpen); + if (nextIsOpen) { + // Select the first item. + nextItem->SelectFirstItem(); + } + } + } + else if NS_DIRECTION_IS_BLOCK(theDirection) { + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + nsIFrame* frame = nsnull; + CallQueryInterface(mCurrentMenu, &frame); + nsWeakFrame weakCurrentMenu(frame); + nsIMenuFrame* currentMenu = mCurrentMenu; + // Open the menu and select its first item. + currentMenu->OpenMenu(PR_TRUE); + if (weakCurrentMenu.IsAlive()) { + currentMenu->SelectFirstItem(); + } + } + + return NS_OK; +} + +/* virtual */ nsIMenuFrame* +nsMenuBarFrame::GetNextMenuItem(nsIMenuFrame* aStart) +{ + nsIFrame* immediateParent = nsnull; + GetInsertionPoint(PresContext()->PresShell(), this, nsnull, &immediateParent); + if (!immediateParent) + immediateParent = this; + + nsIFrame* currFrame = nsnull; + nsIFrame* startFrame = nsnull; + if (aStart) { + aStart->QueryInterface(NS_GET_IID(nsIFrame), (void**)&currFrame); + if (currFrame) { + startFrame = currFrame; + currFrame = currFrame->GetNextSibling(); + } + } + else + currFrame = immediateParent->GetFirstChild(nsnull); + + while (currFrame) { + // See if it's a menu item. + if (IsValidItem(currFrame->GetContent())) { + nsIMenuFrame *menuFrame; + if (NS_FAILED(CallQueryInterface(currFrame, &menuFrame))) + menuFrame = nsnull; + return menuFrame; + } + currFrame = currFrame->GetNextSibling(); + } + + currFrame = immediateParent->GetFirstChild(nsnull); + + // Still don't have anything. Try cycling from the beginning. + while (currFrame && currFrame != startFrame) { + // See if it's a menu item. + if (IsValidItem(currFrame->GetContent())) { + nsIMenuFrame *menuFrame; + if (NS_FAILED(CallQueryInterface(currFrame, &menuFrame))) + menuFrame = nsnull; + return menuFrame; + } + + currFrame = currFrame->GetNextSibling(); + } + + // No luck. Just return our start value. + return aStart; +} + +/* virtual */ nsIMenuFrame* +nsMenuBarFrame::GetPreviousMenuItem(nsIMenuFrame* aStart) +{ + nsIFrame* immediateParent = nsnull; + GetInsertionPoint(PresContext()->PresShell(), this, nsnull, &immediateParent); + if (!immediateParent) + immediateParent = this; + + nsFrameList frames(immediateParent->GetFirstChild(nsnull)); + + nsIFrame* currFrame = nsnull; + nsIFrame* startFrame = nsnull; + if (aStart) { + aStart->QueryInterface(NS_GET_IID(nsIFrame), (void**)&currFrame); + if (currFrame) { + startFrame = currFrame; + currFrame = frames.GetPrevSiblingFor(currFrame); + } + } + else currFrame = frames.LastChild(); + + while (currFrame) { + // See if it's a menu item. + if (IsValidItem(currFrame->GetContent())) { + nsIMenuFrame *menuFrame; + if (NS_FAILED(CallQueryInterface(currFrame, &menuFrame))) + menuFrame = nsnull; + return menuFrame; + } + currFrame = frames.GetPrevSiblingFor(currFrame); + } + + currFrame = frames.LastChild(); + + // Still don't have anything. Try cycling from the end. + while (currFrame && currFrame != startFrame) { + // See if it's a menu item. + if (IsValidItem(currFrame->GetContent())) { + nsIMenuFrame *menuFrame; + if (NS_FAILED(CallQueryInterface(currFrame, &menuFrame))) + menuFrame = nsnull; + return menuFrame; + } + + currFrame = frames.GetPrevSiblingFor(currFrame); + } + + // No luck. Just return our start value. + return aStart; +} + +/* virtual */ nsIMenuFrame* nsMenuBarFrame::GetCurrentMenuItem() { return mCurrentMenu; } -NS_IMETHODIMP -nsMenuBarFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem) + +NS_IMETHODIMP nsMenuBarFrame::SetCurrentMenuItem(nsIMenuFrame* aMenuItem) { if (mCurrentMenu == aMenuItem) return NS_OK; - nsWeakFrame weakFrame(this); - if (mCurrentMenu) - mCurrentMenu->SelectMenu(PR_FALSE); + PRBool wasOpen = PR_FALSE; + + // check if there's an open context menu, we ignore this + if (nsMenuFrame::GetContextMenu()) + return NS_OK; - if (aMenuItem) + nsWeakFrame weakFrame(this); + + // Unset the current child. + if (mCurrentMenu) { + nsIFrame* frame = nsnull; + CallQueryInterface(mCurrentMenu, &frame); + nsWeakFrame weakCurrentMenu(frame); + nsIMenuFrame* currentMenu = mCurrentMenu; + currentMenu->MenuIsOpen(wasOpen); + currentMenu->SelectMenu(PR_FALSE); + if (wasOpen && weakCurrentMenu.IsAlive()) { + currentMenu->OpenMenu(PR_FALSE); + } + } + + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + + + // Set the new child. + if (aMenuItem) { + nsIFrame* newMenu = nsnull; + CallQueryInterface(aMenuItem, &newMenu); + nsWeakFrame weakNewMenu(newMenu); aMenuItem->SelectMenu(PR_TRUE); + NS_ENSURE_TRUE(weakNewMenu.IsAlive(), NS_OK); + aMenuItem->MarkAsGenerated(); // Have the menu building. Get it ready to be shown. + NS_ENSURE_TRUE(weakNewMenu.IsAlive(), NS_OK); + + PRBool isDisabled = PR_FALSE; + aMenuItem->MenuIsDisabled(isDisabled); + if (wasOpen&&!isDisabled) + aMenuItem->OpenMenu(PR_TRUE); + ClearRecentlyRolledUp(); + } NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); mCurrentMenu = aMenuItem; @@ -332,103 +599,211 @@ nsMenuBarFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem) return NS_OK; } -void -nsMenuBarFrame::CurrentMenuIsBeingDestroyed() + +NS_IMETHODIMP +nsMenuBarFrame::Escape(PRBool& aHandledFlag) { - mCurrentMenu->SelectMenu(PR_FALSE); - mCurrentMenu = nsnull; + if (!mCurrentMenu) + return NS_OK; + + nsWeakFrame weakFrame(this); + // See if our menu is open. + PRBool isOpen = PR_FALSE; + mCurrentMenu->MenuIsOpen(isOpen); + if (isOpen) { + // Let the child menu handle this. + aHandledFlag = PR_FALSE; + mCurrentMenu->Escape(aHandledFlag); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + if (!aHandledFlag) { + // Close up this menu but keep our current menu item + // designation. + mCurrentMenu->OpenMenu(PR_FALSE); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + } + return NS_OK; + } + + // Clear our current menu item if we've got one. + SetCurrentMenuItem(nsnull); + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); + + SetActive(PR_FALSE); + + // Clear out our dismissal listener + nsMenuDismissalListener::Shutdown(); + return NS_OK; +} + +NS_IMETHODIMP +nsMenuBarFrame::Enter() +{ + if (!mCurrentMenu) + return NS_OK; + + ClearRecentlyRolledUp(); + + // See if our menu is open. + PRBool isOpen = PR_FALSE; + mCurrentMenu->MenuIsOpen(isOpen); + if (isOpen) { + // Let the child menu handle this. + mCurrentMenu->Enter(); + return NS_OK; + } + + // It's us. Open the current menu. + mCurrentMenu->OpenMenu(PR_TRUE); + mCurrentMenu->SelectFirstItem(); + + return NS_OK; } NS_IMETHODIMP -nsMenuBarFrame::ChangeMenuItem(nsMenuFrame* aMenuItem, - PRBool aSelectFirstItem) +nsMenuBarFrame::ClearRecentlyRolledUp() { - if (mCurrentMenu == aMenuItem) - return NS_OK; + // We're no longer in danger of popping down a menu from the same + // click on the menubar, which was supposed to toggle the menu closed + mRecentRollupMenu = nsnull; - // check if there's an open context menu, we ignore this - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm && pm->HasContextMenu(nsnull)) - return NS_OK; + return NS_OK; +} - // Unset the current child. - PRBool wasOpen = PR_FALSE; - if (mCurrentMenu) { - wasOpen = mCurrentMenu->IsOpen(); - mCurrentMenu->SelectMenu(PR_FALSE); - if (wasOpen) { - nsMenuPopupFrame* popupFrame = mCurrentMenu->GetPopup(); - if (popupFrame) - pm->HidePopup(popupFrame->GetContent(), PR_FALSE, PR_FALSE, PR_TRUE); - } +NS_IMETHODIMP +nsMenuBarFrame::RecentlyRolledUp(nsIMenuFrame *aMenuFrame, PRBool *aJustRolledUp) +{ + // Don't let a click reopen a menu that was just rolled up + // from the same click. Otherwise, the user can't click on + // a menubar item to toggle its submenu closed. + *aJustRolledUp = (mRecentRollupMenu == aMenuFrame); + + return NS_OK; +} + +NS_IMETHODIMP +nsMenuBarFrame::HideChain() +{ + // XXX hack if a context menu is active, do an Escape, which is + // currently bugged and destroys everything. We need to close + // the context menu first, otherwise SetCurrentMenuItem above + // would get blocked. + if (nsMenuFrame::GetContextMenu()) { + PRBool dummy; + mCurrentMenu->Escape(dummy); } - // set to null first in case the IsAlive check below returns false - mCurrentMenu = nsnull; + // Stop capturing rollups + // (must do this during Hide, which happens before the menu item is executed, + // since this reinstates normal event handling.) + nsMenuDismissalListener::Shutdown(); - // Set the new child. - if (aMenuItem) { - nsCOMPtr content = aMenuItem->GetContent(); - nsWeakFrame weakNewMenu(aMenuItem); - aMenuItem->SelectMenu(PR_TRUE); - NS_ENSURE_TRUE(weakNewMenu.IsAlive(), NS_OK); - mCurrentMenu = aMenuItem; - if (wasOpen && !aMenuItem->IsDisabled()) - pm->ShowMenu(content, aSelectFirstItem, PR_TRUE); + ClearRecentlyRolledUp(); + if (mCurrentMenu) { + mCurrentMenu->ActivateMenu(PR_FALSE); + mCurrentMenu->SelectMenu(PR_FALSE); + mRecentRollupMenu = mCurrentMenu; + } + + if (mIsActive) { + ToggleMenuActiveState(); } return NS_OK; } -nsMenuFrame* -nsMenuBarFrame::Enter() +NS_IMETHODIMP +nsMenuBarFrame::DismissChain() { - if (!mCurrentMenu) - return nsnull; - - if (mCurrentMenu->IsOpen()) - return mCurrentMenu->Enter(); - - return mCurrentMenu; -} - -PRBool -nsMenuBarFrame::MenuClosed() -{ - SetActive(PR_FALSE); - if (!mIsActive && mCurrentMenu) { - mCurrentMenu->SelectMenu(PR_FALSE); - mCurrentMenu = nsnull; - return PR_TRUE; + // Stop capturing rollups + nsMenuDismissalListener::Shutdown(); + nsWeakFrame weakFrame(this); + SetCurrentMenuItem(nsnull); + if (weakFrame.IsAlive()) { + SetActive(PR_FALSE); } - return PR_FALSE; + return NS_OK; } -void + +NS_IMETHODIMP +nsMenuBarFrame::KillPendingTimers ( ) +{ + return NS_OK; + +} // KillPendingTimers + + +NS_IMETHODIMP +nsMenuBarFrame::GetWidget(nsIWidget **aWidget) +{ + // (pinkerton/hyatt) + // since the menubar is a menuparent but not a menuItem, the win32 rollup code + // would erroneously add the entire top-level window to the widget list built up for + // determining if a click is in a submenu's menu chain. To get around this, we just + // don't let the menubar have a widget. Things seem to work because the dismissal + // listener is registered when a new menu is popped up, which is the only real reason + // why we need a widget at all. + *aWidget = nsnull; + return NS_OK; +} + +NS_IMETHODIMP nsMenuBarFrame::InstallKeyboardNavigator() { - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) - pm->SetActiveMenuBar(this, PR_TRUE); + if (mKeyboardNavigator) + return NS_OK; + + mKeyboardNavigator = new nsMenuListener(this); + NS_IF_ADDREF(mKeyboardNavigator); + + mTarget->AddEventListener(NS_LITERAL_STRING("keypress"), (nsIDOMKeyListener*)mKeyboardNavigator, PR_TRUE); + mTarget->AddEventListener(NS_LITERAL_STRING("keydown"), (nsIDOMKeyListener*)mKeyboardNavigator, PR_TRUE); + mTarget->AddEventListener(NS_LITERAL_STRING("keyup"), (nsIDOMKeyListener*)mKeyboardNavigator, PR_TRUE); + + nsContentUtils::NotifyInstalledMenuKeyboardListener(PR_TRUE); + + return NS_OK; } -void +NS_IMETHODIMP nsMenuBarFrame::RemoveKeyboardNavigator() { - if (!mIsActive) { - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) - pm->SetActiveMenuBar(this, PR_FALSE); - } + if (!mKeyboardNavigator || mIsActive) + return NS_OK; + + mTarget->RemoveEventListener(NS_LITERAL_STRING("keypress"), (nsIDOMKeyListener*)mKeyboardNavigator, PR_TRUE); + mTarget->RemoveEventListener(NS_LITERAL_STRING("keydown"), (nsIDOMKeyListener*)mKeyboardNavigator, PR_TRUE); + mTarget->RemoveEventListener(NS_LITERAL_STRING("keyup"), (nsIDOMKeyListener*)mKeyboardNavigator, PR_TRUE); + + NS_IF_RELEASE(mKeyboardNavigator); + + nsContentUtils::NotifyInstalledMenuKeyboardListener(PR_FALSE); + + return NS_OK; +} + +// helpers /////////////////////////////////////////////////////////// + +PRBool +nsMenuBarFrame::IsValidItem(nsIContent* aContent) +{ + nsIAtom *tag = aContent->Tag(); + + return ((tag == nsGkAtoms::menu || + tag == nsGkAtoms::menuitem) && + !IsDisabled(aContent)); +} + +PRBool +nsMenuBarFrame::IsDisabled(nsIContent* aContent) +{ + return aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, + nsGkAtoms::_true, eCaseMatters); } void nsMenuBarFrame::Destroy() { - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) - pm->SetActiveMenuBar(this, PR_FALSE); - mTarget->RemoveEventListener(NS_LITERAL_STRING("keypress"), (nsIDOMKeyListener*)mMenuBarListener, PR_FALSE); mTarget->RemoveEventListener(NS_LITERAL_STRING("keydown"), (nsIDOMKeyListener*)mMenuBarListener, PR_FALSE); mTarget->RemoveEventListener(NS_LITERAL_STRING("keyup"), (nsIDOMKeyListener*)mMenuBarListener, PR_FALSE); @@ -440,3 +815,4 @@ nsMenuBarFrame::Destroy() nsBoxFrame::Destroy(); } + diff --git a/layout/xul/base/src/nsXULTooltipListener.cpp b/layout/xul/base/src/nsXULTooltipListener.cpp index 50c0f3495d9..411525e2477 100644 --- a/layout/xul/base/src/nsXULTooltipListener.cpp +++ b/layout/xul/base/src/nsXULTooltipListener.cpp @@ -46,6 +46,7 @@ #include "nsGkAtoms.h" #include "nsIPresShell.h" #include "nsIFrame.h" +#include "nsIMenuFrame.h" #include "nsIPopupBoxObject.h" #include "nsIServiceManager.h" #ifdef MOZ_XUL @@ -58,7 +59,6 @@ #include "nsIScriptContext.h" #include "nsPIDOMWindow.h" #include "nsContentUtils.h" -#include "nsXULPopupManager.h" #include "nsIRootBox.h" nsXULTooltipListener* nsXULTooltipListener::mInstance = nsnull; @@ -209,8 +209,8 @@ nsXULTooltipListener::MouseMove(nsIDOMEvent* aMouseEvent) // mouse has really moved before proceeding. nsCOMPtr mouseEvent(do_QueryInterface(aMouseEvent)); PRInt32 newMouseX, newMouseY; - mouseEvent->GetScreenX(&newMouseX); - mouseEvent->GetScreenY(&newMouseY); + mouseEvent->GetClientX(&newMouseX); + mouseEvent->GetClientY(&newMouseY); if (mMouseClientX == newMouseX && mMouseClientY == newMouseY) return NS_OK; mMouseClientX = newMouseX; @@ -413,8 +413,12 @@ nsXULTooltipListener::ShowTooltip() // at this point, |mCurrentTooltip| holds the content node of // the tooltip. If there is an attribute on the popup telling us // not to create the auto-hide timer, don't. - if (mCurrentTooltip->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautohide, - nsGkAtoms::_true, eCaseMatters)) + nsCOMPtr tooltipEl(do_QueryInterface(mCurrentTooltip)); + if (!tooltipEl) + return NS_ERROR_FAILURE; + nsAutoString noAutoHide; + tooltipEl->GetAttribute(NS_LITERAL_STRING("noautohide"), noAutoHide); + if (!noAutoHide.EqualsLiteral("true")) CreateAutoHideTimer(); // listen for popuphidden on the tooltip node, so that we can @@ -485,38 +489,66 @@ nsXULTooltipListener::LaunchTooltip() if (!mCurrentTooltip) return; -#ifdef MOZ_XUL - if (mIsSourceTree && mNeedTitletip) { - nsCOMPtr obx; - GetSourceTreeBoxObject(getter_AddRefs(obx)); + nsCOMPtr popupBox; + nsCOMPtr xulTooltipEl(do_QueryInterface(mCurrentTooltip)); + if (!xulTooltipEl) { + NS_ERROR("tooltip isn't a XUL element!"); + return; + } - SetTitletipLabel(obx, mCurrentTooltip, mLastTreeRow, mLastTreeCol); + xulTooltipEl->GetBoxObject(getter_AddRefs(popupBox)); + nsCOMPtr popupBoxObject(do_QueryInterface(popupBox)); + if (popupBoxObject) { + PRInt32 x = mMouseClientX; + PRInt32 y = mMouseClientY; +#ifdef MOZ_XUL + if (mIsSourceTree && mNeedTitletip) { + nsCOMPtr obx; + GetSourceTreeBoxObject(getter_AddRefs(obx)); +#ifdef DEBUG_crap + GetTreeCellCoords(obx, mSourceNode, + mLastTreeRow, mLastTreeCol, &x, &y); +#endif + + SetTitletipLabel(obx, mCurrentTooltip, mLastTreeRow, mLastTreeCol); + if (!mCurrentTooltip) { + // Because of mutation events, mCurrentTooltip can be null. + return; + } + mCurrentTooltip->SetAttr(kNameSpaceID_None, nsGkAtoms::titletip, + NS_LITERAL_STRING("true"), PR_TRUE); + } else { + mCurrentTooltip->UnsetAttr(kNameSpaceID_None, nsGkAtoms::titletip, + PR_TRUE); + } if (!mCurrentTooltip) { // Because of mutation events, mCurrentTooltip can be null. return; } - mCurrentTooltip->SetAttr(nsnull, nsGkAtoms::titletip, NS_LITERAL_STRING("true"), PR_TRUE); - } else { - mCurrentTooltip->UnsetAttr(nsnull, nsGkAtoms::titletip, PR_TRUE); - } - if (!mCurrentTooltip) { - // Because of mutation events, mCurrentTooltip can be null. - return; - } #endif - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) - pm->ShowPopupAtScreen(mCurrentTooltip, mMouseClientX, mMouseClientY, PR_FALSE); + nsCOMPtr targetEl(do_QueryInterface(mSourceNode)); + popupBoxObject->ShowPopup(targetEl, xulTooltipEl, x, y, + NS_LITERAL_STRING("tooltip").get(), + NS_LITERAL_STRING("none").get(), + NS_LITERAL_STRING("topleft").get()); + } + + return; } nsresult nsXULTooltipListener::HideTooltip() { if (mCurrentTooltip) { - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) - pm->HidePopup(mCurrentTooltip, PR_FALSE, PR_FALSE, PR_FALSE); + // hide the popup through its box object + nsCOMPtr tooltipEl(do_QueryInterface(mCurrentTooltip)); + nsCOMPtr boxObject; + if (tooltipEl) + tooltipEl->GetBoxObject(getter_AddRefs(boxObject)); + nsCOMPtr popupObject(do_QueryInterface(boxObject)); + if (popupObject) + popupObject->HidePopup(); } DestroyTooltip(); @@ -637,9 +669,10 @@ nsXULTooltipListener::GetTooltipFor(nsIContent* aTarget, nsIContent** aTooltip) nsIDocument* doc = parent->GetCurrentDoc(); nsIPresShell* presShell = doc ? doc->GetPrimaryShell() : nsnull; nsIFrame* frame = presShell ? presShell->GetPrimaryFrameFor(parent) : nsnull; - if (frame && frame->GetType() == nsGkAtoms::menuFrame) { - NS_WARNING("Menu cannot be used as a tooltip"); - return NS_ERROR_FAILURE; + if (frame) { + nsIMenuFrame* menu = nsnull; + CallQueryInterface(frame, &menu); + NS_ENSURE_FALSE(menu, NS_ERROR_FAILURE); } } @@ -708,7 +741,7 @@ nsXULTooltipListener::CreateAutoHideTimer() mAutoHideTimer->Cancel(); mAutoHideTimer = nsnull; } - + mAutoHideTimer = do_CreateInstance("@mozilla.org/timer;1"); if ( mAutoHideTimer ) mAutoHideTimer->InitWithFuncCallback(sAutoHideCallback, this, kTooltipAutoHideTime,