2007-07-04 15:49:38 +00:00
|
|
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
*
|
|
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
|
|
* the License. You may obtain a copy of the License at
|
|
|
|
* http://www.mozilla.org/MPL/
|
|
|
|
*
|
|
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
|
|
* for the specific language governing rights and limitations under the
|
|
|
|
* License.
|
|
|
|
*
|
|
|
|
* The Original Code is mozilla.org code.
|
|
|
|
*
|
|
|
|
* The Initial Developer of the Original Code is Neil Deakin
|
|
|
|
* Portions created by the Initial Developer are Copyright (C) 2006
|
|
|
|
* the Initial Developer. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Contributor(s):
|
|
|
|
*
|
|
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
|
|
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
|
|
|
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
|
|
* the provisions above, a recipient may use your version of this file under
|
|
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
*
|
|
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
|
|
|
|
#include "nsGkAtoms.h"
|
|
|
|
#include "nsXULPopupManager.h"
|
|
|
|
#include "nsMenuFrame.h"
|
|
|
|
#include "nsMenuPopupFrame.h"
|
|
|
|
#include "nsMenuBarFrame.h"
|
|
|
|
#include "nsIPopupBoxObject.h"
|
|
|
|
#include "nsMenuBarListener.h"
|
|
|
|
#include "nsContentUtils.h"
|
|
|
|
#include "nsIDOMDocument.h"
|
|
|
|
#include "nsIDOMNSEvent.h"
|
|
|
|
#include "nsIDOMNSUIEvent.h"
|
2007-07-17 12:21:53 +00:00
|
|
|
#include "nsIPrivateDOMEvent.h"
|
2007-07-04 15:49:38 +00:00
|
|
|
#include "nsEventDispatcher.h"
|
2007-07-24 00:55:52 +00:00
|
|
|
#include "nsEventStateManager.h"
|
2007-07-04 15:49:38 +00:00
|
|
|
#include "nsCSSFrameConstructor.h"
|
|
|
|
#include "nsLayoutUtils.h"
|
|
|
|
#include "nsIViewManager.h"
|
|
|
|
#include "nsILookAndFeel.h"
|
|
|
|
#include "nsIComponentManager.h"
|
|
|
|
#include "nsITimer.h"
|
|
|
|
#include "nsIFocusController.h"
|
|
|
|
#include "nsIDocShellTreeItem.h"
|
|
|
|
#include "nsIDocShell.h"
|
|
|
|
#include "nsPIDOMWindow.h"
|
|
|
|
#include "nsIInterfaceRequestorUtils.h"
|
|
|
|
#include "nsIBaseWindow.h"
|
|
|
|
|
2007-07-16 22:25:29 +00:00
|
|
|
// See matching definitions in nsXULPopupManager.h
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
nsXULPopupManager* nsXULPopupManager::sInstance = nsnull;
|
|
|
|
|
|
|
|
nsIContent* nsMenuChainItem::Content()
|
|
|
|
{
|
|
|
|
return mFrame->GetContent();
|
|
|
|
}
|
|
|
|
|
|
|
|
void nsMenuChainItem::SetParent(nsMenuChainItem* aParent)
|
|
|
|
{
|
|
|
|
if (mParent) {
|
|
|
|
NS_ASSERTION(mParent->mChild == this, "Unexpected - parent's child not set to this");
|
|
|
|
mParent->mChild = nsnull;
|
|
|
|
}
|
|
|
|
mParent = aParent;
|
|
|
|
if (mParent) {
|
|
|
|
if (mParent->mChild)
|
|
|
|
mParent->mChild->mParent = nsnull;
|
|
|
|
mParent->mChild = this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void nsMenuChainItem::Detach(nsMenuChainItem** aRoot)
|
|
|
|
{
|
|
|
|
// If the item has a child, set the child's parent to this item's parent,
|
|
|
|
// effectively removing the item from the chain. If the item has no child,
|
|
|
|
// just set the parent to null.
|
|
|
|
if (mChild) {
|
|
|
|
NS_ASSERTION(this != *aRoot, "Unexpected - popup with child at end of chain");
|
|
|
|
mChild->SetParent(mParent);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// An item without a child should be the first item in the chain, so set
|
|
|
|
// the first item pointer, pointed to by aRoot, to the parent.
|
|
|
|
NS_ASSERTION(this == *aRoot, "Unexpected - popup with no child not at end of chain");
|
|
|
|
*aRoot = mParent;
|
|
|
|
SetParent(nsnull);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMPL_ISUPPORTS4(nsXULPopupManager, nsIDOMKeyListener,
|
|
|
|
nsIMenuRollup, nsIRollupListener, nsITimerCallback)
|
|
|
|
|
|
|
|
nsXULPopupManager::nsXULPopupManager() :
|
|
|
|
mRangeOffset(0),
|
|
|
|
mActiveMenuBar(nsnull),
|
|
|
|
mCurrentMenu(nsnull),
|
|
|
|
mPanels(nsnull),
|
|
|
|
mTimerMenu(nsnull)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
nsXULPopupManager::~nsXULPopupManager()
|
|
|
|
{
|
|
|
|
NS_ASSERTION(!mCurrentMenu && !mPanels, "XUL popups still open");
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
nsXULPopupManager::Init()
|
|
|
|
{
|
|
|
|
sInstance = new nsXULPopupManager();
|
|
|
|
NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
NS_ADDREF(sInstance);
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::Shutdown()
|
|
|
|
{
|
2007-07-12 13:57:01 +00:00
|
|
|
NS_IF_RELEASE(sInstance);
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
nsXULPopupManager*
|
|
|
|
nsXULPopupManager::GetInstance()
|
|
|
|
{
|
|
|
|
return sInstance;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
|
|
|
nsXULPopupManager::Rollup()
|
|
|
|
{
|
2007-09-18 15:00:43 +00:00
|
|
|
nsMenuChainItem* item = GetTopVisibleMenu();
|
|
|
|
if (item)
|
|
|
|
HidePopup(item->Content(), PR_TRUE, PR_TRUE, PR_FALSE);
|
2007-07-04 15:49:38 +00:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
NS_IMETHODIMP nsXULPopupManager::ShouldRollupOnMouseWheelEvent(PRBool *aShouldRollup)
|
|
|
|
{
|
|
|
|
// should rollup only for autocomplete widgets
|
|
|
|
// XXXndeakin this should really be something the popup has more control over
|
2007-08-15 14:03:21 +00:00
|
|
|
nsMenuChainItem* item = GetTopVisibleMenu();
|
|
|
|
*aShouldRollup = (item && !item->Frame()->IsMenu());
|
2007-07-04 15:49:38 +00:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// a menu should not roll up if activated by a mouse activate message (eg. X-mouse)
|
|
|
|
NS_IMETHODIMP nsXULPopupManager::ShouldRollupOnMouseActivate(PRBool *aShouldRollup)
|
|
|
|
{
|
|
|
|
*aShouldRollup = PR_FALSE;
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
|
|
|
nsXULPopupManager::GetSubmenuWidgetChain(nsISupportsArray **_retval)
|
|
|
|
{
|
2007-08-09 08:53:19 +00:00
|
|
|
// this method is used by the widget code to determine the list of popups
|
|
|
|
// that are open. If a mouse click occurs outside one of these popups, the
|
|
|
|
// panels will roll up. If the click is inside a popup, they will not roll up
|
2007-07-04 15:49:38 +00:00
|
|
|
nsresult rv = NS_NewISupportsArray(_retval);
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
2007-08-15 14:03:21 +00:00
|
|
|
nsMenuChainItem* item = GetTopVisibleMenu();
|
2007-07-04 15:49:38 +00:00
|
|
|
while (item) {
|
|
|
|
nsCOMPtr<nsIWidget> widget;
|
|
|
|
item->Frame()->GetWidget(getter_AddRefs(widget));
|
2007-09-18 14:32:22 +00:00
|
|
|
NS_ASSERTION(widget, "open popup has no widget");
|
2007-07-04 15:49:38 +00:00
|
|
|
nsCOMPtr<nsISupports> genericWidget(do_QueryInterface(widget));
|
|
|
|
(*_retval)->AppendElement(genericWidget);
|
2007-08-09 08:53:19 +00:00
|
|
|
// In the case when a menulist inside a panel is open, clicking in the
|
|
|
|
// panel should still roll up the menu, so if a different type is found,
|
|
|
|
// stop scanning.
|
|
|
|
nsMenuChainItem* parent= item->GetParent();
|
|
|
|
if (parent && item->Frame()->PopupType() != parent->Frame()->PopupType())
|
|
|
|
break;
|
|
|
|
item = parent;
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsIFrame*
|
|
|
|
nsXULPopupManager::GetFrameOfTypeForContent(nsIContent* aContent,
|
2007-10-09 12:11:14 +00:00
|
|
|
nsIAtom* aFrameType,
|
|
|
|
PRBool aShouldFlush)
|
2007-07-04 15:49:38 +00:00
|
|
|
{
|
|
|
|
nsIDocument *document = aContent->GetCurrentDoc();
|
|
|
|
if (document) {
|
2007-10-09 12:11:14 +00:00
|
|
|
nsCOMPtr<nsIPresShell> presShell = document->GetPrimaryShell();
|
2007-07-04 15:49:38 +00:00
|
|
|
if (presShell) {
|
2007-10-09 12:11:14 +00:00
|
|
|
if (aShouldFlush)
|
|
|
|
presShell->FlushPendingNotifications(Flush_Frames);
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
nsIFrame* frame = presShell->GetPrimaryFrameFor(aContent);
|
|
|
|
if (frame && frame->GetType() == aFrameType)
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsMenuFrame*
|
|
|
|
nsXULPopupManager::GetMenuFrameForContent(nsIContent* aContent)
|
|
|
|
{
|
2007-10-09 12:11:14 +00:00
|
|
|
// as ShowMenu is called from frames, don't flush to be safe.
|
2007-07-08 07:08:04 +00:00
|
|
|
return static_cast<nsMenuFrame *>
|
2007-10-09 12:11:14 +00:00
|
|
|
(GetFrameOfTypeForContent(aContent, nsGkAtoms::menuFrame, PR_FALSE));
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
nsMenuPopupFrame*
|
|
|
|
nsXULPopupManager::GetPopupFrameForContent(nsIContent* aContent)
|
|
|
|
{
|
2007-07-08 07:08:04 +00:00
|
|
|
return static_cast<nsMenuPopupFrame *>
|
2007-10-09 12:11:14 +00:00
|
|
|
(GetFrameOfTypeForContent(aContent, nsGkAtoms::menuPopupFrame, PR_TRUE));
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
|
2007-08-15 14:03:21 +00:00
|
|
|
nsMenuChainItem*
|
|
|
|
nsXULPopupManager::GetTopVisibleMenu()
|
|
|
|
{
|
|
|
|
nsMenuChainItem* item = mCurrentMenu;
|
|
|
|
while (item && item->Frame()->PopupState() == ePopupInvisible)
|
|
|
|
item = item->GetParent();
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
void
|
|
|
|
nsXULPopupManager::GetMouseLocation(nsIDOMNode** aNode, PRInt32* aOffset)
|
|
|
|
{
|
|
|
|
*aNode = mRangeParent;
|
|
|
|
NS_IF_ADDREF(*aNode);
|
|
|
|
*aOffset = mRangeOffset;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2007-07-17 12:21:53 +00:00
|
|
|
nsXULPopupManager::SetMouseLocation(nsIDOMEvent* aEvent, nsIContent* aPopup)
|
2007-07-04 15:49:38 +00:00
|
|
|
{
|
2007-07-17 12:21:53 +00:00
|
|
|
mCachedMousePoint = nsPoint(0, 0);
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
nsCOMPtr<nsIDOMNSUIEvent> uiEvent = do_QueryInterface(aEvent);
|
2007-07-17 12:21:53 +00:00
|
|
|
NS_ASSERTION(!aEvent || uiEvent, "Expected an nsIDOMNSUIEvent");
|
2007-07-04 15:49:38 +00:00
|
|
|
if (uiEvent) {
|
|
|
|
uiEvent->GetRangeParent(getter_AddRefs(mRangeParent));
|
|
|
|
uiEvent->GetRangeOffset(&mRangeOffset);
|
2007-07-17 12:21:53 +00:00
|
|
|
|
|
|
|
// get the event coordinates relative to the root frame of the document
|
|
|
|
// containing the popup.
|
|
|
|
nsCOMPtr<nsIPrivateDOMEvent> privateEvent(do_QueryInterface(aEvent));
|
|
|
|
if (privateEvent) {
|
|
|
|
NS_ASSERTION(aPopup, "Expected a popup node");
|
|
|
|
nsEvent* event;
|
|
|
|
nsresult rv = privateEvent->GetInternalNSEvent(&event);
|
|
|
|
if (NS_SUCCEEDED(rv) && event) {
|
|
|
|
nsIDocument* doc = aPopup->GetCurrentDoc();
|
|
|
|
if (doc) {
|
|
|
|
nsIPresShell* presShell = doc->GetPrimaryShell();
|
|
|
|
if (presShell) {
|
|
|
|
nsPresContext* presContext = presShell->GetPresContext();
|
|
|
|
nsIFrame* rootFrame = presShell->GetRootFrame();
|
|
|
|
if (rootFrame && presContext) {
|
|
|
|
nsPoint pnt =
|
|
|
|
nsLayoutUtils::GetEventCoordinatesRelativeTo(event, rootFrame);
|
|
|
|
mCachedMousePoint = nsPoint(presContext->AppUnitsToDevPixels(pnt.x),
|
|
|
|
presContext->AppUnitsToDevPixels(pnt.y));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
mRangeParent = nsnull;
|
|
|
|
mRangeOffset = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::SetActiveMenuBar(nsMenuBarFrame* aMenuBar, PRBool aActivate)
|
|
|
|
{
|
|
|
|
if (aActivate)
|
|
|
|
mActiveMenuBar = aMenuBar;
|
|
|
|
else if (mActiveMenuBar == aMenuBar)
|
|
|
|
mActiveMenuBar = nsnull;
|
|
|
|
|
|
|
|
UpdateKeyboardListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::ShowMenu(nsIContent *aMenu,
|
|
|
|
PRBool aSelectFirstItem,
|
|
|
|
PRBool aAsynchronous)
|
|
|
|
{
|
|
|
|
nsMenuFrame* menuFrame = GetMenuFrameForContent(aMenu);
|
|
|
|
if (!menuFrame || !menuFrame->IsMenu())
|
|
|
|
return;
|
|
|
|
|
|
|
|
nsMenuPopupFrame* popupFrame = menuFrame->GetPopup();
|
|
|
|
if (!popupFrame || !MayShowPopup(popupFrame))
|
|
|
|
return;
|
|
|
|
|
|
|
|
// inherit whether or not we're a context menu from the parent
|
|
|
|
PRBool parentIsContextMenu = PR_FALSE;
|
|
|
|
PRBool onMenuBar = PR_FALSE;
|
|
|
|
PRBool onmenu = menuFrame->IsOnMenu();
|
|
|
|
|
|
|
|
nsIMenuParent* parent = menuFrame->GetMenuParent();
|
|
|
|
if (parent && onmenu) {
|
|
|
|
parentIsContextMenu = parent->IsContextMenu();
|
|
|
|
onMenuBar = parent->IsMenuBar();
|
|
|
|
}
|
|
|
|
|
|
|
|
nsAutoString position;
|
|
|
|
if (onMenuBar || !onmenu)
|
|
|
|
position.AssignLiteral("after_start");
|
|
|
|
else
|
|
|
|
position.AssignLiteral("end_before");
|
|
|
|
popupFrame->InitializePopup(aMenu, position, 0, 0, PR_TRUE);
|
|
|
|
|
|
|
|
if (aAsynchronous) {
|
2007-07-17 12:21:53 +00:00
|
|
|
SetMouseLocation(nsnull, nsnull);
|
2007-07-04 15:49:38 +00:00
|
|
|
nsCOMPtr<nsIRunnable> event =
|
|
|
|
new nsXULPopupShowingEvent(popupFrame->GetContent(), aMenu,
|
|
|
|
parentIsContextMenu, aSelectFirstItem);
|
|
|
|
NS_DispatchToCurrentThread(event);
|
|
|
|
}
|
|
|
|
else {
|
2007-08-03 14:05:07 +00:00
|
|
|
nsCOMPtr<nsIContent> popupContent = popupFrame->GetContent();
|
|
|
|
FirePopupShowingEvent(popupContent, aMenu,
|
2007-08-16 01:09:58 +00:00
|
|
|
popupFrame->PresContext(), popupFrame->PopupType(),
|
2007-07-04 15:49:38 +00:00
|
|
|
parentIsContextMenu, aSelectFirstItem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::ShowPopup(nsIContent* aPopup,
|
|
|
|
nsIContent* aAnchorContent,
|
|
|
|
const nsAString& aPosition,
|
|
|
|
PRInt32 aXPos, PRInt32 aYPos,
|
|
|
|
PRBool aIsContextMenu,
|
|
|
|
PRBool aAttributesOverride,
|
|
|
|
PRBool aSelectFirstItem)
|
|
|
|
{
|
|
|
|
nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup);
|
|
|
|
if (!popupFrame || !MayShowPopup(popupFrame))
|
|
|
|
return;
|
|
|
|
|
|
|
|
popupFrame->InitializePopup(aAnchorContent, aPosition, aXPos, aYPos,
|
|
|
|
aAttributesOverride);
|
|
|
|
|
|
|
|
FirePopupShowingEvent(aPopup, nsnull, popupFrame->PresContext(),
|
2007-08-16 01:09:58 +00:00
|
|
|
popupFrame->PopupType(), aIsContextMenu, aSelectFirstItem);
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup,
|
|
|
|
PRInt32 aXPos, PRInt32 aYPos,
|
|
|
|
PRBool aIsContextMenu)
|
|
|
|
{
|
|
|
|
nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup);
|
|
|
|
if (!popupFrame || !MayShowPopup(popupFrame))
|
|
|
|
return;
|
|
|
|
|
|
|
|
popupFrame->InitializePopupAtScreen(aXPos, aYPos);
|
|
|
|
|
|
|
|
FirePopupShowingEvent(aPopup, nsnull, popupFrame->PresContext(),
|
2007-08-16 01:09:58 +00:00
|
|
|
popupFrame->PopupType(), aIsContextMenu, PR_FALSE);
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::ShowPopupWithAnchorAlign(nsIContent* aPopup,
|
|
|
|
nsIContent* aAnchorContent,
|
|
|
|
nsAString& aAnchor,
|
|
|
|
nsAString& aAlign,
|
|
|
|
PRInt32 aXPos, PRInt32 aYPos,
|
|
|
|
PRBool aIsContextMenu)
|
|
|
|
{
|
|
|
|
nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup);
|
|
|
|
if (!popupFrame || !MayShowPopup(popupFrame))
|
|
|
|
return;
|
|
|
|
|
|
|
|
popupFrame->InitializePopupWithAnchorAlign(aAnchorContent, aAnchor,
|
|
|
|
aAlign, aXPos, aYPos);
|
|
|
|
|
|
|
|
FirePopupShowingEvent(aPopup, nsnull, popupFrame->PresContext(),
|
2007-08-16 01:09:58 +00:00
|
|
|
popupFrame->PopupType(), aIsContextMenu, PR_FALSE);
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
|
|
|
|
nsMenuPopupFrame* aPopupFrame,
|
|
|
|
PRBool aIsContextMenu,
|
|
|
|
PRBool aSelectFirstItem)
|
|
|
|
{
|
|
|
|
// clear these as they are no longer valid
|
|
|
|
mRangeParent = nsnull;
|
|
|
|
mRangeOffset = 0;
|
|
|
|
|
2007-08-16 01:09:58 +00:00
|
|
|
nsPopupType popupType = aPopupFrame->PopupType();
|
2007-07-04 15:49:38 +00:00
|
|
|
PRBool ismenu = (popupType == ePopupTypeMenu);
|
|
|
|
|
|
|
|
nsMenuChainItem* item =
|
2007-08-16 01:09:58 +00:00
|
|
|
new nsMenuChainItem(aPopupFrame, aIsContextMenu, popupType);
|
2007-07-04 15:49:38 +00:00
|
|
|
if (!item)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// install keyboard event listeners for navigating menus, but only if
|
|
|
|
// another menu isn't already open.
|
|
|
|
// The ignorekeys attribute may be used to disable adding these event listeners
|
|
|
|
// for menus that want to handle their own keyboard events.
|
|
|
|
if (ismenu) {
|
|
|
|
if (aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ignorekeys,
|
|
|
|
nsGkAtoms::_true, eCaseMatters))
|
|
|
|
item->SetIgnoreKeys(PR_TRUE);
|
|
|
|
|
|
|
|
// if the menu is on a menubar, use the menubar's listener instead
|
|
|
|
nsIFrame* parent = aPopupFrame->GetParent();
|
|
|
|
if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
|
2007-07-08 07:08:04 +00:00
|
|
|
nsMenuFrame* menuFrame = static_cast<nsMenuFrame *>(parent);
|
2007-07-04 15:49:38 +00:00
|
|
|
item->SetOnMenuBar(menuFrame->IsOnMenuBar());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// use a weak frame as the popup will set an open attribute if it is a menu
|
|
|
|
nsWeakFrame weakFrame(aPopupFrame);
|
|
|
|
PRBool hasChildren = aPopupFrame->ShowPopup(aIsContextMenu, aSelectFirstItem);
|
|
|
|
ENSURE_TRUE(weakFrame.IsAlive());
|
|
|
|
|
|
|
|
// popups normally hide when an outside click occurs. Panels may use
|
|
|
|
// the noautohide attribute to disable this behaviour. It is expected
|
|
|
|
// that the application will hide these popups manually. The tooltip
|
|
|
|
// listener will handle closing the tooltip also.
|
|
|
|
if (popupType == ePopupTypeTooltip ||
|
|
|
|
(popupType == ePopupTypePanel &&
|
|
|
|
aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautohide,
|
|
|
|
nsGkAtoms::_true, eIgnoreCase))) {
|
|
|
|
item->SetParent(mPanels);
|
|
|
|
mPanels = item;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
nsIContent* oldmenu = nsnull;
|
|
|
|
if (mCurrentMenu)
|
|
|
|
oldmenu = mCurrentMenu->Content();
|
|
|
|
item->SetParent(mCurrentMenu);
|
|
|
|
mCurrentMenu = item;
|
|
|
|
SetCaptureState(oldmenu);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasChildren) {
|
|
|
|
if (aSelectFirstItem) {
|
|
|
|
nsMenuFrame* next = GetNextMenuItem(aPopupFrame, nsnull, PR_TRUE);
|
|
|
|
aPopupFrame->SetCurrentMenuItem(next);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ismenu)
|
|
|
|
UpdateMenuItems(aPopup);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::HidePopup(nsIContent* aPopup,
|
|
|
|
PRBool aHideChain,
|
|
|
|
PRBool aDeselectMenu,
|
|
|
|
PRBool aAsynchronous)
|
|
|
|
{
|
|
|
|
// remove the popup from the open lists. Just to be safe, check both the
|
|
|
|
// menu and panels lists.
|
|
|
|
|
|
|
|
// if the popup is on the panels list, remove it but don't close any other panels
|
|
|
|
nsMenuPopupFrame* popupFrame = nsnull;
|
|
|
|
PRBool foundPanel = PR_FALSE;
|
|
|
|
nsMenuChainItem* item = mPanels;
|
|
|
|
while (item) {
|
|
|
|
if (item->Content() == aPopup) {
|
|
|
|
foundPanel = PR_TRUE;
|
|
|
|
popupFrame = item->Frame();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
item = item->GetParent();
|
|
|
|
}
|
|
|
|
|
|
|
|
// when removing a menu, all of the child popups must be closed
|
|
|
|
nsMenuChainItem* foundMenu = nsnull;
|
|
|
|
item = mCurrentMenu;
|
|
|
|
while (item) {
|
|
|
|
if (item->Content() == aPopup) {
|
|
|
|
foundMenu = item;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
item = item->GetParent();
|
|
|
|
}
|
|
|
|
|
2007-08-16 01:09:58 +00:00
|
|
|
nsPopupType type = ePopupTypePanel;
|
2007-07-04 15:49:38 +00:00
|
|
|
PRBool deselectMenu = PR_FALSE;
|
|
|
|
nsCOMPtr<nsIContent> popupToHide, nextPopup, lastPopup;
|
|
|
|
if (foundMenu) {
|
2007-08-23 22:13:42 +00:00
|
|
|
// at this point, foundMenu will be set to the found item in the list. If
|
|
|
|
// foundMenu is the topmost menu, the one to remove, then there are no other
|
|
|
|
// popups to hide. If foundMenu is not the topmost menu, then there may be
|
2007-07-04 15:49:38 +00:00
|
|
|
// open submenus below it. In this case, we need to make sure that those
|
2007-08-23 22:13:42 +00:00
|
|
|
// submenus are closed up first. To do this, we scan up the menu list to
|
|
|
|
// find the topmost popup with only menus between it and foundMenu and
|
|
|
|
// close that menu first. In synchronous mode, the FirePopupHidingEvent
|
|
|
|
// method will be called which in turn calls HidePopupCallback to close up
|
|
|
|
// the next popup in the chain. These two methods will be called in
|
|
|
|
// sequence recursively to close up all the necessary popups. In
|
|
|
|
// asynchronous mode, a similar process occurs except that the
|
|
|
|
// FirePopupHidingEvent method is called asynchrounsly. In either case,
|
|
|
|
// nextPopup is set to the content node of the next popup to close, and
|
|
|
|
// lastPopup is set to the last popup in the chain to close, which will be
|
|
|
|
// aPopup, or null to close up all menus.
|
|
|
|
|
|
|
|
nsMenuChainItem* topMenu = foundMenu;
|
|
|
|
// Use IsMenu to ensure that foundMenu is a menu and scan down the child
|
|
|
|
// list until a non-menu is found. If foundMenu isn't a menu at all, don't
|
|
|
|
// scan and just close up this menu.
|
|
|
|
if (foundMenu->IsMenu()) {
|
|
|
|
item = topMenu->GetChild();
|
|
|
|
while (item && item->IsMenu()) {
|
|
|
|
topMenu = item;
|
|
|
|
item = item->GetChild();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
deselectMenu = aDeselectMenu;
|
2007-08-23 22:13:42 +00:00
|
|
|
popupToHide = topMenu->Content();
|
|
|
|
popupFrame = topMenu->Frame();
|
2007-08-16 01:09:58 +00:00
|
|
|
type = popupFrame->PopupType();
|
2007-07-04 15:49:38 +00:00
|
|
|
|
2007-08-23 22:13:42 +00:00
|
|
|
nsMenuChainItem* parent = topMenu->GetParent();
|
2007-07-04 15:49:38 +00:00
|
|
|
|
2007-08-03 14:05:07 +00:00
|
|
|
// close up another popup if there is one, and we are either hiding the
|
|
|
|
// entire chain or the item to hide isn't the topmost popup.
|
2007-08-23 22:13:42 +00:00
|
|
|
if (parent && (aHideChain || topMenu != foundMenu))
|
2007-08-03 14:05:07 +00:00
|
|
|
nextPopup = parent->Content();
|
2007-07-04 15:49:38 +00:00
|
|
|
|
|
|
|
lastPopup = aHideChain ? nsnull : aPopup;
|
|
|
|
}
|
|
|
|
else if (foundPanel) {
|
|
|
|
popupToHide = aPopup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (popupFrame) {
|
2007-08-03 14:05:07 +00:00
|
|
|
nsPopupState state = popupFrame->PopupState();
|
|
|
|
// if the popup is already being hidden, don't attempt to hide it again
|
|
|
|
if (state == ePopupHiding)
|
|
|
|
return;
|
|
|
|
// change the popup state to hiding. Don't set the hiding state if the
|
|
|
|
// popup is invisible, otherwise nsMenuPopupFrame::HidePopup will
|
|
|
|
// run again. In the invisible state, we just want the events to fire.
|
|
|
|
if (state != ePopupInvisible)
|
|
|
|
popupFrame->SetPopupState(ePopupHiding);
|
|
|
|
|
|
|
|
// for menus, popupToHide is always the frommost item in the list to hide.
|
2007-07-04 15:49:38 +00:00
|
|
|
if (aAsynchronous) {
|
|
|
|
nsCOMPtr<nsIRunnable> event =
|
|
|
|
new nsXULPopupHidingEvent(popupToHide, nextPopup, lastPopup,
|
2007-08-16 01:09:58 +00:00
|
|
|
type, deselectMenu);
|
2007-07-04 15:49:38 +00:00
|
|
|
NS_DispatchToCurrentThread(event);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
FirePopupHidingEvent(popupToHide, nextPopup, lastPopup,
|
2007-08-16 01:09:58 +00:00
|
|
|
popupFrame->PresContext(), type, deselectMenu);
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::HidePopupCallback(nsIContent* aPopup,
|
|
|
|
nsMenuPopupFrame* aPopupFrame,
|
|
|
|
nsIContent* aNextPopup,
|
|
|
|
nsIContent* aLastPopup,
|
2007-08-16 01:09:58 +00:00
|
|
|
nsPopupType aPopupType,
|
2007-07-04 15:49:38 +00:00
|
|
|
PRBool aDeselectMenu)
|
|
|
|
{
|
|
|
|
if (mCloseTimer) {
|
|
|
|
mCloseTimer->Cancel();
|
|
|
|
mCloseTimer = nsnull;
|
|
|
|
mTimerMenu = nsnull;
|
|
|
|
}
|
|
|
|
|
2007-08-03 14:05:07 +00:00
|
|
|
// The popup to hide is aPopup. Search the list again to find the item that
|
|
|
|
// corresponds to the popup to hide aPopup. This is done because it's
|
|
|
|
// possible someone added another item (attempted to open another popup)
|
|
|
|
// or removed a popup frame during the event processing so the item isn't at
|
|
|
|
// the front anymore.
|
|
|
|
nsMenuChainItem* item = mPanels;
|
|
|
|
while (item) {
|
|
|
|
if (item->Content() == aPopup) {
|
|
|
|
item->Detach(&mPanels);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
item = item->GetParent();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!item) {
|
|
|
|
item = mCurrentMenu;
|
|
|
|
while (item) {
|
|
|
|
if (item->Content() == aPopup) {
|
|
|
|
item->Detach(&mCurrentMenu);
|
|
|
|
SetCaptureState(aPopup);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
item = item->GetParent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
delete item;
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
nsWeakFrame weakFrame(aPopupFrame);
|
2007-08-03 14:05:07 +00:00
|
|
|
aPopupFrame->HidePopup(aDeselectMenu, ePopupClosed);
|
2007-07-04 15:49:38 +00:00
|
|
|
ENSURE_TRUE(weakFrame.IsAlive());
|
|
|
|
|
|
|
|
// send the popuphidden event synchronously. This event has no default behaviour.
|
|
|
|
nsEventStatus status = nsEventStatus_eIgnore;
|
2007-07-17 12:21:53 +00:00
|
|
|
nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_HIDDEN, nsnull, nsMouseEvent::eReal);
|
2007-07-04 15:49:38 +00:00
|
|
|
nsEventDispatcher::Dispatch(aPopup, aPopupFrame->PresContext(),
|
|
|
|
&event, nsnull, &status);
|
|
|
|
|
|
|
|
// if there are more popups to close, look for the next one
|
|
|
|
if (aNextPopup && aPopup != aLastPopup) {
|
|
|
|
nsMenuChainItem* foundMenu = nsnull;
|
|
|
|
nsMenuChainItem* item = mCurrentMenu;
|
|
|
|
while (item) {
|
|
|
|
if (item->Content() == aNextPopup) {
|
|
|
|
foundMenu = item;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
item = item->GetParent();
|
|
|
|
}
|
|
|
|
|
|
|
|
// continue hiding the chain of popups until the last popup aLastPopup
|
|
|
|
// is reached, or until a popup of a different type is reached. This
|
|
|
|
// last check is needed so that a menulist inside a non-menu panel only
|
|
|
|
// closes the menu and not the panel as well.
|
2007-08-16 01:09:58 +00:00
|
|
|
if (foundMenu &&
|
|
|
|
(aLastPopup || aPopupType == foundMenu->PopupType())) {
|
2007-07-04 15:49:38 +00:00
|
|
|
nsCOMPtr<nsIContent> popupToHide = item->Content();
|
2007-07-10 17:03:32 +00:00
|
|
|
nsMenuChainItem* parent = item->GetParent();
|
2007-07-04 15:49:38 +00:00
|
|
|
|
|
|
|
nsCOMPtr<nsIContent> nextPopup;
|
2007-07-10 17:03:32 +00:00
|
|
|
if (parent && popupToHide != aLastPopup)
|
|
|
|
nextPopup = parent->Content();
|
2007-07-04 15:49:38 +00:00
|
|
|
|
2007-08-03 14:05:07 +00:00
|
|
|
nsMenuPopupFrame* popupFrame = item->Frame();
|
|
|
|
nsPopupState state = popupFrame->PopupState();
|
|
|
|
if (state == ePopupHiding)
|
|
|
|
return;
|
|
|
|
if (state != ePopupInvisible)
|
|
|
|
popupFrame->SetPopupState(ePopupHiding);
|
2007-07-04 15:49:38 +00:00
|
|
|
|
|
|
|
FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup,
|
2007-08-16 01:09:58 +00:00
|
|
|
popupFrame->PresContext(),
|
|
|
|
foundMenu->PopupType(), aDeselectMenu);
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame* aPopup)
|
|
|
|
{
|
|
|
|
// Don't close up immediately.
|
|
|
|
// Kick off a close timer.
|
|
|
|
KillMenuTimer();
|
|
|
|
|
|
|
|
PRInt32 menuDelay = 300; // ms
|
|
|
|
aPopup->PresContext()->LookAndFeel()->
|
|
|
|
GetMetric(nsILookAndFeel::eMetric_SubmenuDelay, menuDelay);
|
|
|
|
|
|
|
|
// Kick off the timer.
|
|
|
|
mCloseTimer = do_CreateInstance("@mozilla.org/timer;1");
|
|
|
|
mCloseTimer->InitWithCallback(this, menuDelay, nsITimer::TYPE_ONE_SHOT);
|
|
|
|
|
|
|
|
// the popup will call PopupDestroyed if it is destroyed, which checks if it
|
|
|
|
// is set to mTimerMenu, so it should be safe to keep a reference to it
|
|
|
|
mTimerMenu = aPopup;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::HidePopupsInDocument(nsIDocument* aDocument)
|
|
|
|
{
|
2007-08-15 14:03:21 +00:00
|
|
|
nsMenuChainItem* item = GetTopVisibleMenu();
|
2007-07-04 15:49:38 +00:00
|
|
|
while (item) {
|
2007-08-03 14:05:07 +00:00
|
|
|
nsMenuChainItem* parent = item->GetParent();
|
|
|
|
if (item->Content()->GetOwnerDoc() == aDocument) {
|
|
|
|
item->Frame()->HidePopup(PR_TRUE, ePopupInvisible);
|
|
|
|
item->Detach(&mCurrentMenu);
|
|
|
|
delete item;
|
|
|
|
}
|
|
|
|
item = parent;
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
item = mPanels;
|
|
|
|
while (item) {
|
2007-08-03 14:05:07 +00:00
|
|
|
nsMenuChainItem* parent = item->GetParent();
|
|
|
|
if (item->Content()->GetOwnerDoc() == aDocument) {
|
|
|
|
item->Frame()->HidePopup(PR_TRUE, ePopupInvisible);
|
|
|
|
item->Detach(&mPanels);
|
|
|
|
delete item;
|
|
|
|
}
|
|
|
|
item = parent;
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
2007-08-15 14:03:21 +00:00
|
|
|
|
|
|
|
SetCaptureState(nsnull);
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::ExecuteMenu(nsIContent* aMenu, nsEvent* aEvent)
|
|
|
|
{
|
2007-08-30 15:43:10 +00:00
|
|
|
CloseMenuMode cmm = CloseMenuMode_Auto;
|
|
|
|
|
|
|
|
static nsIContent::AttrValuesArray strings[] =
|
|
|
|
{&nsGkAtoms::none, &nsGkAtoms::single, nsnull};
|
|
|
|
|
|
|
|
switch (aMenu->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::closemenu,
|
|
|
|
strings, eCaseMatters)) {
|
|
|
|
case 0:
|
|
|
|
cmm = CloseMenuMode_None;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
cmm = CloseMenuMode_Single;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
// When a menuitem is selected to be executed, first hide all the open
|
|
|
|
// popups, but don't remove them yet. This is needed when a menu command
|
|
|
|
// opens a modal dialog. The views associated with the popups needed to be
|
|
|
|
// hidden and the accesibility events fired before the command executes, but
|
|
|
|
// the popuphiding/popuphidden events are fired afterwards.
|
2007-08-15 14:03:21 +00:00
|
|
|
nsMenuChainItem* item = GetTopVisibleMenu();
|
2007-08-30 15:43:10 +00:00
|
|
|
if (cmm != CloseMenuMode_None) {
|
|
|
|
while (item) {
|
|
|
|
// if it isn't a <menupopup>, don't close it automatically
|
|
|
|
if (!item->IsMenu())
|
|
|
|
break;
|
|
|
|
nsMenuChainItem* next = item->GetParent();
|
|
|
|
item->Frame()->HidePopup(cmm == CloseMenuMode_Auto, ePopupInvisible);
|
|
|
|
if (cmm == CloseMenuMode_Single) // only close one level of menu
|
|
|
|
break;
|
|
|
|
item = next;
|
|
|
|
}
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
|
2007-08-15 14:03:21 +00:00
|
|
|
SetCaptureState(nsnull);
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
// Create a trusted event if the triggering event was trusted, or if
|
|
|
|
// we're called from chrome code (since at least one of our caller
|
|
|
|
// passes in a null event).
|
|
|
|
PRBool isTrusted = aEvent ? NS_IS_TRUSTED_EVENT(aEvent) :
|
|
|
|
nsContentUtils::IsCallerChrome();
|
|
|
|
|
|
|
|
PRBool shift = PR_FALSE, control = PR_FALSE, alt = PR_FALSE, meta = PR_FALSE;
|
|
|
|
if (aEvent && (aEvent->eventStructType == NS_MOUSE_EVENT ||
|
|
|
|
aEvent->eventStructType == NS_KEY_EVENT ||
|
|
|
|
aEvent->eventStructType == NS_ACCESSIBLE_EVENT)) {
|
2007-07-08 07:08:04 +00:00
|
|
|
shift = static_cast<nsInputEvent *>(aEvent)->isShift;
|
|
|
|
control = static_cast<nsInputEvent *>(aEvent)->isControl;
|
|
|
|
alt = static_cast<nsInputEvent *>(aEvent)->isAlt;
|
|
|
|
meta = static_cast<nsInputEvent *>(aEvent)->isMeta;
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
|
2007-07-24 00:55:52 +00:00
|
|
|
// Because the command event is firing asynchronously, a flag is needed to
|
|
|
|
// indicate whether user input is being handled. This ensures that a popup
|
|
|
|
// window won't get blocked.
|
|
|
|
PRBool userinput = nsEventStateManager::IsHandlingUserInput();
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
nsCOMPtr<nsIRunnable> event =
|
2007-08-30 15:43:10 +00:00
|
|
|
new nsXULMenuCommandEvent(aMenu, isTrusted, shift, control,
|
|
|
|
alt, meta, userinput, cmm);
|
2007-07-04 15:49:38 +00:00
|
|
|
NS_DispatchToCurrentThread(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup,
|
|
|
|
nsIContent* aMenu,
|
|
|
|
nsPresContext* aPresContext,
|
2007-08-16 01:09:58 +00:00
|
|
|
nsPopupType aPopupType,
|
2007-07-04 15:49:38 +00:00
|
|
|
PRBool aIsContextMenu,
|
|
|
|
PRBool aSelectFirstItem)
|
|
|
|
{
|
|
|
|
nsCOMPtr<nsIPresShell> presShell = aPresContext->PresShell();
|
|
|
|
|
|
|
|
// set the open attribute on the menu first so that templates will generate
|
|
|
|
// their content before the popupshowing event fires.
|
|
|
|
if (aMenu)
|
|
|
|
aMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::open,
|
|
|
|
NS_LITERAL_STRING("true"), PR_TRUE);
|
|
|
|
|
|
|
|
// XXXndeakin (bug 383930)
|
|
|
|
// eventually, the popup events will be a different event type with
|
|
|
|
// additional fields for the anchor node and position and so forth. This
|
|
|
|
// is where those details would be retrieved. This removes the need for
|
|
|
|
// all the globals people keep adding to nsIDOMXULDocument.
|
|
|
|
nsEventStatus status = nsEventStatus_eIgnore;
|
2007-07-17 12:21:53 +00:00
|
|
|
nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_SHOWING, nsnull, nsMouseEvent::eReal);
|
|
|
|
nsPoint pnt;
|
|
|
|
event.widget = presShell->GetRootFrame()->
|
|
|
|
GetClosestView()->GetNearestWidget(&pnt);
|
|
|
|
event.refPoint = mCachedMousePoint;
|
2007-07-04 15:49:38 +00:00
|
|
|
nsEventDispatcher::Dispatch(aPopup, aPresContext, &event, nsnull, &status);
|
2007-07-17 12:21:53 +00:00
|
|
|
mCachedMousePoint = nsPoint(0, 0);
|
2007-07-04 15:49:38 +00:00
|
|
|
|
2007-08-16 01:09:58 +00:00
|
|
|
// if a panel, blur whatever has focus so that the panel can take the focus.
|
|
|
|
// This is done after the popupshowing event in case that event is cancelled.
|
|
|
|
// Using noautofocus="true" will disable this behaviour, which is needed for
|
|
|
|
// the autocomplete widget as it manages focus itself.
|
|
|
|
if (aPopupType == ePopupTypePanel &&
|
|
|
|
!aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
|
|
|
|
nsGkAtoms::_true, eCaseMatters)) {
|
|
|
|
nsIEventStateManager* esm = presShell->GetPresContext()->EventStateManager();
|
|
|
|
|
|
|
|
// Only remove the focus if the currently focused item is ouside the
|
|
|
|
// popup. It isn't a big deal if the current focus is in a child popup
|
|
|
|
// inside the popup as that shouldn't be visible. This check ensures that
|
|
|
|
// a node inside the popup that is focused during a popupshowing event
|
|
|
|
// remains focused.
|
|
|
|
nsCOMPtr<nsIContent> currentFocus;
|
|
|
|
esm->GetFocusedContent(getter_AddRefs(currentFocus));
|
|
|
|
if (currentFocus &&
|
|
|
|
!nsContentUtils::ContentIsDescendantOf(currentFocus, aPopup)) {
|
|
|
|
esm->SetContentState(nsnull, NS_EVENT_STATE_FOCUS);
|
|
|
|
esm->SetFocusedContent(nsnull);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
// it is common to append content to the menu during the popupshowing event.
|
|
|
|
// Flush the notifications so that the frames are up to date before showing
|
|
|
|
// the popup, otherwise the new frames will reflow after the popup appears,
|
|
|
|
// causing the popup to flicker. Frame code always calls this asynchronously,
|
|
|
|
// so this should be safe.
|
|
|
|
nsIDocument *document = aPopup->GetCurrentDoc();
|
|
|
|
if (document)
|
|
|
|
document->FlushPendingNotifications(Flush_Layout);
|
|
|
|
|
|
|
|
// get the frame again in case it went away
|
|
|
|
nsIFrame* frame = presShell->GetPrimaryFrameFor(aPopup);
|
|
|
|
if (frame && frame->GetType() == nsGkAtoms::menuPopupFrame) {
|
2007-07-08 07:08:04 +00:00
|
|
|
nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame *>(frame);
|
2007-07-04 15:49:38 +00:00
|
|
|
|
2007-08-03 14:05:07 +00:00
|
|
|
// if the event was cancelled, don't open the popup, and reset it's
|
|
|
|
// state back to closed
|
|
|
|
if (status == nsEventStatus_eConsumeNoDefault) {
|
|
|
|
popupFrame->SetPopupState(ePopupClosed);
|
|
|
|
}
|
|
|
|
else {
|
2007-07-04 15:49:38 +00:00
|
|
|
ShowPopupCallback(aPopup, popupFrame, aIsContextMenu, aSelectFirstItem);
|
2007-08-03 14:05:07 +00:00
|
|
|
}
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup,
|
|
|
|
nsIContent* aNextPopup,
|
|
|
|
nsIContent* aLastPopup,
|
|
|
|
nsPresContext *aPresContext,
|
2007-08-16 01:09:58 +00:00
|
|
|
nsPopupType aPopupType,
|
2007-07-04 15:49:38 +00:00
|
|
|
PRBool aDeselectMenu)
|
|
|
|
{
|
|
|
|
nsCOMPtr<nsIPresShell> presShell = aPresContext->PresShell();
|
|
|
|
|
|
|
|
nsEventStatus status = nsEventStatus_eIgnore;
|
2007-07-17 12:21:53 +00:00
|
|
|
nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_HIDING, nsnull, nsMouseEvent::eReal);
|
2007-07-04 15:49:38 +00:00
|
|
|
nsEventDispatcher::Dispatch(aPopup, aPresContext, &event, nsnull, &status);
|
|
|
|
|
2007-08-16 01:09:58 +00:00
|
|
|
// when a panel is closed, blur whatever has focus inside the popup
|
|
|
|
if (aPopupType == ePopupTypePanel &&
|
|
|
|
!aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
|
|
|
|
nsGkAtoms::_true, eCaseMatters)) {
|
|
|
|
nsIEventStateManager* esm = presShell->GetPresContext()->EventStateManager();
|
|
|
|
|
|
|
|
// Remove the focus from the focused node only if it is inside the popup.
|
|
|
|
nsCOMPtr<nsIContent> currentFocus;
|
|
|
|
esm->GetFocusedContent(getter_AddRefs(currentFocus));
|
|
|
|
if (currentFocus &&
|
|
|
|
nsContentUtils::ContentIsDescendantOf(currentFocus, aPopup)) {
|
|
|
|
esm->SetContentState(nsnull, NS_EVENT_STATE_FOCUS);
|
|
|
|
esm->SetFocusedContent(nsnull);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
// get frame again in case it went away
|
|
|
|
nsIFrame* frame = presShell->GetPrimaryFrameFor(aPopup);
|
|
|
|
if (frame && frame->GetType() == nsGkAtoms::menuPopupFrame) {
|
2007-07-08 07:08:04 +00:00
|
|
|
nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame *>(frame);
|
2007-07-04 15:49:38 +00:00
|
|
|
|
2007-08-03 14:05:07 +00:00
|
|
|
// if the event was cancelled, don't hide the popup, and reset it's
|
|
|
|
// state back to open. Only popups in chrome shells can prevent a popup
|
|
|
|
// from hiding.
|
|
|
|
if (status == nsEventStatus_eConsumeNoDefault &&
|
|
|
|
!popupFrame->IsInContentShell()) {
|
|
|
|
popupFrame->SetPopupState(ePopupOpenAndVisible);
|
|
|
|
}
|
|
|
|
else {
|
2007-07-04 15:49:38 +00:00
|
|
|
HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup,
|
2007-08-16 01:09:58 +00:00
|
|
|
aPopupType, aDeselectMenu);
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-07-11 17:23:30 +00:00
|
|
|
PRBool
|
|
|
|
nsXULPopupManager::IsPopupOpen(nsIContent* aPopup)
|
|
|
|
{
|
2007-08-03 14:05:07 +00:00
|
|
|
// a popup is open if it is in the open list. The assertions ensure that the
|
|
|
|
// frame is in the correct state. If the popup is in the hiding or invisible
|
|
|
|
// state, it will still be in the open popup list until it is closed.
|
2007-07-11 17:23:30 +00:00
|
|
|
nsMenuChainItem* item = mCurrentMenu;
|
|
|
|
while (item) {
|
2007-08-03 14:05:07 +00:00
|
|
|
if (item->Content() == aPopup) {
|
|
|
|
NS_ASSERTION(item->Frame()->IsOpen() ||
|
|
|
|
item->Frame()->PopupState() == ePopupHiding ||
|
|
|
|
item->Frame()->PopupState() == ePopupInvisible,
|
2007-08-30 15:43:10 +00:00
|
|
|
"popup in open list not actually open");
|
2007-07-11 17:23:30 +00:00
|
|
|
return PR_TRUE;
|
2007-08-03 14:05:07 +00:00
|
|
|
}
|
2007-07-11 17:23:30 +00:00
|
|
|
item = item->GetParent();
|
|
|
|
}
|
|
|
|
|
|
|
|
item = mPanels;
|
|
|
|
while (item) {
|
2007-08-03 14:05:07 +00:00
|
|
|
if (item->Content() == aPopup) {
|
|
|
|
NS_ASSERTION(item->Frame()->IsOpen() ||
|
|
|
|
item->Frame()->PopupState() == ePopupHiding ||
|
|
|
|
item->Frame()->PopupState() == ePopupInvisible,
|
2007-08-30 15:43:10 +00:00
|
|
|
"popup in open list not actually open");
|
2007-07-11 17:23:30 +00:00
|
|
|
return PR_TRUE;
|
2007-08-03 14:05:07 +00:00
|
|
|
}
|
2007-07-11 17:23:30 +00:00
|
|
|
item = item->GetParent();
|
|
|
|
}
|
|
|
|
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
PRBool
|
|
|
|
nsXULPopupManager::IsPopupOpenForMenuParent(nsIMenuParent* aMenuParent)
|
|
|
|
{
|
2007-08-15 14:03:21 +00:00
|
|
|
nsMenuChainItem* item = GetTopVisibleMenu();
|
2007-07-04 15:49:38 +00:00
|
|
|
while (item) {
|
2007-08-03 14:05:07 +00:00
|
|
|
nsMenuPopupFrame* popup = item->Frame();
|
|
|
|
if (popup && popup->IsOpen()) {
|
|
|
|
nsIFrame* parent = popup->GetParent();
|
|
|
|
if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
|
|
|
|
nsMenuFrame* menuFrame = static_cast<nsMenuFrame *>(parent);
|
|
|
|
if (menuFrame->GetMenuParent() == aMenuParent)
|
|
|
|
return PR_TRUE;
|
|
|
|
}
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
item = item->GetParent();
|
|
|
|
}
|
|
|
|
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
|
2007-08-16 01:09:58 +00:00
|
|
|
nsIFrame*
|
|
|
|
nsXULPopupManager::GetTopPopup(nsPopupType aType)
|
|
|
|
{
|
|
|
|
if (aType == ePopupTypePanel && mPanels)
|
|
|
|
return mPanels->Frame();
|
|
|
|
|
|
|
|
nsMenuChainItem* item = GetTopVisibleMenu();
|
|
|
|
while (item) {
|
2007-09-18 15:00:43 +00:00
|
|
|
if (item->PopupType() == aType || aType == ePopupTypeAny)
|
2007-08-16 01:09:58 +00:00
|
|
|
return item->Frame();
|
|
|
|
item = item->GetParent();
|
|
|
|
}
|
|
|
|
|
|
|
|
return nsnull;
|
|
|
|
}
|
|
|
|
|
2007-07-16 14:53:32 +00:00
|
|
|
nsTArray<nsIFrame *>
|
|
|
|
nsXULPopupManager::GetOpenPopups()
|
|
|
|
{
|
|
|
|
nsTArray<nsIFrame *> popups;
|
|
|
|
|
|
|
|
nsMenuChainItem* item = mCurrentMenu;
|
|
|
|
while (item) {
|
2007-08-15 14:03:21 +00:00
|
|
|
if (item->Frame()->PopupState() != ePopupInvisible)
|
|
|
|
popups.AppendElement(static_cast<nsIFrame*>(item->Frame()));
|
2007-07-16 14:53:32 +00:00
|
|
|
item = item->GetParent();
|
|
|
|
}
|
|
|
|
|
|
|
|
return popups;
|
|
|
|
}
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
PRBool
|
|
|
|
nsXULPopupManager::MayShowPopup(nsMenuPopupFrame* aPopup)
|
|
|
|
{
|
2007-08-03 14:05:07 +00:00
|
|
|
// if a popup's IsOpen method returns true, then the popup must always be in
|
|
|
|
// the popup chain scanned in IsPopupOpen.
|
|
|
|
NS_ASSERTION(!aPopup->IsOpen() || IsPopupOpen(aPopup->GetContent()),
|
|
|
|
"popup frame state doesn't match XULPopupManager open state");
|
|
|
|
|
|
|
|
nsPopupState state = aPopup->PopupState();
|
|
|
|
|
|
|
|
// if the popup is not in the open popup chain, then it must have a state that
|
|
|
|
// is either closed, in the process of being shown, or invisible.
|
|
|
|
NS_ASSERTION(IsPopupOpen(aPopup->GetContent()) || state == ePopupClosed ||
|
|
|
|
state == ePopupShowing || state == ePopupInvisible,
|
|
|
|
"popup not in XULPopupManager open list is open");
|
|
|
|
|
|
|
|
// don't show popups unless they are closed or invisible
|
|
|
|
if (state != ePopupClosed && state != ePopupInvisible)
|
2007-07-04 15:49:38 +00:00
|
|
|
return PR_FALSE;
|
|
|
|
|
|
|
|
nsCOMPtr<nsISupports> cont = aPopup->PresContext()->GetContainer();
|
|
|
|
nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(cont);
|
|
|
|
if (!dsti)
|
|
|
|
return PR_FALSE;
|
|
|
|
|
|
|
|
// chrome shells can always open popups, but other types of shells can only
|
|
|
|
// open popups when they are focused
|
|
|
|
PRInt32 type = -1;
|
|
|
|
if (NS_FAILED(dsti->GetItemType(&type)))
|
|
|
|
return PR_FALSE;
|
|
|
|
|
|
|
|
if (type != nsIDocShellTreeItem::typeChrome) {
|
|
|
|
nsCOMPtr<nsPIDOMWindow> win = do_GetInterface(dsti);
|
|
|
|
if (!win)
|
|
|
|
return PR_FALSE;
|
|
|
|
|
|
|
|
// only allow popups in active windows
|
|
|
|
PRBool active;
|
|
|
|
nsIFocusController* focusController = win->GetRootFocusController();
|
|
|
|
focusController->GetActive(&active);
|
|
|
|
if (!active)
|
|
|
|
return PR_FALSE;
|
|
|
|
|
|
|
|
nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(dsti);
|
|
|
|
if (!baseWin)
|
|
|
|
return PR_FALSE;
|
|
|
|
|
|
|
|
// only allow popups in visible frames
|
|
|
|
PRBool visible;
|
|
|
|
baseWin->GetVisibility(&visible);
|
|
|
|
if (!visible)
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// cannot open a popup that is a submenu of a menupopup that isn't open.
|
|
|
|
nsIFrame* parent = aPopup->GetParent();
|
|
|
|
if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
|
2007-07-08 07:08:04 +00:00
|
|
|
nsMenuFrame* menuFrame = static_cast<nsMenuFrame *>(parent);
|
2007-07-04 15:49:38 +00:00
|
|
|
nsIMenuParent* parentPopup = menuFrame->GetMenuParent();
|
|
|
|
if (parentPopup && !parentPopup->IsOpen())
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return PR_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame* aPopup)
|
|
|
|
{
|
|
|
|
// when a popup frame is destroyed, just unhook it from the list of popups
|
|
|
|
if (mTimerMenu == aPopup) {
|
|
|
|
if (mCloseTimer) {
|
|
|
|
mCloseTimer->Cancel();
|
|
|
|
mCloseTimer = nsnull;
|
|
|
|
}
|
|
|
|
mTimerMenu = nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsMenuChainItem* item = mPanels;
|
|
|
|
while (item) {
|
|
|
|
if (item->Frame() == aPopup) {
|
|
|
|
item->Detach(&mPanels);
|
|
|
|
delete item;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
item = item->GetParent();
|
|
|
|
}
|
|
|
|
|
|
|
|
nsCOMPtr<nsIContent> oldMenu;
|
|
|
|
if (mCurrentMenu)
|
|
|
|
oldMenu = mCurrentMenu->Content();
|
|
|
|
|
|
|
|
nsMenuChainItem* menuToDestroy = nsnull;
|
|
|
|
item = mCurrentMenu;
|
|
|
|
while (item) {
|
|
|
|
if (item->Frame() == aPopup) {
|
|
|
|
item->Detach(&mCurrentMenu);
|
|
|
|
menuToDestroy = item;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
item = item->GetParent();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (menuToDestroy) {
|
|
|
|
// menuToDestroy will be set to the item to delete. Iterate through any
|
|
|
|
// child menus and destroy them as well, since the parent is going away
|
|
|
|
nsIFrame* menuToDestroyFrame = menuToDestroy->Frame();
|
|
|
|
item = menuToDestroy->GetChild();
|
|
|
|
while (item) {
|
|
|
|
nsMenuChainItem* next = item->GetChild();
|
|
|
|
|
|
|
|
// if the popup is a child frame of the menu that was destroyed, unhook
|
|
|
|
// it from the list of open menus and inform the popup frame that it
|
|
|
|
// should be hidden. Don't bother with the events since the frames are
|
|
|
|
// going away. If the child menu is not a child frame, for example, a
|
|
|
|
// context menu, use HidePopup instead
|
|
|
|
if (nsLayoutUtils::IsProperAncestorFrame(menuToDestroyFrame, item->Frame())) {
|
|
|
|
item->Detach(&mCurrentMenu);
|
2007-08-03 14:05:07 +00:00
|
|
|
item->Frame()->HidePopup(PR_FALSE, ePopupInvisible);
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
HidePopup(item->Content(), PR_FALSE, PR_FALSE, PR_TRUE);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
delete item;
|
|
|
|
item = next;
|
|
|
|
}
|
|
|
|
|
|
|
|
delete menuToDestroy;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (oldMenu)
|
|
|
|
SetCaptureState(oldMenu);
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
|
|
|
nsXULPopupManager::HasContextMenu(nsMenuPopupFrame* aPopup)
|
|
|
|
{
|
2007-08-15 14:03:21 +00:00
|
|
|
nsMenuChainItem* item = GetTopVisibleMenu();
|
2007-07-04 15:49:38 +00:00
|
|
|
while (item && item->Frame() != aPopup) {
|
|
|
|
if (item->IsContextMenu())
|
|
|
|
return PR_TRUE;
|
|
|
|
item = item->GetParent();
|
|
|
|
}
|
|
|
|
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::SetCaptureState(nsIContent* aOldPopup)
|
|
|
|
{
|
2007-08-15 14:03:21 +00:00
|
|
|
nsMenuChainItem* item = GetTopVisibleMenu();
|
|
|
|
if (item && aOldPopup == item->Content())
|
2007-07-04 15:49:38 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
if (mWidget) {
|
|
|
|
mWidget->CaptureRollupEvents(this, PR_FALSE, PR_FALSE);
|
|
|
|
mWidget = nsnull;
|
|
|
|
}
|
|
|
|
|
2007-08-15 14:03:21 +00:00
|
|
|
if (item) {
|
|
|
|
nsMenuPopupFrame* popup = item->Frame();
|
2007-07-04 15:49:38 +00:00
|
|
|
nsCOMPtr<nsIWidget> widget;
|
|
|
|
popup->GetWidget(getter_AddRefs(widget));
|
|
|
|
if (widget) {
|
|
|
|
widget->CaptureRollupEvents(this, PR_TRUE, popup->ConsumeOutsideClicks());
|
|
|
|
mWidget = widget;
|
|
|
|
popup->AttachedDismissalListener();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UpdateKeyboardListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::UpdateKeyboardListeners()
|
|
|
|
{
|
|
|
|
nsCOMPtr<nsIDOMEventTarget> newTarget;
|
2007-08-15 14:03:21 +00:00
|
|
|
nsMenuChainItem* item = GetTopVisibleMenu();
|
|
|
|
if (item) {
|
|
|
|
if (!item->IgnoreKeys())
|
|
|
|
newTarget = do_QueryInterface(item->Content()->GetDocument());
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
else if (mActiveMenuBar) {
|
|
|
|
newTarget = do_QueryInterface(mActiveMenuBar->GetContent()->GetDocument());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mKeyListener != newTarget) {
|
|
|
|
if (mKeyListener) {
|
|
|
|
mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, PR_TRUE);
|
|
|
|
mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, PR_TRUE);
|
|
|
|
mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, PR_TRUE);
|
|
|
|
mKeyListener = nsnull;
|
|
|
|
nsContentUtils::NotifyInstalledMenuKeyboardListener(PR_FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newTarget) {
|
|
|
|
newTarget->AddEventListener(NS_LITERAL_STRING("keypress"), this, PR_TRUE);
|
|
|
|
newTarget->AddEventListener(NS_LITERAL_STRING("keydown"), this, PR_TRUE);
|
|
|
|
newTarget->AddEventListener(NS_LITERAL_STRING("keyup"), this, PR_TRUE);
|
|
|
|
nsContentUtils::NotifyInstalledMenuKeyboardListener(PR_TRUE);
|
|
|
|
mKeyListener = newTarget;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::UpdateMenuItems(nsIContent* aPopup)
|
|
|
|
{
|
|
|
|
// Walk all of the menu's children, checking to see if any of them has a
|
|
|
|
// command attribute. If so, then several attributes must potentially be updated.
|
|
|
|
|
|
|
|
nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(aPopup->GetDocument()));
|
|
|
|
PRUint32 count = aPopup->GetChildCount();
|
|
|
|
for (PRUint32 i = 0; i < count; i++) {
|
|
|
|
nsCOMPtr<nsIContent> grandChild = aPopup->GetChildAt(i);
|
|
|
|
|
|
|
|
if (grandChild->NodeInfo()->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL)) {
|
|
|
|
// See if we have a command attribute.
|
|
|
|
nsAutoString command;
|
|
|
|
grandChild->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
|
|
|
|
if (!command.IsEmpty()) {
|
|
|
|
// We do! Look it up in our document
|
|
|
|
nsCOMPtr<nsIDOMElement> commandElt;
|
|
|
|
domDoc->GetElementById(command, getter_AddRefs(commandElt));
|
|
|
|
nsCOMPtr<nsIContent> commandContent(do_QueryInterface(commandElt));
|
|
|
|
if (commandContent) {
|
|
|
|
nsAutoString commandValue;
|
|
|
|
// The menu's disabled state needs to be updated to match the command.
|
|
|
|
if (commandContent->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue))
|
|
|
|
grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue, PR_TRUE);
|
|
|
|
else
|
|
|
|
grandChild->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, PR_TRUE);
|
|
|
|
|
|
|
|
// The menu's label, accesskey and checked states need to be updated
|
|
|
|
// to match the command. Note that unlike the disabled state if the
|
|
|
|
// command has *no* value, we assume the menu is supplying its own.
|
|
|
|
if (commandContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue))
|
|
|
|
grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue, PR_TRUE);
|
|
|
|
|
|
|
|
if (commandContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue))
|
|
|
|
grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue, PR_TRUE);
|
|
|
|
|
|
|
|
if (commandContent->GetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue))
|
|
|
|
grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue, PR_TRUE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify
|
|
|
|
//
|
|
|
|
// The item selection timer has fired, we might have to readjust the
|
|
|
|
// selected item. There are two cases here that we are trying to deal with:
|
|
|
|
// (1) diagonal movement from a parent menu to a submenu passing briefly over
|
|
|
|
// other items, and
|
|
|
|
// (2) moving out from a submenu to a parent or grandparent menu.
|
|
|
|
// In both cases, |mTimerMenu| is the menu item that might have an open submenu and
|
|
|
|
// |mCurrentMenu| is the item the mouse is currently over, which could be none of them.
|
|
|
|
//
|
|
|
|
// case (1):
|
|
|
|
// As the mouse moves from the parent item of a submenu (we'll call 'A') diagonally into the
|
|
|
|
// submenu, it probably passes through one or more sibilings (B). As the mouse passes
|
|
|
|
// through B, it becomes the current menu item and the timer is set and mTimerMenu is
|
|
|
|
// set to A. Before the timer fires, the mouse leaves the menu containing A and B and
|
|
|
|
// enters the submenus. Now when the timer fires, |mCurrentMenu| is null (!= |mTimerMenu|)
|
|
|
|
// so we have to see if anything in A's children is selected (recall that even disabled
|
|
|
|
// items are selected, the style just doesn't show it). If that is the case, we need to
|
|
|
|
// set the selected item back to A.
|
|
|
|
//
|
|
|
|
// case (2);
|
|
|
|
// Item A has an open submenu, and in it there is an item (B) which also has an open
|
|
|
|
// submenu (so there are 3 menus displayed right now). The mouse then leaves B's child
|
|
|
|
// submenu and selects an item that is a sibling of A, call it C. When the mouse enters C,
|
|
|
|
// the timer is set and |mTimerMenu| is A and |mCurrentMenu| is C. As the timer fires,
|
|
|
|
// the mouse is still within C. The correct behavior is to set the current item to C
|
|
|
|
// and close up the chain parented at A.
|
|
|
|
//
|
|
|
|
// This brings up the question of is the logic of case (1) enough? The answer is no,
|
|
|
|
// and is discussed in bugzilla bug 29400. Case (1) asks if A's submenu has a selected
|
|
|
|
// child, and if it does, set the selected item to A. Because B has a submenu open, it
|
|
|
|
// is selected and as a result, A is set to be the selected item even though the mouse
|
|
|
|
// rests in C -- very wrong.
|
|
|
|
//
|
|
|
|
// The solution is to use the same idea, but instead of only checking one level,
|
|
|
|
// drill all the way down to the deepest open submenu and check if it has something
|
|
|
|
// selected. Since the mouse is in a grandparent, it won't, and we know that we can
|
|
|
|
// safely close up A and all its children.
|
|
|
|
//
|
|
|
|
// The code below melds the two cases together.
|
|
|
|
//
|
|
|
|
nsresult
|
|
|
|
nsXULPopupManager::Notify(nsITimer* aTimer)
|
|
|
|
{
|
|
|
|
if (aTimer == mCloseTimer)
|
|
|
|
KillMenuTimer();
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
nsXULPopupManager::KillMenuTimer()
|
|
|
|
{
|
|
|
|
if (mCloseTimer && mTimerMenu) {
|
|
|
|
mCloseTimer->Cancel();
|
|
|
|
mCloseTimer = nsnull;
|
|
|
|
|
|
|
|
if (mTimerMenu->IsOpen())
|
|
|
|
HidePopup(mTimerMenu->GetContent(), PR_FALSE, PR_FALSE, PR_TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
mTimerMenu = nsnull;
|
|
|
|
}
|
|
|
|
|
2007-07-11 12:05:40 +00:00
|
|
|
void
|
|
|
|
nsXULPopupManager::CancelMenuTimer(nsIMenuParent* aMenuParent)
|
|
|
|
{
|
|
|
|
if (mCloseTimer && mTimerMenu == aMenuParent) {
|
|
|
|
mCloseTimer->Cancel();
|
|
|
|
mCloseTimer = nsnull;
|
|
|
|
mTimerMenu = nsnull;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
PRBool
|
2007-07-23 17:08:10 +00:00
|
|
|
nsXULPopupManager::HandleShortcutNavigation(nsIDOMKeyEvent* aKeyEvent,
|
|
|
|
nsMenuPopupFrame* aFrame)
|
2007-07-04 15:49:38 +00:00
|
|
|
{
|
2007-08-15 14:03:21 +00:00
|
|
|
nsMenuChainItem* item = GetTopVisibleMenu();
|
|
|
|
if (!aFrame && item)
|
|
|
|
aFrame = item->Frame();
|
2007-07-04 15:49:38 +00:00
|
|
|
|
2007-07-23 17:08:10 +00:00
|
|
|
if (aFrame) {
|
2007-07-04 15:49:38 +00:00
|
|
|
PRBool action;
|
2007-07-23 17:08:10 +00:00
|
|
|
nsMenuFrame* result = aFrame->FindMenuWithShortcut(aKeyEvent, action);
|
2007-07-04 15:49:38 +00:00
|
|
|
if (result) {
|
2007-07-23 17:08:10 +00:00
|
|
|
aFrame->ChangeMenuItem(result, PR_FALSE);
|
2007-07-07 08:40:54 +00:00
|
|
|
if (action) {
|
|
|
|
nsMenuFrame* menuToOpen = result->Enter();
|
|
|
|
if (menuToOpen) {
|
|
|
|
nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
|
|
|
|
ShowMenu(content, PR_TRUE, PR_FALSE);
|
|
|
|
}
|
|
|
|
}
|
2007-07-04 15:49:38 +00:00
|
|
|
return PR_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mActiveMenuBar) {
|
|
|
|
nsMenuFrame* result = mActiveMenuBar->FindMenuWithShortcut(aKeyEvent);
|
|
|
|
if (result) {
|
|
|
|
mActiveMenuBar->SetActive(PR_TRUE);
|
|
|
|
result->OpenMenu(PR_TRUE);
|
|
|
|
return PR_TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PRBool
|
|
|
|
nsXULPopupManager::HandleKeyboardNavigation(PRUint32 aKeyCode)
|
|
|
|
{
|
|
|
|
// navigate up through the open menus, looking for the topmost one
|
|
|
|
// in the same hierarchy
|
|
|
|
nsMenuChainItem* item = nsnull;
|
2007-08-15 14:03:21 +00:00
|
|
|
nsMenuChainItem* nextitem = GetTopVisibleMenu();
|
2007-07-04 15:49:38 +00:00
|
|
|
|
|
|
|
while (nextitem) {
|
|
|
|
item = nextitem;
|
|
|
|
nextitem = item->GetParent();
|
|
|
|
|
|
|
|
if (nextitem) {
|
|
|
|
// stop if the parent isn't a menu
|
|
|
|
if (!nextitem->IsMenu())
|
|
|
|
break;
|
|
|
|
|
|
|
|
// check to make sure that the parent is actually the parent menu. It won't
|
|
|
|
// be if the parent is in a different frame hierarchy, for example, for a
|
|
|
|
// context menu opened on another menu.
|
2007-07-08 07:08:04 +00:00
|
|
|
nsIMenuParent* expectedParent = static_cast<nsIMenuParent *>(nextitem->Frame());
|
2007-07-04 15:49:38 +00:00
|
|
|
nsIFrame* parent = item->Frame()->GetParent();
|
|
|
|
if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
|
2007-07-08 07:08:04 +00:00
|
|
|
nsMenuFrame* menuFrame = static_cast<nsMenuFrame *>(parent);
|
2007-07-04 15:49:38 +00:00
|
|
|
if (menuFrame->GetMenuParent() != expectedParent)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nsIFrame* itemFrame;
|
|
|
|
if (item)
|
|
|
|
itemFrame = item->Frame();
|
|
|
|
else if (mActiveMenuBar)
|
|
|
|
itemFrame = mActiveMenuBar;
|
|
|
|
else
|
|
|
|
return PR_FALSE;
|
|
|
|
|
|
|
|
nsNavigationDirection theDirection;
|
|
|
|
NS_DIRECTION_FROM_KEY_CODE(itemFrame, theDirection, aKeyCode);
|
|
|
|
|
|
|
|
// if a popup is open, first check for navigation within the popup
|
|
|
|
if (item && HandleKeyboardNavigationInPopup(item, theDirection))
|
|
|
|
return PR_TRUE;
|
|
|
|
|
|
|
|
// no popup handled the key, so check the active menubar, if any
|
|
|
|
if (mActiveMenuBar) {
|
|
|
|
nsMenuFrame* currentMenu = mActiveMenuBar->GetCurrentMenuItem();
|
|
|
|
|
|
|
|
if (NS_DIRECTION_IS_INLINE(theDirection)) {
|
|
|
|
nsMenuFrame* nextItem = (theDirection == eNavigationDirection_End) ?
|
|
|
|
GetNextMenuItem(mActiveMenuBar, currentMenu, PR_FALSE) :
|
|
|
|
GetPreviousMenuItem(mActiveMenuBar, currentMenu, PR_FALSE);
|
|
|
|
mActiveMenuBar->ChangeMenuItem(nextItem, PR_TRUE);
|
|
|
|
return PR_TRUE;
|
|
|
|
}
|
|
|
|
else if NS_DIRECTION_IS_BLOCK(theDirection) {
|
|
|
|
// Open the menu and select its first item.
|
|
|
|
nsCOMPtr<nsIContent> content = currentMenu->GetContent();
|
|
|
|
ShowMenu(content, PR_TRUE, PR_FALSE);
|
|
|
|
return PR_TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
|
|
|
nsXULPopupManager::HandleKeyboardNavigationInPopup(nsMenuChainItem* item,
|
2007-07-23 17:08:10 +00:00
|
|
|
nsMenuPopupFrame* aFrame,
|
2007-07-04 15:49:38 +00:00
|
|
|
nsNavigationDirection aDir)
|
|
|
|
{
|
2007-07-23 17:08:10 +00:00
|
|
|
NS_ASSERTION(aFrame, "aFrame is null");
|
|
|
|
NS_ASSERTION(!item || item->Frame() == aFrame,
|
|
|
|
"aFrame is expected to be equal to item->Frame()");
|
|
|
|
|
|
|
|
nsMenuFrame* currentMenu = aFrame->GetCurrentMenuItem();
|
2007-07-04 15:49:38 +00:00
|
|
|
|
2007-07-23 17:08:10 +00:00
|
|
|
aFrame->ClearIncrementalString();
|
2007-07-04 15:49:38 +00:00
|
|
|
|
|
|
|
// This method only gets called if we're open.
|
|
|
|
if (!currentMenu && NS_DIRECTION_IS_INLINE(aDir)) {
|
|
|
|
// We've been opened, but we haven't had anything selected.
|
|
|
|
// We can handle End, but our parent handles Start.
|
|
|
|
if (aDir == eNavigationDirection_End) {
|
2007-07-23 17:08:10 +00:00
|
|
|
nsMenuFrame* nextItem = GetNextMenuItem(aFrame, nsnull, PR_TRUE);
|
2007-07-04 15:49:38 +00:00
|
|
|
if (nextItem) {
|
2007-07-23 17:08:10 +00:00
|
|
|
aFrame->ChangeMenuItem(nextItem, PR_FALSE);
|
2007-07-04 15:49:38 +00:00
|
|
|
return PR_TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool isContainer = PR_FALSE;
|
|
|
|
PRBool isOpen = PR_FALSE;
|
|
|
|
if (currentMenu) {
|
|
|
|
isOpen = currentMenu->IsOpen();
|
|
|
|
isContainer = currentMenu->IsMenu();
|
|
|
|
if (isOpen) {
|
|
|
|
// for an open popup, have the child process the event
|
2007-07-23 17:08:10 +00:00
|
|
|
nsMenuChainItem* child = item ? item->GetChild() : nsnull;
|
2007-07-04 15:49:38 +00:00
|
|
|
if (child && HandleKeyboardNavigationInPopup(child, aDir))
|
|
|
|
return PR_TRUE;
|
|
|
|
}
|
|
|
|
else if (aDir == eNavigationDirection_End &&
|
|
|
|
isContainer && !currentMenu->IsDisabled()) {
|
|
|
|
// The menu is not yet open. Open it and select the first item.
|
|
|
|
nsCOMPtr<nsIContent> content = currentMenu->GetContent();
|
|
|
|
ShowMenu(content, PR_TRUE, PR_FALSE);
|
|
|
|
return PR_TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// For block progression, we can move in either direction
|
|
|
|
if (NS_DIRECTION_IS_BLOCK(aDir) ||
|
|
|
|
NS_DIRECTION_IS_BLOCK_TO_EDGE(aDir)) {
|
|
|
|
nsMenuFrame* nextItem;
|
|
|
|
|
|
|
|
if (aDir == eNavigationDirection_Before)
|
2007-07-23 17:08:10 +00:00
|
|
|
nextItem = GetPreviousMenuItem(aFrame, currentMenu, PR_TRUE);
|
2007-07-04 15:49:38 +00:00
|
|
|
else if (aDir == eNavigationDirection_After)
|
2007-07-23 17:08:10 +00:00
|
|
|
nextItem = GetNextMenuItem(aFrame, currentMenu, PR_TRUE);
|
2007-07-04 15:49:38 +00:00
|
|
|
else if (aDir == eNavigationDirection_First)
|
2007-07-23 17:08:10 +00:00
|
|
|
nextItem = GetNextMenuItem(aFrame, nsnull, PR_TRUE);
|
2007-07-04 15:49:38 +00:00
|
|
|
else
|
2007-07-23 17:08:10 +00:00
|
|
|
nextItem = GetPreviousMenuItem(aFrame, nsnull, PR_TRUE);
|
2007-07-04 15:49:38 +00:00
|
|
|
|
|
|
|
if (nextItem) {
|
2007-07-23 17:08:10 +00:00
|
|
|
aFrame->ChangeMenuItem(nextItem, PR_FALSE);
|
2007-07-04 15:49:38 +00:00
|
|
|
return PR_TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (currentMenu && isContainer && isOpen) {
|
|
|
|
if (aDir == eNavigationDirection_Start) {
|
|
|
|
// close a submenu when Left is pressed
|
|
|
|
nsMenuPopupFrame* popupFrame = currentMenu->GetPopup();
|
|
|
|
if (popupFrame)
|
|
|
|
HidePopup(popupFrame->GetContent(), PR_FALSE, PR_FALSE, PR_FALSE);
|
|
|
|
return PR_TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsMenuFrame*
|
|
|
|
nsXULPopupManager::GetNextMenuItem(nsIFrame* aParent,
|
|
|
|
nsMenuFrame* aStart,
|
|
|
|
PRBool aIsPopup)
|
|
|
|
{
|
|
|
|
nsIFrame* immediateParent = nsnull;
|
|
|
|
nsPresContext* presContext = aParent->PresContext();
|
|
|
|
presContext->PresShell()->
|
|
|
|
FrameConstructor()->GetInsertionPoint(aParent, nsnull, &immediateParent);
|
|
|
|
if (!immediateParent)
|
|
|
|
immediateParent = aParent;
|
|
|
|
|
|
|
|
nsIFrame* currFrame = nsnull;
|
|
|
|
if (aStart)
|
|
|
|
currFrame = aStart->GetNextSibling();
|
|
|
|
else
|
|
|
|
currFrame = immediateParent->GetFirstChild(nsnull);
|
|
|
|
|
|
|
|
while (currFrame) {
|
|
|
|
// See if it's a menu item.
|
|
|
|
if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) {
|
|
|
|
return (currFrame->GetType() == nsGkAtoms::menuFrame) ?
|
2007-07-08 07:08:04 +00:00
|
|
|
static_cast<nsMenuFrame *>(currFrame) : nsnull;
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
currFrame = currFrame->GetNextSibling();
|
|
|
|
}
|
|
|
|
|
|
|
|
currFrame = immediateParent->GetFirstChild(nsnull);
|
|
|
|
|
|
|
|
// Still don't have anything. Try cycling from the beginning.
|
|
|
|
while (currFrame && currFrame != aStart) {
|
|
|
|
// See if it's a menu item.
|
|
|
|
if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) {
|
|
|
|
return (currFrame->GetType() == nsGkAtoms::menuFrame) ?
|
2007-07-08 07:08:04 +00:00
|
|
|
static_cast<nsMenuFrame *>(currFrame) : nsnull;
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
currFrame = currFrame->GetNextSibling();
|
|
|
|
}
|
|
|
|
|
|
|
|
// No luck. Just return our start value.
|
|
|
|
return aStart;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsMenuFrame*
|
|
|
|
nsXULPopupManager::GetPreviousMenuItem(nsIFrame* aParent,
|
|
|
|
nsMenuFrame* aStart,
|
|
|
|
PRBool aIsPopup)
|
|
|
|
{
|
|
|
|
nsIFrame* immediateParent = nsnull;
|
|
|
|
nsPresContext* presContext = aParent->PresContext();
|
|
|
|
presContext->PresShell()->
|
|
|
|
FrameConstructor()->GetInsertionPoint(aParent, nsnull, &immediateParent);
|
|
|
|
if (!immediateParent)
|
|
|
|
immediateParent = aParent;
|
|
|
|
|
|
|
|
nsFrameList frames(immediateParent->GetFirstChild(nsnull));
|
|
|
|
|
|
|
|
nsIFrame* currFrame = nsnull;
|
|
|
|
if (aStart)
|
|
|
|
currFrame = frames.GetPrevSiblingFor(aStart);
|
|
|
|
else
|
|
|
|
currFrame = frames.LastChild();
|
|
|
|
|
|
|
|
while (currFrame) {
|
|
|
|
// See if it's a menu item.
|
|
|
|
if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) {
|
|
|
|
return (currFrame->GetType() == nsGkAtoms::menuFrame) ?
|
2007-07-08 07:08:04 +00:00
|
|
|
static_cast<nsMenuFrame *>(currFrame) : nsnull;
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
currFrame = frames.GetPrevSiblingFor(currFrame);
|
|
|
|
}
|
|
|
|
|
|
|
|
currFrame = frames.LastChild();
|
|
|
|
|
|
|
|
// Still don't have anything. Try cycling from the end.
|
|
|
|
while (currFrame && currFrame != aStart) {
|
|
|
|
// See if it's a menu item.
|
|
|
|
if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) {
|
|
|
|
return (currFrame->GetType() == nsGkAtoms::menuFrame) ?
|
2007-07-08 07:08:04 +00:00
|
|
|
static_cast<nsMenuFrame *>(currFrame) : nsnull;
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
currFrame = frames.GetPrevSiblingFor(currFrame);
|
|
|
|
}
|
|
|
|
|
|
|
|
// No luck. Just return our start value.
|
|
|
|
return aStart;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
|
|
|
nsXULPopupManager::IsValidMenuItem(nsPresContext* aPresContext,
|
|
|
|
nsIContent* aContent,
|
|
|
|
PRBool aOnPopup)
|
|
|
|
{
|
|
|
|
PRInt32 ns = aContent->GetNameSpaceID();
|
|
|
|
nsIAtom *tag = aContent->Tag();
|
|
|
|
if (ns == kNameSpaceID_XUL &&
|
|
|
|
tag != nsGkAtoms::menu &&
|
|
|
|
tag != nsGkAtoms::menuitem)
|
|
|
|
return PR_FALSE;
|
|
|
|
|
|
|
|
if (ns == kNameSpaceID_XHTML && (!aOnPopup || tag != nsGkAtoms::option))
|
|
|
|
return PR_FALSE;
|
|
|
|
|
|
|
|
PRBool skipNavigatingDisabledMenuItem = PR_TRUE;
|
|
|
|
if (aOnPopup) {
|
|
|
|
aPresContext->LookAndFeel()->
|
|
|
|
GetMetric(nsILookAndFeel::eMetric_SkipNavigatingDisabledMenuItem,
|
|
|
|
skipNavigatingDisabledMenuItem);
|
|
|
|
}
|
|
|
|
|
|
|
|
return !(skipNavigatingDisabledMenuItem &&
|
|
|
|
aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
|
|
|
|
nsGkAtoms::_true, eCaseMatters));
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
nsXULPopupManager::KeyUp(nsIDOMEvent* aKeyEvent)
|
|
|
|
{
|
2007-08-23 21:21:27 +00:00
|
|
|
if (mCurrentMenu) {
|
|
|
|
aKeyEvent->StopPropagation();
|
|
|
|
aKeyEvent->PreventDefault();
|
|
|
|
}
|
2007-07-04 15:49:38 +00:00
|
|
|
|
|
|
|
return NS_OK; // I am consuming event
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
nsXULPopupManager::KeyDown(nsIDOMEvent* aKeyEvent)
|
|
|
|
{
|
2007-08-23 21:21:27 +00:00
|
|
|
// don't do anything if a menu isn't open
|
|
|
|
if (!mCurrentMenu)
|
|
|
|
return NS_OK;
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
PRInt32 menuAccessKey = -1;
|
|
|
|
|
|
|
|
// If the key just pressed is the access key (usually Alt),
|
|
|
|
// dismiss and unfocus the menu.
|
|
|
|
|
|
|
|
nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
|
|
|
|
if (menuAccessKey) {
|
|
|
|
PRUint32 theChar;
|
|
|
|
nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
|
|
|
|
keyEvent->GetKeyCode(&theChar);
|
|
|
|
|
|
|
|
if (theChar == (PRUint32)menuAccessKey) {
|
|
|
|
PRBool ctrl = PR_FALSE;
|
|
|
|
if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_CONTROL)
|
|
|
|
keyEvent->GetCtrlKey(&ctrl);
|
|
|
|
PRBool alt=PR_FALSE;
|
|
|
|
if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_ALT)
|
|
|
|
keyEvent->GetAltKey(&alt);
|
|
|
|
PRBool shift=PR_FALSE;
|
|
|
|
if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_SHIFT)
|
|
|
|
keyEvent->GetShiftKey(&shift);
|
|
|
|
PRBool meta=PR_FALSE;
|
|
|
|
if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_META)
|
|
|
|
keyEvent->GetMetaKey(&meta);
|
|
|
|
if (!(ctrl || alt || shift || meta)) {
|
|
|
|
// The access key just went down and no other
|
|
|
|
// modifiers are already down.
|
|
|
|
Rollup();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Since a menu was open, eat the event to keep other event
|
|
|
|
// listeners from becoming confused.
|
|
|
|
aKeyEvent->StopPropagation();
|
|
|
|
aKeyEvent->PreventDefault();
|
|
|
|
return NS_OK; // I am consuming event
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
|
|
nsXULPopupManager::KeyPress(nsIDOMEvent* aKeyEvent)
|
|
|
|
{
|
|
|
|
// Don't check prevent default flag -- menus always get first shot at key events.
|
|
|
|
// When a menu is open, the prevent default flag on a keypress is always set, so
|
|
|
|
// that no one else uses the key event.
|
|
|
|
|
|
|
|
//handlers shouldn't be triggered by non-trusted events.
|
|
|
|
nsCOMPtr<nsIDOMNSEvent> domNSEvent = do_QueryInterface(aKeyEvent);
|
|
|
|
PRBool trustedEvent = PR_FALSE;
|
|
|
|
|
|
|
|
if (domNSEvent) {
|
|
|
|
domNSEvent->GetIsTrusted(&trustedEvent);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!trustedEvent)
|
|
|
|
return NS_OK;
|
|
|
|
|
|
|
|
nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
|
|
|
|
PRUint32 theChar;
|
|
|
|
keyEvent->GetKeyCode(&theChar);
|
|
|
|
|
|
|
|
if (theChar == NS_VK_LEFT ||
|
|
|
|
theChar == NS_VK_RIGHT ||
|
|
|
|
theChar == NS_VK_UP ||
|
|
|
|
theChar == NS_VK_DOWN ||
|
|
|
|
theChar == NS_VK_HOME ||
|
|
|
|
theChar == NS_VK_END) {
|
|
|
|
HandleKeyboardNavigation(theChar);
|
|
|
|
}
|
|
|
|
else if (theChar == NS_VK_ESCAPE) {
|
2007-08-15 14:35:02 +00:00
|
|
|
// Pressing Escape hides one level of menus only. If no menu is open,
|
|
|
|
// check if a menubar is active and inform it that a menu closed. Even
|
|
|
|
// though in this latter case, a menu didn't actually close, the effect
|
|
|
|
// ends up being the same. Similar for the tab key below.
|
2007-08-15 14:03:21 +00:00
|
|
|
nsMenuChainItem* item = GetTopVisibleMenu();
|
|
|
|
if (item)
|
|
|
|
HidePopup(item->Content(), PR_FALSE, PR_FALSE, PR_FALSE);
|
2007-08-15 14:35:02 +00:00
|
|
|
else if (mActiveMenuBar)
|
|
|
|
mActiveMenuBar->MenuClosed();
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
else if (theChar == NS_VK_TAB) {
|
2007-08-15 14:35:02 +00:00
|
|
|
if (mCurrentMenu)
|
|
|
|
Rollup();
|
|
|
|
else if (mActiveMenuBar)
|
|
|
|
mActiveMenuBar->MenuClosed();
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
else if (theChar == NS_VK_ENTER ||
|
|
|
|
theChar == NS_VK_RETURN) {
|
|
|
|
// If there is a popup open, check if the current item needs to be opened.
|
|
|
|
// Otherwise, tell the active menubar, if any, to activate the menu. The
|
|
|
|
// Enter method will return a menu if one needs to be opened as a result.
|
|
|
|
nsMenuFrame* menuToOpen = nsnull;
|
2007-08-15 14:03:21 +00:00
|
|
|
nsMenuChainItem* item = GetTopVisibleMenu();
|
|
|
|
if (item)
|
|
|
|
menuToOpen = item->Frame()->Enter();
|
2007-07-04 15:49:38 +00:00
|
|
|
else if (mActiveMenuBar)
|
|
|
|
menuToOpen = mActiveMenuBar->Enter();
|
|
|
|
if (menuToOpen) {
|
|
|
|
nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
|
|
|
|
ShowMenu(content, PR_TRUE, PR_FALSE);
|
|
|
|
}
|
|
|
|
}
|
2007-10-01 23:20:37 +00:00
|
|
|
#ifndef XP_MACOSX
|
2007-07-04 15:49:38 +00:00
|
|
|
else if (theChar == NS_VK_F10) {
|
|
|
|
// doesn't matter what modifier keys are down in Non-Mac platform
|
|
|
|
// if the menu bar is active and F10 is pressed - deactivate it
|
|
|
|
Rollup();
|
|
|
|
}
|
2007-10-01 23:20:37 +00:00
|
|
|
#endif // !XP_MACOSX
|
2007-07-04 15:49:38 +00:00
|
|
|
else {
|
2007-07-23 17:08:10 +00:00
|
|
|
HandleShortcutNavigation(keyEvent, nsnull);
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
|
2007-08-23 21:21:27 +00:00
|
|
|
if (mCurrentMenu) {
|
|
|
|
// if a menu is open, it consumes the key event
|
|
|
|
aKeyEvent->StopPropagation();
|
|
|
|
aKeyEvent->PreventDefault();
|
|
|
|
}
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
return NS_OK; // I am consuming event
|
|
|
|
}
|
|
|
|
|
|
|
|
static nsPresContext*
|
|
|
|
GetPresContextFor(nsIContent* aContent)
|
|
|
|
{
|
|
|
|
nsIDocument *document = aContent->GetCurrentDoc();
|
|
|
|
if (document) {
|
|
|
|
nsIPresShell* presShell = document->GetPrimaryShell();
|
|
|
|
if (presShell)
|
|
|
|
return presShell->GetPresContext();
|
|
|
|
}
|
|
|
|
|
|
|
|
return nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
|
|
|
nsXULPopupShowingEvent::Run()
|
|
|
|
{
|
|
|
|
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
|
|
|
|
nsPresContext* context = GetPresContextFor(mPopup);
|
|
|
|
if (pm && context) {
|
2007-08-16 01:09:58 +00:00
|
|
|
// the popupshowing event should only be fired asynchronously
|
|
|
|
// for menus, so just use ePopupTypeMenu as the type
|
|
|
|
pm->FirePopupShowingEvent(mPopup, mMenu, context, ePopupTypeMenu,
|
2007-07-04 15:49:38 +00:00
|
|
|
mIsContextMenu, mSelectFirstItem);
|
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
|
|
|
nsXULPopupHidingEvent::Run()
|
|
|
|
{
|
|
|
|
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
|
|
|
|
nsPresContext* context = GetPresContextFor(mPopup);
|
|
|
|
if (pm && context) {
|
|
|
|
pm->FirePopupHidingEvent(mPopup, mNextPopup, mLastPopup,
|
2007-08-16 01:09:58 +00:00
|
|
|
context, mPopupType, mDeselectMenu);
|
2007-07-04 15:49:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
|
|
|
nsXULMenuCommandEvent::Run()
|
|
|
|
{
|
|
|
|
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
|
|
|
|
if (!pm)
|
|
|
|
return NS_OK;
|
|
|
|
|
|
|
|
// The order of the nsIViewManager and nsIPresShell COM pointers is
|
|
|
|
// important below. We want the pres shell to get released before the
|
|
|
|
// associated view manager on exit from this function.
|
|
|
|
// See bug 54233.
|
|
|
|
// XXXndeakin is this still needed?
|
2007-08-23 22:13:42 +00:00
|
|
|
|
|
|
|
nsCOMPtr<nsIContent> popup;
|
2007-07-04 15:49:38 +00:00
|
|
|
nsMenuFrame* menuFrame = pm->GetMenuFrameForContent(mMenu);
|
|
|
|
if (menuFrame) {
|
2007-08-23 22:13:42 +00:00
|
|
|
// Find the popup that the menu is inside. Below, this popup will
|
|
|
|
// need to be hidden.
|
|
|
|
nsIFrame* popupFrame = menuFrame->GetParent();
|
|
|
|
while (popupFrame) {
|
2007-10-26 17:24:35 +00:00
|
|
|
// If the menu is a descendant of a menubar, clear the recently closed
|
|
|
|
// state. Break out afterwards, as the menubar is the top level of a
|
|
|
|
// menu hierarchy.
|
|
|
|
if (popupFrame->GetType() == nsGkAtoms::menuBarFrame) {
|
|
|
|
(static_cast<nsMenuBarFrame *>(popupFrame))->SetRecentlyClosed(nsnull);
|
2007-08-23 22:13:42 +00:00
|
|
|
break;
|
|
|
|
}
|
2007-10-26 17:24:35 +00:00
|
|
|
else if (!popup && popupFrame->GetType() == nsGkAtoms::menuPopupFrame) {
|
|
|
|
popup = popupFrame->GetContent();
|
|
|
|
}
|
2007-08-23 22:13:42 +00:00
|
|
|
popupFrame = popupFrame->GetParent();
|
|
|
|
}
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
nsPresContext* presContext = menuFrame->PresContext();
|
|
|
|
nsCOMPtr<nsIViewManager> kungFuDeathGrip = presContext->GetViewManager();
|
|
|
|
nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
|
|
|
|
|
|
|
|
// Deselect ourselves.
|
2007-08-30 15:43:10 +00:00
|
|
|
if (mCloseMenuMode != CloseMenuMode_None)
|
|
|
|
menuFrame->SelectMenu(PR_FALSE);
|
2007-07-04 15:49:38 +00:00
|
|
|
|
2007-07-24 00:55:52 +00:00
|
|
|
nsAutoHandlingUserInputStatePusher userInpStatePusher(mUserInput);
|
|
|
|
|
2007-07-04 15:49:38 +00:00
|
|
|
nsEventStatus status = nsEventStatus_eIgnore;
|
|
|
|
nsXULCommandEvent commandEvent(mIsTrusted, NS_XUL_COMMAND, nsnull);
|
|
|
|
commandEvent.isShift = mShift;
|
|
|
|
commandEvent.isControl = mControl;
|
|
|
|
commandEvent.isAlt = mAlt;
|
|
|
|
commandEvent.isMeta = mMeta;
|
|
|
|
shell->HandleDOMEventWithTarget(mMenu, &commandEvent, &status);
|
|
|
|
}
|
|
|
|
|
2007-08-30 15:43:10 +00:00
|
|
|
if (popup && mCloseMenuMode != CloseMenuMode_None)
|
2007-09-18 15:00:43 +00:00
|
|
|
pm->HidePopup(popup, mCloseMenuMode == CloseMenuMode_Auto, PR_TRUE, PR_FALSE);
|
2007-07-04 15:49:38 +00:00
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|