michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsMenuBarFrame.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsCSSRendering.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsMenuFrame.h" michael@0: #include "nsMenuPopupFrame.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsCSSFrameConstructor.h" michael@0: #ifdef XP_WIN michael@0: #include "nsISound.h" michael@0: #include "nsWidgetsCID.h" michael@0: #endif michael@0: #include "nsContentUtils.h" michael@0: #include "nsUTF8Utils.h" michael@0: #include "mozilla/TextEvents.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: // michael@0: // NS_NewMenuBarFrame michael@0: // michael@0: // Wrapper for creating a new menu Bar container michael@0: // michael@0: nsIFrame* michael@0: NS_NewMenuBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsMenuBarFrame (aPresShell, aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsMenuBarFrame) michael@0: michael@0: NS_QUERYFRAME_HEAD(nsMenuBarFrame) michael@0: NS_QUERYFRAME_ENTRY(nsMenuBarFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) michael@0: michael@0: // michael@0: // nsMenuBarFrame cntr michael@0: // michael@0: nsMenuBarFrame::nsMenuBarFrame(nsIPresShell* aShell, nsStyleContext* aContext): michael@0: nsBoxFrame(aShell, aContext), michael@0: mMenuBarListener(nullptr), michael@0: mStayActive(false), michael@0: mIsActive(false), michael@0: mCurrentMenu(nullptr), michael@0: mTarget(nullptr) michael@0: { michael@0: } // cntr michael@0: michael@0: void michael@0: nsMenuBarFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: nsBoxFrame::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: // Create the menu bar listener. michael@0: mMenuBarListener = new nsMenuBarListener(this); michael@0: NS_ADDREF(mMenuBarListener); michael@0: michael@0: // Hook up the menu bar as a key listener on the whole document. It will see every michael@0: // key press that occurs, but after everyone else does. michael@0: mTarget = aContent->GetDocument(); michael@0: michael@0: // Also hook up the listener to the window listening for focus events. This is so we can keep proper michael@0: // state as the user alt-tabs through processes. michael@0: michael@0: mTarget->AddEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false); michael@0: mTarget->AddEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false); michael@0: mTarget->AddEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false); michael@0: michael@0: // mousedown event should be handled in all phase michael@0: mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true); michael@0: mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false); michael@0: mTarget->AddEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMenuBarFrame::SetActive(bool aActiveFlag) michael@0: { michael@0: // If the activity is not changed, there is nothing to do. michael@0: if (mIsActive == aActiveFlag) michael@0: return NS_OK; michael@0: michael@0: if (!aActiveFlag) { michael@0: // Don't deactivate when switching between menus on the menubar. michael@0: if (mStayActive) michael@0: return NS_OK; michael@0: michael@0: // if there is a request to deactivate the menu bar, check to see whether michael@0: // there is a menu popup open for the menu bar. In this case, don't michael@0: // deactivate the menu bar. michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm && pm->IsPopupOpenForMenuParent(this)) michael@0: return NS_OK; michael@0: } michael@0: michael@0: mIsActive = aActiveFlag; michael@0: if (mIsActive) { michael@0: InstallKeyboardNavigator(); michael@0: } michael@0: else { michael@0: mActiveByKeyboard = false; michael@0: RemoveKeyboardNavigator(); michael@0: } michael@0: michael@0: NS_NAMED_LITERAL_STRING(active, "DOMMenuBarActive"); michael@0: NS_NAMED_LITERAL_STRING(inactive, "DOMMenuBarInactive"); michael@0: michael@0: FireDOMEvent(mIsActive ? active : inactive, mContent); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsMenuFrame* michael@0: nsMenuBarFrame::ToggleMenuActiveState() michael@0: { michael@0: if (mIsActive) { michael@0: // Deactivate the menu bar michael@0: SetActive(false); michael@0: if (mCurrentMenu) { michael@0: nsMenuFrame* closeframe = mCurrentMenu; michael@0: closeframe->SelectMenu(false); michael@0: mCurrentMenu = nullptr; michael@0: return closeframe; michael@0: } michael@0: } michael@0: else { michael@0: // if the menu bar is already selected (eg. mouseover), deselect it michael@0: if (mCurrentMenu) michael@0: mCurrentMenu->SelectMenu(false); michael@0: michael@0: // Set the active menu to be the top left item (e.g., the File menu). michael@0: // We use an attribute called "menuactive" to track the current michael@0: // active menu. michael@0: nsMenuFrame* firstFrame = nsXULPopupManager::GetNextMenuItem(this, nullptr, false); michael@0: if (firstFrame) { michael@0: // Activate the menu bar michael@0: SetActive(true); michael@0: firstFrame->SelectMenu(true); michael@0: michael@0: // Track this item for keyboard navigation. michael@0: mCurrentMenu = firstFrame; michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: static void michael@0: GetInsertionPoint(nsIPresShell* aShell, nsIFrame* aFrame, nsIFrame* aChild, michael@0: nsIFrame** aResult) michael@0: { michael@0: nsIContent* child = nullptr; michael@0: if (aChild) michael@0: child = aChild->GetContent(); michael@0: *aResult = aShell->FrameConstructor()-> michael@0: GetInsertionPoint(aFrame->GetContent(), child); michael@0: } michael@0: michael@0: nsMenuFrame* michael@0: nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent) michael@0: { michael@0: uint32_t charCode; michael@0: aKeyEvent->GetCharCode(&charCode); michael@0: michael@0: nsAutoTArray accessKeys; michael@0: WidgetKeyboardEvent* nativeKeyEvent = michael@0: aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent(); michael@0: if (nativeKeyEvent) michael@0: nsContentUtils::GetAccessKeyCandidates(nativeKeyEvent, accessKeys); michael@0: if (accessKeys.IsEmpty() && charCode) michael@0: accessKeys.AppendElement(charCode); michael@0: michael@0: if (accessKeys.IsEmpty()) michael@0: return nullptr; // no character was pressed so just return michael@0: michael@0: // Enumerate over our list of frames. michael@0: nsIFrame* immediateParent = nullptr; michael@0: GetInsertionPoint(PresContext()->PresShell(), this, nullptr, &immediateParent); michael@0: if (!immediateParent) michael@0: immediateParent = this; michael@0: michael@0: // Find a most preferred accesskey which should be returned. michael@0: nsIFrame* foundMenu = nullptr; michael@0: uint32_t foundIndex = accessKeys.NoIndex; michael@0: nsIFrame* currFrame = immediateParent->GetFirstPrincipalChild(); michael@0: michael@0: while (currFrame) { michael@0: nsIContent* current = currFrame->GetContent(); michael@0: michael@0: // See if it's a menu item. michael@0: if (nsXULPopupManager::IsValidMenuItem(PresContext(), current, false)) { michael@0: // Get the shortcut attribute. michael@0: nsAutoString shortcutKey; michael@0: current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, shortcutKey); michael@0: if (!shortcutKey.IsEmpty()) { michael@0: ToLowerCase(shortcutKey); michael@0: const char16_t* start = shortcutKey.BeginReading(); michael@0: const char16_t* end = shortcutKey.EndReading(); michael@0: uint32_t ch = UTF16CharEnumerator::NextChar(&start, end); michael@0: uint32_t index = accessKeys.IndexOf(ch); michael@0: if (index != accessKeys.NoIndex && michael@0: (foundIndex == accessKeys.NoIndex || index < foundIndex)) { michael@0: foundMenu = currFrame; michael@0: foundIndex = index; michael@0: } michael@0: } michael@0: } michael@0: currFrame = currFrame->GetNextSibling(); michael@0: } michael@0: if (foundMenu) { michael@0: return do_QueryFrame(foundMenu); michael@0: } michael@0: michael@0: // didn't find a matching menu item michael@0: #ifdef XP_WIN michael@0: // behavior on Windows - this item is on the menu bar, beep and deactivate the menu bar michael@0: if (mIsActive) { michael@0: nsCOMPtr soundInterface = do_CreateInstance("@mozilla.org/sound;1"); michael@0: if (soundInterface) michael@0: soundInterface->Beep(); michael@0: } michael@0: michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm) { michael@0: nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny); michael@0: if (popup) michael@0: pm->HidePopup(popup->GetContent(), true, true, true, false); michael@0: } michael@0: michael@0: SetCurrentMenuItem(nullptr); michael@0: SetActive(false); michael@0: michael@0: #endif // #ifdef XP_WIN michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: /* virtual */ nsMenuFrame* michael@0: nsMenuBarFrame::GetCurrentMenuItem() michael@0: { michael@0: return mCurrentMenu; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMenuBarFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem) michael@0: { michael@0: if (mCurrentMenu == aMenuItem) michael@0: return NS_OK; michael@0: michael@0: if (mCurrentMenu) michael@0: mCurrentMenu->SelectMenu(false); michael@0: michael@0: if (aMenuItem) michael@0: aMenuItem->SelectMenu(true); michael@0: michael@0: mCurrentMenu = aMenuItem; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsMenuBarFrame::CurrentMenuIsBeingDestroyed() michael@0: { michael@0: mCurrentMenu->SelectMenu(false); michael@0: mCurrentMenu = nullptr; michael@0: } michael@0: michael@0: class nsMenuBarSwitchMenu : public nsRunnable michael@0: { michael@0: public: michael@0: nsMenuBarSwitchMenu(nsIContent* aMenuBar, michael@0: nsIContent *aOldMenu, michael@0: nsIContent *aNewMenu, michael@0: bool aSelectFirstItem) michael@0: : mMenuBar(aMenuBar), mOldMenu(aOldMenu), mNewMenu(aNewMenu), michael@0: mSelectFirstItem(aSelectFirstItem) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE michael@0: { michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (!pm) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: // if switching from one menu to another, set a flag so that the call to michael@0: // HidePopup doesn't deactivate the menubar when the first menu closes. michael@0: nsMenuBarFrame* menubar = nullptr; michael@0: if (mOldMenu && mNewMenu) { michael@0: menubar = do_QueryFrame(mMenuBar->GetPrimaryFrame()); michael@0: if (menubar) michael@0: menubar->SetStayActive(true); michael@0: } michael@0: michael@0: if (mOldMenu) { michael@0: nsWeakFrame weakMenuBar(menubar); michael@0: pm->HidePopup(mOldMenu, false, false, false, false); michael@0: // clear the flag again michael@0: if (mNewMenu && weakMenuBar.IsAlive()) michael@0: menubar->SetStayActive(false); michael@0: } michael@0: michael@0: if (mNewMenu) michael@0: pm->ShowMenu(mNewMenu, mSelectFirstItem, false); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mMenuBar; michael@0: nsCOMPtr mOldMenu; michael@0: nsCOMPtr mNewMenu; michael@0: bool mSelectFirstItem; michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: nsMenuBarFrame::ChangeMenuItem(nsMenuFrame* aMenuItem, michael@0: bool aSelectFirstItem) michael@0: { michael@0: if (mCurrentMenu == aMenuItem) michael@0: return NS_OK; michael@0: michael@0: // check if there's an open context menu, we ignore this michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm && pm->HasContextMenu(nullptr)) michael@0: return NS_OK; michael@0: michael@0: nsIContent* aOldMenu = nullptr, *aNewMenu = nullptr; michael@0: michael@0: // Unset the current child. michael@0: bool wasOpen = false; michael@0: if (mCurrentMenu) { michael@0: wasOpen = mCurrentMenu->IsOpen(); michael@0: mCurrentMenu->SelectMenu(false); michael@0: if (wasOpen) { michael@0: nsMenuPopupFrame* popupFrame = mCurrentMenu->GetPopup(); michael@0: if (popupFrame) michael@0: aOldMenu = popupFrame->GetContent(); michael@0: } michael@0: } michael@0: michael@0: // set to null first in case the IsAlive check below returns false michael@0: mCurrentMenu = nullptr; michael@0: michael@0: // Set the new child. michael@0: if (aMenuItem) { michael@0: nsCOMPtr content = aMenuItem->GetContent(); michael@0: aMenuItem->SelectMenu(true); michael@0: mCurrentMenu = aMenuItem; michael@0: if (wasOpen && !aMenuItem->IsDisabled()) michael@0: aNewMenu = content; michael@0: } michael@0: michael@0: // use an event so that hiding and showing can be done synchronously, which michael@0: // avoids flickering michael@0: nsCOMPtr event = michael@0: new nsMenuBarSwitchMenu(GetContent(), aOldMenu, aNewMenu, aSelectFirstItem); michael@0: return NS_DispatchToCurrentThread(event); michael@0: } michael@0: michael@0: nsMenuFrame* michael@0: nsMenuBarFrame::Enter(WidgetGUIEvent* aEvent) michael@0: { michael@0: if (!mCurrentMenu) michael@0: return nullptr; michael@0: michael@0: if (mCurrentMenu->IsOpen()) michael@0: return mCurrentMenu->Enter(aEvent); michael@0: michael@0: return mCurrentMenu; michael@0: } michael@0: michael@0: bool michael@0: nsMenuBarFrame::MenuClosed() michael@0: { michael@0: SetActive(false); michael@0: if (!mIsActive && mCurrentMenu) { michael@0: mCurrentMenu->SelectMenu(false); michael@0: mCurrentMenu = nullptr; michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsMenuBarFrame::InstallKeyboardNavigator() michael@0: { michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm) michael@0: pm->SetActiveMenuBar(this, true); michael@0: } michael@0: michael@0: void michael@0: nsMenuBarFrame::RemoveKeyboardNavigator() michael@0: { michael@0: if (!mIsActive) { michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm) michael@0: pm->SetActiveMenuBar(this, false); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMenuBarFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); michael@0: if (pm) michael@0: pm->SetActiveMenuBar(this, false); michael@0: michael@0: mTarget->RemoveEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false); michael@0: mTarget->RemoveEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false); michael@0: mTarget->RemoveEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false); michael@0: michael@0: mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true); michael@0: mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false); michael@0: mTarget->RemoveEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true); michael@0: michael@0: NS_IF_RELEASE(mMenuBarListener); michael@0: michael@0: nsBoxFrame::DestroyFrom(aDestructRoot); michael@0: }