gecko-dev/layout/xul/base/src/nsMenuFrame.cpp

521 lines
14 KiB
C++
Raw Normal View History

1999-07-18 06:36:37 +00:00
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
1999-07-20 08:19:47 +00:00
#include "nsXULAtoms.h"
#include "nsHTMLAtoms.h"
1999-07-18 06:36:37 +00:00
#include "nsMenuFrame.h"
1999-07-21 02:56:23 +00:00
#include "nsBoxFrame.h"
1999-07-18 06:36:37 +00:00
#include "nsIContent.h"
#include "prtypes.h"
#include "nsIAtom.h"
#include "nsIPresContext.h"
#include "nsIStyleContext.h"
#include "nsCSSRendering.h"
#include "nsINameSpaceManager.h"
#include "nsLayoutAtoms.h"
#include "nsMenuPopupFrame.h"
1999-07-21 07:42:16 +00:00
#include "nsMenuBarFrame.h"
1999-07-26 02:26:26 +00:00
#include "nsIView.h"
#include "nsIWidget.h"
1999-07-18 06:36:37 +00:00
#define NS_MENU_POPUP_LIST_INDEX (NS_AREA_FRAME_ABSOLUTE_LIST_INDEX + 1)
static gEatMouseMove = PR_FALSE;
1999-07-18 06:36:37 +00:00
//
// NS_NewMenuFrame
//
// Wrapper for creating a new menu popup container
//
nsresult
1999-07-23 05:17:08 +00:00
NS_NewMenuFrame(nsIFrame** aNewFrame, PRUint32 aFlags)
1999-07-18 06:36:37 +00:00
{
NS_PRECONDITION(aNewFrame, "null OUT ptr");
if (nsnull == aNewFrame) {
return NS_ERROR_NULL_POINTER;
}
nsMenuFrame* it = new nsMenuFrame;
if ( !it )
return NS_ERROR_OUT_OF_MEMORY;
*aNewFrame = it;
if (aFlags)
it->SetIsMenu(PR_TRUE);
1999-07-18 06:36:37 +00:00
return NS_OK;
}
1999-07-21 07:42:16 +00:00
NS_IMETHODIMP_(nsrefcnt)
nsMenuFrame::AddRef(void)
{
return NS_OK;
}
NS_IMETHODIMP_(nsrefcnt)
nsMenuFrame::Release(void)
{
return NS_OK;
}
NS_IMETHODIMP nsMenuFrame::QueryInterface(REFNSIID aIID, void** aInstancePtr)
{
if (NULL == aInstancePtr) {
return NS_ERROR_NULL_POINTER;
}
*aInstancePtr = NULL;
1999-07-25 01:14:43 +00:00
if (aIID.Equals(nsITimerCallback::GetIID())) {
*aInstancePtr = (void*)(nsITimerCallback*) this;
NS_ADDREF_THIS();
return NS_OK;
}
1999-07-21 07:42:16 +00:00
return nsBoxFrame::QueryInterface(aIID, aInstancePtr);
}
1999-07-18 06:36:37 +00:00
//
// nsMenuFrame cntr
//
nsMenuFrame::nsMenuFrame()
:mMenuOpen(PR_FALSE), mIsMenu(PR_FALSE), mMenuParent(nsnull)
1999-07-18 06:36:37 +00:00
{
} // cntr
1999-07-21 07:42:16 +00:00
NS_IMETHODIMP
nsMenuFrame::Init(nsIPresContext& aPresContext,
nsIContent* aContent,
nsIFrame* aParent,
nsIStyleContext* aContext,
nsIFrame* aPrevInFlow)
{
nsresult rv = nsBoxFrame::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow);
// Set our menu parent.
nsCOMPtr<nsIMenuParent> menuparent = do_QueryInterface(aParent);
mMenuParent = menuparent.get();
return rv;
}
// The following methods are all overridden to ensure that the xpmenuchildren frame
// is placed in the appropriate list.
NS_IMETHODIMP
nsMenuFrame::FirstChild(nsIAtom* aListName,
nsIFrame** aFirstChild) const
{
if (nsLayoutAtoms::popupList == aListName) {
*aFirstChild = mPopupFrames.FirstChild();
} else {
1999-07-21 02:56:23 +00:00
nsBoxFrame::FirstChild(aListName, aFirstChild);
}
return NS_OK;
}
NS_IMETHODIMP
nsMenuFrame::SetInitialChildList(nsIPresContext& aPresContext,
nsIAtom* aListName,
nsIFrame* aChildList)
{
nsresult rv = NS_OK;
if (nsLayoutAtoms::popupList == aListName) {
mPopupFrames.SetFrames(aChildList);
} else {
1999-07-20 08:19:47 +00:00
nsFrameList frames(aChildList);
// We may have an xpmenuchildren in here. Get it out, and move it into
// the popup frame list.
nsIFrame* frame = frames.FirstChild();
while (frame) {
nsCOMPtr<nsIContent> content;
frame->GetContent(getter_AddRefs(content));
nsCOMPtr<nsIAtom> tag;
content->GetTag(*getter_AddRefs(tag));
if (tag.get() == nsXULAtoms::xpmenuchildren) {
// Remove this frame from the list and place it in the other list.
frames.RemoveFrame(frame);
mPopupFrames.AppendFrame(this, frame);
1999-07-21 02:56:23 +00:00
rv = nsBoxFrame::SetInitialChildList(aPresContext, aListName, aChildList);
1999-07-20 08:19:47 +00:00
return rv;
}
frame->GetNextSibling(&frame);
}
1999-07-21 02:56:23 +00:00
rv = nsBoxFrame::SetInitialChildList(aPresContext, aListName, aChildList);
}
return rv;
}
NS_IMETHODIMP
nsMenuFrame::GetAdditionalChildListName(PRInt32 aIndex,
nsIAtom** aListName) const
{
// Maintain a separate child list for the menu contents.
// This is necessary because we don't want the menu contents to be included in the layout
// of the menu's single item because it would take up space, when it is supposed to
// be floating above the display.
/*NS_PRECONDITION(nsnull != aListName, "null OUT parameter pointer");
*aListName = nsnull;
if (NS_MENU_POPUP_LIST_INDEX == aIndex) {
*aListName = nsLayoutAtoms::popupList;
NS_ADDREF(*aListName);
return NS_OK;
}*/
1999-07-21 02:56:23 +00:00
return nsBoxFrame::GetAdditionalChildListName(aIndex, aListName);
}
NS_IMETHODIMP
1999-07-22 02:24:52 +00:00
nsMenuFrame::Destroy(nsIPresContext& aPresContext)
{
// Cleanup frames in popup child list
mPopupFrames.DestroyFrames(aPresContext);
1999-07-22 02:24:52 +00:00
return nsBoxFrame::Destroy(aPresContext);
}
// Called to prevent events from going to anything inside the menu.
NS_IMETHODIMP
nsMenuFrame::GetFrameForPoint(const nsPoint& aPoint,
nsIFrame** aFrame)
{
*aFrame = this; // Capture all events so that we can perform selection
return NS_OK;
1999-07-20 08:19:47 +00:00
}
NS_IMETHODIMP
nsMenuFrame::HandleEvent(nsIPresContext& aPresContext,
nsGUIEvent* aEvent,
nsEventStatus& aEventStatus)
{
aEventStatus = nsEventStatus_eConsumeDoDefault;
if (IsDisabled()) // Disabled menus process no events.
return NS_OK;
1999-07-20 09:50:48 +00:00
if (aEvent->message == NS_MOUSE_LEFT_BUTTON_DOWN) {
PRBool isMenuBar = PR_TRUE;
if (mMenuParent)
mMenuParent->IsMenuBar(isMenuBar);
if (isMenuBar) {
// The menu item was selected. Bring up the menu.
nsIFrame* frame = mPopupFrames.FirstChild();
if (frame) {
// We have children.
ToggleMenuState();
if (!IsOpen()) {
// We closed up. The menu bar should always be
// deactivated when this happens.
mMenuParent->SetActive(PR_FALSE);
}
1999-07-23 08:36:39 +00:00
}
}
}
else if (aEvent->message == NS_MOUSE_LEFT_BUTTON_UP && !IsMenu() &&
mMenuParent) {
// The menu item was invoked and can now be dismissed.
1999-07-24 22:02:23 +00:00
// XXX Execute the execute event handler.
mMenuParent->DismissChain();
}
1999-07-21 07:42:16 +00:00
else if (aEvent->message == NS_MOUSE_EXIT) {
// Kill our timer if one is active.
if (mOpenTimer) {
mOpenTimer->Cancel();
mOpenTimer = nsnull;
}
1999-07-21 07:42:16 +00:00
// Deactivate the menu.
1999-07-23 08:36:39 +00:00
PRBool isActive = PR_FALSE;
PRBool isMenuBar = PR_FALSE;
if (mMenuParent) {
mMenuParent->IsMenuBar(isMenuBar);
PRBool cancel = PR_TRUE;
1999-07-23 08:36:39 +00:00
if (isMenuBar) {
mMenuParent->GetIsActive(isActive);
if (isActive) cancel = PR_FALSE;
1999-07-23 08:36:39 +00:00
}
if (cancel) {
if (IsMenu() && !isMenuBar && mMenuOpen) {
// Submenus don't get closed up.
}
else mMenuParent->SetCurrentMenuItem(nsnull);
}
1999-07-23 08:36:39 +00:00
}
1999-07-21 07:42:16 +00:00
}
1999-07-25 01:14:43 +00:00
else if (aEvent->message == NS_MOUSE_MOVE && mMenuParent) {
if (gEatMouseMove) {
gEatMouseMove = PR_FALSE;
return NS_OK;
}
1999-07-21 07:42:16 +00:00
// Let the menu parent know we're the new item.
1999-07-25 01:14:43 +00:00
mMenuParent->SetCurrentMenuItem(this);
PRBool isMenuBar = PR_TRUE;
mMenuParent->IsMenuBar(isMenuBar);
// If we're a menu (and not a menu item),
// kick off the timer.
1999-07-25 01:14:43 +00:00
if (!isMenuBar && IsMenu() && !mMenuOpen && !mOpenTimer) {
// We're a menu, we're closed, and no timer has been kicked off.
NS_NewTimer(getter_AddRefs(mOpenTimer));
mOpenTimer->Init(this, 250); // 250 ms delay
}
1999-07-21 07:42:16 +00:00
}
return NS_OK;
}
void
nsMenuFrame::ToggleMenuState()
1999-07-21 07:42:16 +00:00
{
if (mMenuOpen) {
1999-07-21 07:42:16 +00:00
OpenMenu(PR_FALSE);
}
else {
OpenMenu(PR_TRUE);
}
}
void
nsMenuFrame::SelectMenu(PRBool aActivateFlag)
{
if (aActivateFlag) {
// Highlight the menu.
mContent->SetAttribute(kNameSpaceID_None, nsXULAtoms::menuactive, "true", PR_TRUE);
}
else {
1999-07-21 07:42:16 +00:00
// Unhighlight the menu.
mContent->UnsetAttribute(kNameSpaceID_None, nsXULAtoms::menuactive, PR_TRUE);
}
}
void
nsMenuFrame::OpenMenu(PRBool aActivateFlag)
{
gEatMouseMove = PR_TRUE;
if (!mIsMenu)
return;
1999-07-21 07:42:16 +00:00
nsCOMPtr<nsIContent> child;
GetMenuChildrenElement(getter_AddRefs(child));
nsIFrame* frame = mPopupFrames.FirstChild();
nsMenuPopupFrame* menuPopup = (nsMenuPopupFrame*)frame;
1999-07-21 07:42:16 +00:00
if (aActivateFlag) {
1999-07-24 22:02:23 +00:00
// XXX Execute the oncreate handler
1999-07-22 09:49:35 +00:00
// Sync up the view.
1999-07-24 22:51:50 +00:00
PRBool onMenuBar = PR_TRUE;
if (mMenuParent)
mMenuParent->IsMenuBar(onMenuBar);
1999-07-22 09:49:35 +00:00
if (menuPopup)
1999-07-24 22:51:50 +00:00
menuPopup->SyncViewWithFrame(onMenuBar);
1999-07-22 09:49:35 +00:00
// Open the menu.
1999-07-21 07:42:16 +00:00
mContent->SetAttribute(kNameSpaceID_None, nsXULAtoms::open, "true", PR_TRUE);
if (child) {
// We've got some children for real.
1999-07-21 07:42:16 +00:00
child->SetAttribute(kNameSpaceID_None, nsXULAtoms::menuactive, "true", PR_TRUE);
// Tell the menu bar we're active.
1999-07-23 08:36:39 +00:00
mMenuParent->SetActive(PR_TRUE);
}
mMenuOpen = PR_TRUE;
//if (menuPopup)
// menuPopup->CaptureMouseEvents(PR_TRUE);
}
1999-07-21 07:42:16 +00:00
else {
1999-07-24 22:02:23 +00:00
// Close the menu.
// XXX Execute the ondestroy handler
1999-07-21 07:42:16 +00:00
mContent->UnsetAttribute(kNameSpaceID_None, nsXULAtoms::open, PR_TRUE);
if (child)
child->UnsetAttribute(kNameSpaceID_None, nsXULAtoms::menuactive, PR_TRUE);
mMenuOpen = PR_FALSE;
1999-07-22 09:49:35 +00:00
// Make sure we clear out our own items.
if (menuPopup)
menuPopup->SetCurrentMenuItem(nsnull);
1999-07-26 02:26:26 +00:00
// Set the focus back to our view's widget.
nsIView* view;
GetView(&view);
if (!view) {
nsPoint offset;
GetOffsetFromView(offset, &view);
}
nsCOMPtr<nsIWidget> widget;
view->GetWidget(*getter_AddRefs(widget));
widget->SetFocus();
1999-07-21 07:42:16 +00:00
}
}
void
nsMenuFrame::GetMenuChildrenElement(nsIContent** aResult)
{
*aResult = nsnull;
nsIFrame* frame = mPopupFrames.FirstChild();
if (frame) {
frame->GetContent(aResult);
}
}
NS_IMETHODIMP
nsMenuFrame::Reflow(nsIPresContext& aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus)
{
1999-07-21 02:56:23 +00:00
nsresult rv = nsBoxFrame::Reflow(aPresContext, aDesiredSize, aReflowState, aStatus);
nsIFrame* frame = mPopupFrames.FirstChild();
if (!frame || (rv != NS_OK))
return rv;
// Constrain the child's width and height to aAvailableWidth and aAvailableHeight
nsSize availSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
nsHTMLReflowState kidReflowState(aPresContext, aReflowState, frame,
availSize);
kidReflowState.mComputedWidth = NS_UNCONSTRAINEDSIZE;
kidReflowState.mComputedHeight = NS_UNCONSTRAINEDSIZE;
// Reflow child
nscoord w = aDesiredSize.width;
nscoord h = aDesiredSize.height;
rv = ReflowChild(frame, aPresContext, aDesiredSize, kidReflowState, aStatus);
// Set the child's width and height to its desired size
nsRect rect;
frame->GetRect(rect);
rect.width = aDesiredSize.width;
rect.height = aDesiredSize.height;
frame->SetRect(rect);
// Don't let it affect our size.
aDesiredSize.width = w;
aDesiredSize.height = h;
return rv;
}
void
nsMenuFrame::ShortcutNavigation(PRUint32 aLetter, PRBool& aHandledFlag)
{
nsIFrame* frame = mPopupFrames.FirstChild();
if (frame) {
nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame;
popup->ShortcutNavigation(aLetter, aHandledFlag);
}
}
void
nsMenuFrame::KeyboardNavigation(PRUint32 aDirection, PRBool& aHandledFlag)
{
nsIFrame* frame = mPopupFrames.FirstChild();
if (frame) {
nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame;
popup->KeyboardNavigation(aDirection, aHandledFlag);
}
}
void
1999-07-23 08:36:39 +00:00
nsMenuFrame::Escape(PRBool& aHandledFlag)
{
1999-07-23 08:36:39 +00:00
nsIFrame* frame = mPopupFrames.FirstChild();
if (frame) {
nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame;
popup->Escape(aHandledFlag);
}
}
1999-07-24 22:02:23 +00:00
void
nsMenuFrame::Enter()
{
if (!mMenuOpen) {
// The enter key press applies to us.
// XXX Execute the event handler.
if (!IsMenu() && mMenuParent) {
1999-07-24 22:02:23 +00:00
// Close up the parent.
mMenuParent->DismissChain();
}
else {
1999-07-24 22:02:23 +00:00
OpenMenu(PR_TRUE);
SelectFirstItem();
}
return;
}
nsIFrame* frame = mPopupFrames.FirstChild();
if (frame) {
nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame;
popup->Enter();
}
}
void
nsMenuFrame::SelectFirstItem()
{
nsIFrame* frame = mPopupFrames.FirstChild();
if (frame) {
nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame;
nsIFrame* result;
popup->GetNextMenuItem(nsnull, &result);
popup->SetCurrentMenuItem(result);
}
}
PRBool
nsMenuFrame::IsMenu()
{
nsCOMPtr<nsIAtom> tag;
mContent->GetTag(*getter_AddRefs(tag));
if (tag.get() == nsXULAtoms::xpmenu)
return PR_TRUE;
return PR_FALSE;
1999-07-25 01:14:43 +00:00
}
void
nsMenuFrame::Notify(nsITimer* aTimer)
{
// Our timer has fired.
if (aTimer == mOpenTimer.get()) {
if (!mMenuOpen && mMenuParent) {
nsAutoString active = "";
mContent->GetAttribute(kNameSpaceID_None, nsXULAtoms::menuactive, active);
if (active == "true") {
// We're still the active menu.
OpenMenu(PR_TRUE);
}
1999-07-25 01:14:43 +00:00
}
mOpenTimer->Cancel();
mOpenTimer = nsnull;
1999-07-25 01:14:43 +00:00
}
1999-07-25 01:14:43 +00:00
mOpenTimer = nsnull;
}
PRBool
nsMenuFrame::IsDisabled()
{
nsString disabled = "";
mContent->GetAttribute(kNameSpaceID_None, nsHTMLAtoms::disabled, disabled);
if (disabled == "true")
return PR_TRUE;
return PR_FALSE;
}