diff options
Diffstat (limited to '')
-rw-r--r-- | layout/xul/nsMenuBarFrame.cpp | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/layout/xul/nsMenuBarFrame.cpp b/layout/xul/nsMenuBarFrame.cpp new file mode 100644 index 0000000000..c116bebfba --- /dev/null +++ b/layout/xul/nsMenuBarFrame.cpp @@ -0,0 +1,355 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMenuBarFrame.h" +#include "nsIContent.h" +#include "nsAtom.h" +#include "nsPresContext.h" +#include "nsCSSRendering.h" +#include "nsNameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsMenuFrame.h" +#include "nsMenuPopupFrame.h" +#include "nsUnicharUtils.h" +#include "nsPIDOMWindow.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsCSSFrameConstructor.h" +#ifdef XP_WIN +# include "nsISound.h" +# include "nsWidgetsCID.h" +#endif +#include "nsUTF8Utils.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/PresShell.h" +#include "mozilla/TextEvents.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/KeyboardEvent.h" + +using namespace mozilla; +using mozilla::dom::KeyboardEvent; + +// +// NS_NewMenuBarFrame +// +// Wrapper for creating a new menu Bar container +// +nsIFrame* NS_NewMenuBarFrame(PresShell* aPresShell, ComputedStyle* aStyle) { + return new (aPresShell) nsMenuBarFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsMenuBarFrame) + +NS_QUERYFRAME_HEAD(nsMenuBarFrame) + NS_QUERYFRAME_ENTRY(nsMenuBarFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) + +// +// nsMenuBarFrame cntr +// +nsMenuBarFrame::nsMenuBarFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsBoxFrame(aStyle, aPresContext, kClassID), + mStayActive(false), + mIsActive(false), + mActiveByKeyboard(false), + mCurrentMenu(nullptr) {} // cntr + +void nsMenuBarFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + + // Create the menu bar listener. + mMenuBarListener = new nsMenuBarListener(this, aContent); +} + +NS_IMETHODIMP +nsMenuBarFrame::SetActive(bool aActiveFlag) { + // If the activity is not changed, there is nothing to do. + if (mIsActive == aActiveFlag) return NS_OK; + + if (!aActiveFlag) { + // Don't deactivate when switching between menus on the menubar. + if (mStayActive) return NS_OK; + + // 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(); + } else { + mActiveByKeyboard = false; + RemoveKeyboardNavigator(); + } + + constexpr auto active = u"DOMMenuBarActive"_ns; + constexpr auto inactive = u"DOMMenuBarInactive"_ns; + + FireDOMEvent(mIsActive ? active : inactive, mContent); + + return NS_OK; +} + +nsMenuFrame* nsMenuBarFrame::ToggleMenuActiveState() { + if (mIsActive) { + // Deactivate the menu bar + SetActive(false); + if (mCurrentMenu) { + nsMenuFrame* closeframe = mCurrentMenu; + closeframe->SelectMenu(false); + mCurrentMenu = nullptr; + return closeframe; + } + } else { + // if the menu bar is already selected (eg. mouseover), deselect it + if (mCurrentMenu) mCurrentMenu->SelectMenu(false); + + // 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, nullptr, false, false); + if (firstFrame) { + // Activate the menu bar + SetActive(true); + firstFrame->SelectMenu(true); + + // Track this item for keyboard navigation. + mCurrentMenu = firstFrame; + } + } + + return nullptr; +} + +nsMenuFrame* nsMenuBarFrame::FindMenuWithShortcut(KeyboardEvent* aKeyEvent, + bool aPeek) { + uint32_t charCode = aKeyEvent->CharCode(); + + AutoTArray<uint32_t, 10> accessKeys; + WidgetKeyboardEvent* nativeKeyEvent = + aKeyEvent->WidgetEventPtr()->AsKeyboardEvent(); + if (nativeKeyEvent) { + nativeKeyEvent->GetAccessKeyCandidates(accessKeys); + } + if (accessKeys.IsEmpty() && charCode) accessKeys.AppendElement(charCode); + + if (accessKeys.IsEmpty()) + return nullptr; // no character was pressed so just return + + // Enumerate over our list of frames. + nsContainerFrame* immediateParent = + nsXULPopupManager::ImmediateParentFrame(this); + + // Find a most preferred accesskey which should be returned. + nsIFrame* foundMenu = nullptr; + size_t foundIndex = accessKeys.NoIndex; + nsIFrame* currFrame = immediateParent->PrincipalChildList().FirstChild(); + + while (currFrame) { + nsIContent* current = currFrame->GetContent(); + + // See if it's a menu item. + if (nsXULPopupManager::IsValidMenuItem(current, false)) { + // Get the shortcut attribute. + nsAutoString shortcutKey; + if (current->IsElement()) { + current->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, + shortcutKey); + } + if (!shortcutKey.IsEmpty()) { + ToLowerCase(shortcutKey); + const char16_t* start = shortcutKey.BeginReading(); + const char16_t* end = shortcutKey.EndReading(); + uint32_t ch = UTF16CharEnumerator::NextChar(&start, end); + size_t index = accessKeys.IndexOf(ch); + if (index != accessKeys.NoIndex && + (foundIndex == accessKeys.NoIndex || index < foundIndex)) { + foundMenu = currFrame; + foundIndex = index; + } + } + } + currFrame = currFrame->GetNextSibling(); + } + if (foundMenu) { + return do_QueryFrame(foundMenu); + } + + // didn't find a matching menu item +#ifdef XP_WIN + if (!aPeek) { + // behavior on Windows - this item is on the menu bar, beep and deactivate + // the menu bar + if (mIsActive) { + nsCOMPtr<nsISound> soundInterface = + do_CreateInstance("@mozilla.org/sound;1"); + if (soundInterface) soundInterface->Beep(); + } + + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + nsIFrame* popup = pm->GetTopPopup(ePopupTypeMenu); + if (popup) pm->HidePopup(popup->GetContent(), true, true, true, false); + } + + SetCurrentMenuItem(nullptr); + SetActive(false); + } + +#endif // #ifdef XP_WIN + + return nullptr; +} + +/* virtual */ +nsMenuFrame* nsMenuBarFrame::GetCurrentMenuItem() { return mCurrentMenu; } + +NS_IMETHODIMP +nsMenuBarFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem) { + if (mCurrentMenu == aMenuItem) return NS_OK; + + if (mCurrentMenu) mCurrentMenu->SelectMenu(false); + + if (aMenuItem) aMenuItem->SelectMenu(true); + + mCurrentMenu = aMenuItem; + + return NS_OK; +} + +void nsMenuBarFrame::CurrentMenuIsBeingDestroyed() { + mCurrentMenu->SelectMenu(false); + mCurrentMenu = nullptr; +} + +class nsMenuBarSwitchMenu : public Runnable { + public: + nsMenuBarSwitchMenu(nsIContent* aMenuBar, nsIContent* aOldMenu, + nsIContent* aNewMenu, bool aSelectFirstItem) + : mozilla::Runnable("nsMenuBarSwitchMenu"), + mMenuBar(aMenuBar), + mOldMenu(aOldMenu), + mNewMenu(aNewMenu), + mSelectFirstItem(aSelectFirstItem) {} + + NS_IMETHOD Run() override { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (!pm) return NS_ERROR_UNEXPECTED; + + // if switching from one menu to another, set a flag so that the call to + // HidePopup doesn't deactivate the menubar when the first menu closes. + nsMenuBarFrame* menubar = nullptr; + if (mOldMenu && mNewMenu) { + menubar = do_QueryFrame(mMenuBar->GetPrimaryFrame()); + if (menubar) menubar->SetStayActive(true); + } + + if (mOldMenu) { + AutoWeakFrame weakMenuBar(menubar); + pm->HidePopup(mOldMenu, false, false, false, false); + // clear the flag again + if (mNewMenu && weakMenuBar.IsAlive()) menubar->SetStayActive(false); + } + + if (mNewMenu) pm->ShowMenu(mNewMenu, mSelectFirstItem, false); + + return NS_OK; + } + + private: + nsCOMPtr<nsIContent> mMenuBar; + nsCOMPtr<nsIContent> mOldMenu; + nsCOMPtr<nsIContent> mNewMenu; + bool mSelectFirstItem; +}; + +NS_IMETHODIMP +nsMenuBarFrame::ChangeMenuItem(nsMenuFrame* aMenuItem, bool aSelectFirstItem, + bool aFromKey) { + if (mCurrentMenu == aMenuItem) return NS_OK; + + // check if there's an open context menu, we ignore this + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm && pm->HasContextMenu(nullptr)) return NS_OK; + + nsIContent* aOldMenu = nullptr; + nsIContent* aNewMenu = nullptr; + + // Unset the current child. + bool wasOpen = false; + if (mCurrentMenu) { + wasOpen = mCurrentMenu->IsOpen(); + mCurrentMenu->SelectMenu(false); + if (wasOpen) { + nsMenuPopupFrame* popupFrame = mCurrentMenu->GetPopup(); + if (popupFrame) aOldMenu = popupFrame->GetContent(); + } + } + + // set to null first in case the IsAlive check below returns false + mCurrentMenu = nullptr; + + // Set the new child. + if (aMenuItem) { + nsCOMPtr<nsIContent> content = aMenuItem->GetContent(); + aMenuItem->SelectMenu(true); + mCurrentMenu = aMenuItem; + if (wasOpen && !aMenuItem->IsDisabled()) aNewMenu = content; + } + + // use an event so that hiding and showing can be done synchronously, which + // avoids flickering + nsCOMPtr<nsIRunnable> event = new nsMenuBarSwitchMenu( + GetContent(), aOldMenu, aNewMenu, aSelectFirstItem); + return mContent->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget()); +} + +nsMenuFrame* nsMenuBarFrame::Enter(WidgetGUIEvent* aEvent) { + if (!mCurrentMenu) return nullptr; + + if (mCurrentMenu->IsOpen()) return mCurrentMenu->Enter(aEvent); + + return mCurrentMenu; +} + +bool nsMenuBarFrame::MenuClosed() { + SetActive(false); + if (!mIsActive && mCurrentMenu) { + mCurrentMenu->SelectMenu(false); + mCurrentMenu = nullptr; + return true; + } + return false; +} + +void nsMenuBarFrame::InstallKeyboardNavigator() { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) pm->SetActiveMenuBar(this, true); +} + +void nsMenuBarFrame::RemoveKeyboardNavigator() { + if (!mIsActive) { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) pm->SetActiveMenuBar(this, false); + } +} + +void nsMenuBarFrame::DestroyFrom(nsIFrame* aDestructRoot, + PostDestroyData& aPostDestroyData) { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) pm->SetActiveMenuBar(this, false); + + mMenuBarListener->OnDestroyMenuBarFrame(); + mMenuBarListener = nullptr; + + nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData); +} |