Back to index

lightning-sunbird  0.9+nobinonly
nsMenuFrame.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Original Author: David W. Hyatt (hyatt@netscape.com)
00024  *   Michael Lowe <michael.lowe@bigfoot.com>
00025  *   Pierre Phaneuf <pp@ludusdesign.com>
00026  *   Dean Tessman <dean_tessman@hotmail.com>
00027  *
00028  * Alternatively, the contents of this file may be used under the terms of
00029  * either of the GNU General Public License Version 2 or later (the "GPL"),
00030  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00031  * in which case the provisions of the GPL or the LGPL are applicable instead
00032  * of those above. If you wish to allow use of your version of this file only
00033  * under the terms of either the GPL or the LGPL, and not to allow others to
00034  * use your version of this file under the terms of the MPL, indicate your
00035  * decision by deleting the provisions above and replace them with the notice
00036  * and other provisions required by the GPL or the LGPL. If you do not delete
00037  * the provisions above, a recipient may use your version of this file under
00038  * the terms of any one of the MPL, the GPL or the LGPL.
00039  *
00040  * ***** END LICENSE BLOCK ***** */
00041 
00042 #include "nsXULAtoms.h"
00043 #include "nsHTMLAtoms.h"
00044 #include "nsHTMLParts.h"
00045 #include "nsMenuFrame.h"
00046 #include "nsBoxFrame.h"
00047 #include "nsIContent.h"
00048 #include "prtypes.h"
00049 #include "nsIAtom.h"
00050 #include "nsPresContext.h"
00051 #include "nsIPresShell.h"
00052 #include "nsStyleContext.h"
00053 #include "nsCSSRendering.h"
00054 #include "nsINameSpaceManager.h"
00055 #include "nsLayoutAtoms.h"
00056 #include "nsMenuPopupFrame.h"
00057 #include "nsMenuBarFrame.h"
00058 #include "nsIView.h"
00059 #include "nsIWidget.h"
00060 #include "nsIDocument.h"
00061 #include "nsIDOMNSDocument.h"
00062 #include "nsIDOMDocument.h"
00063 #include "nsIDOMElement.h"
00064 #include "nsISupportsArray.h"
00065 #include "nsIDOMText.h"
00066 #include "nsILookAndFeel.h"
00067 #include "nsIComponentManager.h"
00068 #include "nsWidgetsCID.h"
00069 #include "nsBoxLayoutState.h"
00070 #include "nsIScrollableFrame.h"
00071 #include "nsIViewManager.h"
00072 #include "nsIBindingManager.h"
00073 #include "nsIServiceManager.h"
00074 #include "nsIXBLService.h"
00075 #include "nsCSSFrameConstructor.h"
00076 #include "nsIDOMKeyEvent.h"
00077 #include "nsIScrollableView.h"
00078 #include "nsXPIDLString.h"
00079 #include "nsReadableUtils.h"
00080 #include "nsUnicharUtils.h"
00081 #include "nsIStringBundle.h"
00082 #include "nsGUIEvent.h"
00083 #include "nsIEventStateManager.h"
00084 #include "nsITimerInternal.h"
00085 #include "nsContentUtils.h"
00086 #include "nsIEventQueueService.h"
00087 #include "nsIServiceManager.h"
00088 #include "nsIReflowCallback.h"
00089 
00090 #define NS_MENU_POPUP_LIST_INDEX   0
00091 
00092 #if defined(XP_WIN) || defined(XP_OS2)
00093 #define NSCONTEXTMENUISMOUSEUP 1
00094 #endif
00095 
00096 static NS_DEFINE_CID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID);
00097 
00098 static PRInt32 gEatMouseMove = PR_FALSE;
00099 
00100 nsMenuDismissalListener* nsMenuFrame::sDismissalListener = nsnull;
00101 
00102 static NS_DEFINE_IID(kLookAndFeelCID, NS_LOOKANDFEEL_CID);
00103 
00104 nsrefcnt nsMenuFrame::gRefCnt = 0;
00105 nsString *nsMenuFrame::gShiftText = nsnull;
00106 nsString *nsMenuFrame::gControlText = nsnull;
00107 nsString *nsMenuFrame::gMetaText = nsnull;
00108 nsString *nsMenuFrame::gAltText = nsnull;
00109 nsString *nsMenuFrame::gModifierSeparator = nsnull;
00110 
00111 //
00112 // NS_NewMenuFrame
00113 //
00114 // Wrapper for creating a new menu popup container
00115 //
00116 nsresult
00117 NS_NewMenuFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame, PRUint32 aFlags)
00118 {
00119   NS_PRECONDITION(aNewFrame, "null OUT ptr");
00120   if (nsnull == aNewFrame) {
00121     return NS_ERROR_NULL_POINTER;
00122   }
00123   nsMenuFrame* it = new (aPresShell) nsMenuFrame (aPresShell);
00124   if ( !it )
00125     return NS_ERROR_OUT_OF_MEMORY;
00126   *aNewFrame = it;
00127   if (aFlags)
00128     it->SetIsMenu(PR_TRUE);
00129   return NS_OK;
00130 }
00131 
00132 NS_IMETHODIMP_(nsrefcnt) 
00133 nsMenuFrame::AddRef(void)
00134 {
00135   return NS_OK;
00136 }
00137 
00138 NS_IMETHODIMP_(nsrefcnt)
00139 nsMenuFrame::Release(void)
00140 {
00141     return NS_OK;
00142 }
00143 
00144 //
00145 // QueryInterface
00146 //
00147 NS_INTERFACE_MAP_BEGIN(nsMenuFrame)
00148   NS_INTERFACE_MAP_ENTRY(nsIMenuFrame)
00149   NS_INTERFACE_MAP_ENTRY(nsIScrollableViewProvider)
00150 NS_INTERFACE_MAP_END_INHERITING(nsBoxFrame)
00151 
00152 //
00153 // nsMenuFrame cntr
00154 //
00155 nsMenuFrame::nsMenuFrame(nsIPresShell* aShell):nsBoxFrame(aShell),
00156     mIsMenu(PR_FALSE),
00157     mMenuOpen(PR_FALSE),
00158     mCreateHandlerSucceeded(PR_FALSE),
00159     mChecked(PR_FALSE),
00160     mType(eMenuType_Normal),
00161     mMenuParent(nsnull),
00162     mPresContext(nsnull),
00163     mLastPref(-1,-1)
00164 {
00165 
00166 } // cntr
00167 
00168 NS_IMETHODIMP
00169 nsMenuFrame::SetParent(const nsIFrame* aParent)
00170 {
00171   nsBoxFrame::SetParent(aParent);
00172   const nsIFrame* currFrame = aParent;
00173   while (!mMenuParent && currFrame) {
00174     // Set our menu parent.
00175     CallQueryInterface(NS_CONST_CAST(nsIFrame*, currFrame), &mMenuParent);
00176 
00177     currFrame = currFrame->GetParent();
00178   }
00179 
00180   return NS_OK;
00181 }
00182 
00183 class nsASyncMenuInitialization : public nsIReflowCallback
00184 {
00185 public:
00186   nsASyncMenuInitialization(nsIFrame* aFrame)
00187     : mWeakFrame(aFrame)
00188   {
00189   }
00190 
00191   NS_DECL_ISUPPORTS
00192 
00193   NS_IMETHOD ReflowFinished(nsIPresShell* aShell, PRBool* aFlushFlag) {
00194     if (mWeakFrame.IsAlive()) {
00195       nsIMenuFrame* imenu = nsnull;
00196       CallQueryInterface(mWeakFrame.GetFrame(), &imenu);
00197       if (imenu) {
00198         nsMenuFrame* menu = NS_STATIC_CAST(nsMenuFrame*, imenu);
00199         menu->UpdateMenuType(menu->GetPresContext());
00200         *aFlushFlag = PR_TRUE;
00201       }
00202     }
00203     return NS_OK;
00204   }
00205 
00206   nsWeakFrame mWeakFrame;
00207 };
00208 
00209 NS_IMPL_ISUPPORTS1(nsASyncMenuInitialization, nsIReflowCallback)
00210 
00211 NS_IMETHODIMP
00212 nsMenuFrame::Init(nsPresContext*  aPresContext,
00213                      nsIContent*      aContent,
00214                      nsIFrame*        aParent,
00215                      nsStyleContext*  aContext,
00216                      nsIFrame*        aPrevInFlow)
00217 {
00218   mPresContext = aPresContext; // Don't addref it.  Our lifetime is shorter.
00219 
00220   nsresult  rv = nsBoxFrame::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow);
00221 
00222   // Set up a mediator which can be used for callbacks on this frame.
00223   mTimerMediator = new nsMenuTimerMediator(this);
00224   if (NS_UNLIKELY(!mTimerMediator))
00225     return NS_ERROR_OUT_OF_MEMORY;
00226 
00227   nsIFrame* currFrame = aParent;
00228   while (!mMenuParent && currFrame) {
00229     // Set our menu parent.
00230     CallQueryInterface(currFrame, &mMenuParent);
00231 
00232     currFrame = currFrame->GetParent();
00233   }
00234 
00235   //load the display strings for the keyboard accelerators, but only once
00236   if (gRefCnt++ == 0) {
00237     
00238     nsCOMPtr<nsIStringBundleService> bundleService(do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv));
00239     nsCOMPtr<nsIStringBundle> bundle;
00240     if (NS_SUCCEEDED(rv) && bundleService) {
00241       rv = bundleService->CreateBundle( "chrome://global-platform/locale/platformKeys.properties",
00242                                         getter_AddRefs(bundle));
00243     }    
00244     
00245     NS_ASSERTION(NS_SUCCEEDED(rv) && bundle, "chrome://global/locale/platformKeys.properties could not be loaded");
00246     nsXPIDLString shiftModifier;
00247     nsXPIDLString metaModifier;
00248     nsXPIDLString altModifier;
00249     nsXPIDLString controlModifier;
00250     nsXPIDLString modifierSeparator;
00251     if (NS_SUCCEEDED(rv) && bundle) {
00252       //macs use symbols for each modifier key, so fetch each from the bundle, which also covers i18n
00253       rv = bundle->GetStringFromName(NS_LITERAL_STRING("VK_SHIFT").get(), getter_Copies(shiftModifier));
00254       rv = bundle->GetStringFromName(NS_LITERAL_STRING("VK_META").get(), getter_Copies(metaModifier));
00255       rv = bundle->GetStringFromName(NS_LITERAL_STRING("VK_ALT").get(), getter_Copies(altModifier));
00256       rv = bundle->GetStringFromName(NS_LITERAL_STRING("VK_CONTROL").get(), getter_Copies(controlModifier));
00257       rv = bundle->GetStringFromName(NS_LITERAL_STRING("MODIFIER_SEPARATOR").get(), getter_Copies(modifierSeparator));
00258     } else {
00259       rv = NS_ERROR_NOT_AVAILABLE;
00260     }
00261     //if any of these don't exist, we get  an empty string
00262     gShiftText = new nsString(shiftModifier);
00263     gMetaText = new nsString(metaModifier);
00264     gAltText = new nsString(altModifier);
00265     gControlText = new nsString(controlModifier);
00266     gModifierSeparator = new nsString(modifierSeparator);    
00267   }
00268   
00269   BuildAcceleratorText();
00270   nsCOMPtr<nsIReflowCallback> cb = new nsASyncMenuInitialization(this);
00271   NS_ENSURE_TRUE(cb, NS_ERROR_OUT_OF_MEMORY);
00272   mPresContext->PresShell()->PostReflowCallback(cb);
00273   return rv;
00274 }
00275 
00276 nsMenuFrame::~nsMenuFrame()
00277 {
00278   // Clean up shared statics
00279   if (--gRefCnt == 0) {
00280     delete gShiftText;
00281     gShiftText = nsnull;
00282     delete gControlText;  
00283     gControlText = nsnull;
00284     delete gMetaText;  
00285     gMetaText = nsnull;
00286     delete gAltText;  
00287     gAltText = nsnull;
00288     delete gModifierSeparator;
00289     gModifierSeparator = nsnull;
00290   }
00291 }
00292 
00293 // The following methods are all overridden to ensure that the menupopup frame
00294 // is placed in the appropriate list.
00295 nsIFrame*
00296 nsMenuFrame::GetFirstChild(nsIAtom* aListName) const
00297 {
00298   if (nsLayoutAtoms::popupList == aListName) {
00299     return mPopupFrames.FirstChild();
00300   }
00301   return nsBoxFrame::GetFirstChild(aListName);
00302 }
00303 
00304 NS_IMETHODIMP
00305 nsMenuFrame::SetInitialChildList(nsPresContext* aPresContext,
00306                                                nsIAtom*        aListName,
00307                                                nsIFrame*       aChildList)
00308 {
00309   nsresult rv = NS_OK;
00310   if (nsLayoutAtoms::popupList == aListName) {
00311     mPopupFrames.SetFrames(aChildList);
00312   } else {
00313 
00314     nsFrameList frames(aChildList);
00315 
00316     // We may have a menupopup in here. Get it out, and move it into
00317     // the popup frame list.
00318     nsIFrame* frame = frames.FirstChild();
00319     while (frame) {
00320       nsIMenuParent *menuPar;
00321       CallQueryInterface(frame, &menuPar);
00322       if (menuPar) {
00323         PRBool isMenuBar;
00324         menuPar->IsMenuBar(isMenuBar);
00325         if (!isMenuBar) {
00326           // Remove this frame from the list and place it in the other list.
00327           frames.RemoveFrame(frame);
00328           mPopupFrames.AppendFrame(this, frame);
00329           nsIFrame* first = frames.FirstChild();
00330           rv = nsBoxFrame::SetInitialChildList(aPresContext, aListName, first);
00331           return rv;
00332         }
00333       }
00334       frame = frame->GetNextSibling();
00335     }
00336 
00337     // Didn't find it.
00338     rv = nsBoxFrame::SetInitialChildList(aPresContext, aListName, aChildList);
00339   }
00340   return rv;
00341 }
00342 
00343 nsIAtom*
00344 nsMenuFrame::GetAdditionalChildListName(PRInt32 aIndex) const
00345 {
00346   // don't expose the child frame list, it slows things down
00347 #if 0
00348   if (NS_MENU_POPUP_LIST_INDEX == aIndex) {
00349     return nsLayoutAtoms::popupList;
00350   }
00351 #endif
00352 
00353   return nsnull;
00354 }
00355 
00356 nsresult
00357 nsMenuFrame::DestroyPopupFrames(nsPresContext* aPresContext)
00358 {
00359   // Remove our frame mappings
00360   nsCSSFrameConstructor* frameConstructor =
00361     aPresContext->PresShell()->FrameConstructor();
00362   nsIFrame* curFrame = mPopupFrames.FirstChild();
00363   while (curFrame) {
00364     frameConstructor->RemoveMappingsForFrameSubtree(curFrame, nsnull);
00365     curFrame = curFrame->GetNextSibling();
00366   }
00367 
00368    // Cleanup frames in popup child list
00369   mPopupFrames.DestroyFrames(aPresContext);
00370   return NS_OK;
00371 }
00372 
00373 NS_IMETHODIMP
00374 nsMenuFrame::Destroy(nsPresContext* aPresContext)
00375 {
00376   // Kill our timer if one is active. This is not strictly necessary as
00377   // the pointer to this frame will be cleared from the mediator, but
00378   // this is done for added safety.
00379   if (mOpenTimer) {
00380     mOpenTimer->Cancel();
00381   }
00382 
00383   // Null out the pointer to this frame in the mediator wrapper so that it 
00384   // doesn't try to interact with a deallocated frame.
00385   mTimerMediator->ClearFrame();
00386 
00387   nsWeakFrame weakFrame(this);
00388   // are we our menu parent's current menu item?
00389   if (mMenuParent) {
00390     nsIMenuFrame *curItem = mMenuParent->GetCurrentMenuItem();
00391     if (curItem == this) {
00392       // yes; tell it that we're going away
00393       mMenuParent->SetCurrentMenuItem(nsnull);
00394       NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
00395     }
00396   }
00397 
00398   UngenerateMenu();
00399   NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
00400   DestroyPopupFrames(aPresContext);
00401   return nsBoxFrame::Destroy(aPresContext);
00402 }
00403 
00404 // Called to prevent events from going to anything inside the menu.
00405 NS_IMETHODIMP
00406 nsMenuFrame::GetFrameForPoint(const nsPoint& aPoint, 
00407                               nsFramePaintLayer aWhichLayer,    
00408                               nsIFrame**     aFrame)
00409 {
00410   nsresult result = nsBoxFrame::GetFrameForPoint(aPoint, aWhichLayer, aFrame);
00411   if (NS_FAILED(result) || *aFrame == this) {
00412     return result;
00413   }
00414   nsIContent* content = (*aFrame)->GetContent();
00415   if (content) {
00416     // This allows selective overriding for subcontent.
00417     nsAutoString value;
00418     content->GetAttr(kNameSpaceID_None, nsXULAtoms::allowevents, value);
00419     if (value.EqualsLiteral("true"))
00420       return result;
00421   }
00422   if (GetStyleVisibility()->IsVisible()) {
00423     *aFrame = this; // Capture all events so that we can perform selection
00424     return NS_OK;
00425   }
00426   return NS_ERROR_FAILURE;
00427 }
00428 
00429 NS_IMETHODIMP 
00430 nsMenuFrame::HandleEvent(nsPresContext* aPresContext, 
00431                              nsGUIEvent*     aEvent,
00432                              nsEventStatus*  aEventStatus)
00433 {
00434   NS_ENSURE_ARG_POINTER(aEventStatus);
00435   nsWeakFrame weakFrame(this);
00436   if (*aEventStatus == nsEventStatus_eIgnore)
00437     *aEventStatus = nsEventStatus_eConsumeDoDefault;
00438   
00439   if (aEvent->message == NS_KEY_PRESS && !IsDisabled()) {
00440     nsKeyEvent* keyEvent = (nsKeyEvent*)aEvent;
00441     PRUint32 keyCode = keyEvent->keyCode;
00442 #ifdef XP_MACOSX
00443     // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed)
00444     if (!IsOpen() && ((keyEvent->charCode == NS_VK_SPACE && !keyEvent->isMeta) ||
00445         (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN)))
00446       OpenMenu(PR_TRUE);
00447 #else
00448     // On other platforms, toggle menulist on unmodified F4 or Alt arrow
00449     if ((keyCode == NS_VK_F4 && !keyEvent->isAlt) ||
00450         ((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->isAlt))
00451       OpenMenu(!IsOpen());
00452 #endif
00453   }
00454   else if (aEvent->message == NS_MOUSE_LEFT_BUTTON_DOWN && !IsDisabled() && IsMenu() ) {
00455     PRBool isMenuBar = PR_FALSE;
00456     if (mMenuParent)
00457       mMenuParent->IsMenuBar(isMenuBar);
00458 
00459     // The menu item was selected. Bring up the menu.
00460     // We have children.
00461     if ( isMenuBar || !mMenuParent ) {
00462       ToggleMenuState();
00463       NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
00464 
00465       if (!IsOpen() && mMenuParent) {
00466         // We closed up. The menu bar should always be
00467         // deactivated when this happens.
00468         mMenuParent->SetActive(PR_FALSE);
00469       }
00470     }
00471     else
00472       if ( !IsOpen() ) {
00473         // one of our siblings is probably open and even possibly waiting
00474         // for its close timer to fire. Tell our parent to close it down. Not
00475         // doing this before its timer fires will cause the rollup state to
00476         // get very confused.
00477         if ( mMenuParent )
00478           mMenuParent->KillPendingTimers();
00479 
00480         // safe to open up
00481         OpenMenu(PR_TRUE);
00482       }
00483   }
00484   else if (
00485 #ifndef NSCONTEXTMENUISMOUSEUP
00486             aEvent->message == NS_MOUSE_RIGHT_BUTTON_UP &&
00487 #else
00488             aEvent->message == NS_CONTEXTMENU &&
00489 #endif
00490             mMenuParent && !IsMenu() && !IsDisabled()) {
00491     // if this menu is a context menu it accepts right-clicks...fire away!
00492     // Make sure we cancel default processing of the context menu event so
00493     // that it doesn't bubble and get seen again by the popuplistener and show
00494     // another context menu.
00495     //
00496     // Furthermore (there's always more, isn't there?), on some platforms (win32
00497     // being one of them) we get the context menu event on a mouse up while
00498     // on others we get it on a mouse down. For the ones where we get it on a
00499     // mouse down, we must continue listening for the right button up event to
00500     // dismiss the menu.
00501     PRBool isContextMenu = PR_FALSE;
00502     mMenuParent->GetIsContextMenu(isContextMenu);
00503     if ( isContextMenu ) {
00504       *aEventStatus = nsEventStatus_eConsumeNoDefault;
00505       Execute(aEvent);
00506     }
00507   }
00508   else if (aEvent->message == NS_MOUSE_LEFT_BUTTON_UP && !IsMenu() && mMenuParent && !IsDisabled()) {
00509     // Execute the execute event handler.
00510     Execute(aEvent);
00511   }
00512   else if (aEvent->message == NS_MOUSE_EXIT_SYNTH) {
00513     // Kill our timer if one is active.
00514     if (mOpenTimer) {
00515       mOpenTimer->Cancel();
00516       mOpenTimer = nsnull;
00517     }
00518 
00519     // Deactivate the menu.
00520     PRBool isActive = PR_FALSE;
00521     PRBool isMenuBar = PR_FALSE;
00522     if (mMenuParent) {
00523       mMenuParent->IsMenuBar(isMenuBar);
00524       PRBool cancel = PR_TRUE;
00525       if (isMenuBar) {
00526         mMenuParent->GetIsActive(isActive);
00527         if (isActive) cancel = PR_FALSE;
00528       }
00529       
00530       if (cancel) {
00531         if (IsMenu() && !isMenuBar && mMenuOpen) {
00532           // Submenus don't get closed up immediately.
00533         }
00534         else mMenuParent->SetCurrentMenuItem(nsnull);
00535       }
00536     }
00537   }
00538   else if (aEvent->message == NS_MOUSE_MOVE && mMenuParent) {
00539     if (gEatMouseMove) {
00540       gEatMouseMove = PR_FALSE;
00541       return NS_OK;
00542     }
00543 
00544     // we checked for mMenuParent right above
00545 
00546     PRBool isMenuBar = PR_FALSE;
00547     mMenuParent->IsMenuBar(isMenuBar);
00548 
00549     // Let the menu parent know we're the new item.
00550     mMenuParent->SetCurrentMenuItem(this);
00551     NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
00552     NS_ENSURE_TRUE(mMenuParent, NS_OK);
00553     
00554     // we need to check if we really became the current menu
00555     // item or not
00556     nsIMenuFrame *realCurrentItem = mMenuParent->GetCurrentMenuItem();
00557     if (realCurrentItem != this) {
00558       // we didn't (presumably because a context menu was active)
00559       return NS_OK;
00560     }
00561 
00562     // If we're a menu (and not a menu item),
00563     // kick off the timer.
00564     if (!IsDisabled() && !isMenuBar && IsMenu() && !mMenuOpen && !mOpenTimer) {
00565 
00566       PRInt32 menuDelay = 300;   // ms
00567 
00568       nsCOMPtr<nsILookAndFeel> lookAndFeel(do_GetService(kLookAndFeelCID));
00569       if (lookAndFeel)
00570         lookAndFeel->GetMetric(nsILookAndFeel::eMetric_SubmenuDelay, menuDelay);
00571 
00572       // We're a menu, we're built, we're closed, and no timer has been kicked off.
00573       mOpenTimer = do_CreateInstance("@mozilla.org/timer;1");
00574 
00575       nsCOMPtr<nsITimerInternal> ti = do_QueryInterface(mOpenTimer);
00576       ti->SetIdle(PR_FALSE);
00577 
00578       mOpenTimer->InitWithCallback(mTimerMediator, menuDelay, nsITimer::TYPE_ONE_SHOT);
00579 
00580     }
00581   }
00582   
00583   return NS_OK;
00584 }
00585 
00586 NS_IMETHODIMP
00587 nsMenuFrame::ToggleMenuState()
00588 {
00589   nsWeakFrame weakFrame(this);
00590   if (mMenuOpen) {
00591     OpenMenu(PR_FALSE);
00592     NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
00593   }
00594   else {
00595     PRBool justRolledUp = PR_FALSE;
00596     if (mMenuParent) {
00597       mMenuParent->RecentlyRolledUp(this, &justRolledUp);
00598     }
00599     if (justRolledUp) {
00600       // Don't let a click reopen a menu that was just rolled up
00601       // from the same click. Otherwise, the user can't click on
00602       // a menubar item to toggle its submenu closed.
00603       OpenMenu(PR_FALSE);
00604       NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
00605       SelectMenu(PR_TRUE);
00606       NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
00607       NS_ENSURE_TRUE(mMenuParent, NS_OK);
00608       mMenuParent->SetActive(PR_FALSE);
00609     }
00610     else {
00611       if (mMenuParent) {
00612         mMenuParent->SetActive(PR_TRUE);
00613         NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
00614       }
00615       OpenMenu(PR_TRUE);
00616     }
00617   }
00618   NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
00619 
00620   if (mMenuParent) {
00621     // Make sure the current menu which is being toggled on
00622     // the menubar is highlighted
00623     mMenuParent->SetCurrentMenuItem(this);
00624     NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
00625     NS_ENSURE_TRUE(mMenuParent, NS_OK);
00626     // We've successfully prevented the same click from both
00627     // dismissing and reopening this menu. 
00628     // Clear the recent rollup state so we don't prevent
00629     // this menu from being opened by the next click.
00630     mMenuParent->ClearRecentlyRolledUp();
00631   }
00632 
00633   return NS_OK;
00634 }
00635 
00636 NS_IMETHODIMP
00637 nsMenuFrame::SelectMenu(PRBool aActivateFlag)
00638 {
00639   if (!mContent) {
00640     return NS_OK;
00641   }
00642 
00643   nsAutoString domEventToFire;
00644 
00645   nsWeakFrame weakFrame(this);
00646   if (aActivateFlag) {
00647     // Highlight the menu.
00648     mContent->SetAttr(kNameSpaceID_None, nsXULAtoms::menuactive, NS_LITERAL_STRING("true"), PR_TRUE);
00649     // The menuactivated event is used by accessibility to track the user's movements through menus
00650     domEventToFire.AssignLiteral("DOMMenuItemActive");
00651   }
00652   else {
00653     // Unhighlight the menu.
00654     mContent->UnsetAttr(kNameSpaceID_None, nsXULAtoms::menuactive, PR_TRUE);
00655     domEventToFire.AssignLiteral("DOMMenuItemInactive");
00656   }
00657 
00658   if (weakFrame.IsAlive()) {
00659     FireDOMEventSynch(domEventToFire);
00660   }
00661   return NS_OK;
00662 }
00663 
00664 PRBool nsMenuFrame::IsGenerated()
00665 {
00666   nsCOMPtr<nsIContent> child;
00667   GetMenuChildrenElement(getter_AddRefs(child));
00668   
00669   // Generate the menu if it hasn't been generated already.  This
00670   // takes it from display: none to display: block and gives us
00671   // a menu forevermore.
00672   if (child) {
00673     nsString genVal;
00674     child->GetAttr(kNameSpaceID_None, nsXULAtoms::menugenerated, genVal);
00675     if (genVal.IsEmpty())
00676       return PR_FALSE;
00677   }
00678 
00679   return PR_TRUE;
00680 }
00681 
00682 NS_IMETHODIMP
00683 nsMenuFrame::MarkAsGenerated()
00684 {
00685   nsCOMPtr<nsIContent> child;
00686   GetMenuChildrenElement(getter_AddRefs(child));
00687   
00688   // Generate the menu if it hasn't been generated already.  This
00689   // takes it from display: none to display: block and gives us
00690   // a menu forevermore.
00691   if (child) {
00692     nsAutoString genVal;
00693     child->GetAttr(kNameSpaceID_None, nsXULAtoms::menugenerated, genVal);
00694     if (genVal.IsEmpty()) {
00695       child->SetAttr(kNameSpaceID_None, nsXULAtoms::menugenerated, NS_LITERAL_STRING("true"), PR_TRUE);
00696     }
00697   }
00698 
00699   return NS_OK;
00700 }
00701 
00702 struct nsASyncUngenerate : public PLEvent
00703 {
00704   nsASyncUngenerate(nsIContent* aMenu, nsIContent* aPopup)
00705     : mMenu(aMenu), mPopup(aPopup)
00706   {
00707   }
00708 
00709   void HandleEvent() {
00710     nsIDocument* doc = mMenu->GetCurrentDoc();
00711     if (doc) {
00712       nsIPresShell* shell = doc->GetShellAt(0);
00713       if (shell) {
00714         nsIFrame* frame = nsnull;
00715         shell->GetPrimaryFrameFor(mMenu, &frame);
00716         if (frame) {
00717           nsIMenuFrame* newMenu = nsnull;
00718           CallQueryInterface(frame, &newMenu);
00719           if (newMenu) {
00720             // Menu has been recreated.
00721             return;
00722           }
00723         }
00724       }
00725     }
00726     nsIContent* popupParent = mPopup->GetParent();
00727     if (popupParent && popupParent != mMenu) {
00728       // Popup has been moved to be a child of some other element than mMenu.
00729       return;
00730     }
00731     nsAutoString genVal;
00732     mPopup->GetAttr(kNameSpaceID_None, nsXULAtoms::menugenerated, genVal);
00733     if (!genVal.IsEmpty()) {
00734       mPopup->UnsetAttr(kNameSpaceID_None, nsXULAtoms::menugenerated, PR_TRUE);
00735     }
00736   }
00737 
00738   nsCOMPtr<nsIContent> mMenu;
00739   nsCOMPtr<nsIContent> mPopup;
00740 };
00741 
00742 static void* PR_CALLBACK HandleASyncUngenerate(PLEvent* aEvent)
00743 {
00744   NS_STATIC_CAST(nsASyncUngenerate*, aEvent)->HandleEvent();
00745   return nsnull;
00746 }
00747 
00748 static void PR_CALLBACK DestroyASyncUngenerate(PLEvent* aEvent)
00749 {
00750   delete NS_STATIC_CAST(nsASyncUngenerate*, aEvent);
00751 }
00752 
00753 NS_IMETHODIMP
00754 nsMenuFrame::UngenerateMenu()
00755 {
00756   nsCOMPtr<nsIContent> child;
00757   GetMenuChildrenElement(getter_AddRefs(child));
00758   
00759   if (child) {
00760     nsCOMPtr<nsIEventQueueService> eventService =
00761       do_GetService(kEventQueueServiceCID);
00762     if (eventService) {
00763       nsCOMPtr<nsIEventQueue> eventQueue;
00764         eventService->GetThreadEventQueue(PR_GetCurrentThread(),
00765                                           getter_AddRefs(eventQueue));
00766       if (eventQueue) {
00767         nsASyncUngenerate* ungenerate =
00768           new nsASyncUngenerate(GetContent(), child);
00769         if (ungenerate) {
00770           PL_InitEvent(ungenerate, nsnull,
00771                        ::HandleASyncUngenerate,
00772                        ::DestroyASyncUngenerate);
00773           if (NS_FAILED(eventQueue->PostEvent(ungenerate))) {
00774             PL_DestroyEvent(ungenerate);
00775           }
00776         }
00777       }
00778     }
00779   }
00780 
00781   return NS_OK;
00782 }
00783 
00784 NS_IMETHODIMP
00785 nsMenuFrame::ActivateMenu(PRBool aActivateFlag)
00786 {
00787   nsIFrame* frame = mPopupFrames.FirstChild();
00788   nsMenuPopupFrame* menuPopup = (nsMenuPopupFrame*)frame;
00789   
00790   if (!menuPopup) 
00791     return NS_OK;
00792 
00793   if (aActivateFlag) {
00794       nsRect rect = menuPopup->GetRect();
00795       nsIView* view = menuPopup->GetView();
00796       nsIViewManager* viewManager = view->GetViewManager();
00797       rect.x = rect.y = 0;
00798       viewManager->ResizeView(view, rect);
00799 
00800       // make sure the scrolled window is at 0,0
00801       if (mLastPref.height <= rect.height) {
00802         nsIBox* child;
00803         menuPopup->GetChildBox(&child);
00804 
00805         nsCOMPtr<nsIScrollableFrame> scrollframe(do_QueryInterface(child));
00806         if (scrollframe) {
00807           scrollframe->ScrollTo(nsPoint(0,0));
00808         }
00809       }
00810 
00811       viewManager->UpdateView(view, rect, NS_VMREFRESH_IMMEDIATE);
00812       viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
00813 
00814   } else {
00815     if (mMenuOpen) {
00816       nsWeakFrame weakFrame(this);
00817       nsWeakFrame weakPopup(menuPopup);
00818       FireDOMEventSynch(NS_LITERAL_STRING("DOMMenuInactive"), menuPopup->GetContent());
00819       NS_ENSURE_TRUE(weakFrame.IsAlive() && weakPopup.IsAlive(), NS_OK);
00820     }
00821     nsIView* view = menuPopup->GetView();
00822     NS_ASSERTION(view, "View is gone, looks like someone forgot to rollup the popup!");
00823     if (view) {
00824       nsIViewManager* viewManager = view->GetViewManager();
00825       if (viewManager) { // the view manager can be null during widget teardown
00826         viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
00827         viewManager->ResizeView(view, nsRect(0, 0, 0, 0));
00828       }
00829     }
00830     // set here so hide chain can close the menu as well.
00831     mMenuOpen = PR_FALSE;
00832   }
00833   
00834   return NS_OK;
00835 }  
00836 
00837 NS_IMETHODIMP
00838 nsMenuFrame::AttributeChanged(nsIContent* aChild,
00839                               PRInt32 aNameSpaceID,
00840                               nsIAtom* aAttribute,
00841                               PRInt32 aModType)
00842 {
00843   nsAutoString value;
00844 
00845   if (aAttribute == nsHTMLAtoms::checked) {
00846     if (mType != eMenuType_Normal)
00847         UpdateMenuSpecialState(GetPresContext());
00848   } else if (aAttribute == nsXULAtoms::acceltext) {
00849     // someone reset the accelText attribute, so clear the bit that says *we* set it
00850     AddStateBits(NS_STATE_ACCELTEXT_IS_DERIVED);
00851     BuildAcceleratorText();
00852   } else if (aAttribute == nsXULAtoms::key) {
00853     BuildAcceleratorText();
00854   } else if ( aAttribute == nsHTMLAtoms::type || aAttribute == nsHTMLAtoms::name )
00855     UpdateMenuType(GetPresContext());
00856 
00857   return NS_OK;
00858 }
00859 
00860 NS_IMETHODIMP
00861 nsMenuFrame::OpenMenu(PRBool aActivateFlag)
00862 {
00863   if (!mContent)
00864     return NS_OK;
00865 
00866   nsWeakFrame weakFrame(this);
00867   if (aActivateFlag) {
00868     // Now that the menu is opened, we should have a menupopup child built.
00869     // Mark it as generated, which ensures a frame gets built.
00870     MarkAsGenerated();
00871     NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
00872 
00873     mContent->SetAttr(kNameSpaceID_None, nsXULAtoms::open, NS_LITERAL_STRING("true"), PR_TRUE);
00874   }
00875   else {
00876     mContent->UnsetAttr(kNameSpaceID_None, nsXULAtoms::open, PR_TRUE);
00877   }
00878 
00879   NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
00880   OpenMenuInternal(aActivateFlag);
00881 
00882   return NS_OK;
00883 }
00884 
00885 void 
00886 nsMenuFrame::OpenMenuInternal(PRBool aActivateFlag) 
00887 {
00888   gEatMouseMove = PR_TRUE;
00889 
00890   if (!mIsMenu)
00891     return;
00892 
00893   nsWeakFrame weakFrame(this);
00894 
00895   if (aActivateFlag) {
00896     // Execute the oncreate handler
00897     if (!OnCreate() || !weakFrame.IsAlive())
00898       return;
00899 
00900     mCreateHandlerSucceeded = PR_TRUE;
00901   
00902     // Set the focus back to our view's widget.
00903     if (nsMenuFrame::sDismissalListener)
00904       nsMenuFrame::sDismissalListener->EnableListener(PR_FALSE);
00905     
00906     // XXX Only have this here because of RDF-generated content.
00907     MarkAsGenerated();
00908     ENSURE_TRUE(weakFrame.IsAlive());
00909 
00910     nsIFrame* frame = mPopupFrames.FirstChild();
00911     nsMenuPopupFrame* menuPopup = (nsMenuPopupFrame*)frame;
00912     
00913     PRBool wasOpen = mMenuOpen;
00914     mMenuOpen = PR_TRUE;
00915 
00916     if (menuPopup) {
00917       nsWeakFrame weakMenuPopup(frame);
00918       // inherit whether or not we're a context menu from the parent
00919       if ( mMenuParent ) {
00920         PRBool parentIsContextMenu = PR_FALSE;
00921         mMenuParent->GetIsContextMenu(parentIsContextMenu);
00922         menuPopup->SetIsContextMenu(parentIsContextMenu);
00923         ENSURE_TRUE(weakFrame.IsAlive());
00924       }
00925 
00926       // Install a keyboard navigation listener if we're the root of the menu chain.
00927       PRBool onMenuBar = PR_TRUE;
00928       if (mMenuParent)
00929         mMenuParent->IsMenuBar(onMenuBar);
00930 
00931       if (mMenuParent && onMenuBar)
00932         mMenuParent->InstallKeyboardNavigator();
00933       else if (!mMenuParent) {
00934         ENSURE_TRUE(weakMenuPopup.IsAlive());
00935         menuPopup->InstallKeyboardNavigator();
00936       }
00937       
00938       // Tell the menu bar we're active.
00939       if (mMenuParent) {
00940         mMenuParent->SetActive(PR_TRUE);
00941         ENSURE_TRUE(weakFrame.IsAlive());
00942       }
00943 
00944       nsIContent* menuPopupContent = menuPopup->GetContent();
00945 
00946       // Sync up the view.
00947       nsAutoString popupAnchor, popupAlign;
00948       
00949       menuPopupContent->GetAttr(kNameSpaceID_None, nsXULAtoms::popupanchor, popupAnchor);
00950       menuPopupContent->GetAttr(kNameSpaceID_None, nsXULAtoms::popupalign, popupAlign);
00951 
00952       ConvertPosition(menuPopupContent, popupAnchor, popupAlign);
00953 
00954       if (onMenuBar) {
00955         if (popupAnchor.IsEmpty())
00956           popupAnchor.AssignLiteral("bottomleft");
00957         if (popupAlign.IsEmpty())
00958           popupAlign.AssignLiteral("topleft");
00959       }
00960       else {
00961         if (popupAnchor.IsEmpty())
00962           popupAnchor.AssignLiteral("topright");
00963         if (popupAlign.IsEmpty())
00964           popupAlign.AssignLiteral("topleft");
00965       }
00966 
00967       nsBoxLayoutState state(mPresContext);
00968 
00969       // If the menu popup was not open, do a reflow.  This is either the
00970       // initial reflow for a brand-new popup, or a subsequent reflow for
00971       // a menu that was deactivated and needs to be brought back to its
00972       // active dimensions.
00973       if (!wasOpen)
00974       {
00975          menuPopup->MarkDirty(state);
00976 
00977          mPresContext->PresShell()->FlushPendingNotifications(Flush_OnlyReflow);
00978       }
00979 
00980       nsRect curRect(menuPopup->GetRect());
00981       menuPopup->SetBounds(state, nsRect(0,0,mLastPref.width, mLastPref.height));
00982 
00983       nsIView* view = menuPopup->GetView();
00984       nsIViewManager* vm = view->GetViewManager();
00985       if (vm) {
00986         vm->SetViewVisibility(view, nsViewVisibility_kHide);
00987       }
00988       menuPopup->SyncViewWithFrame(mPresContext, popupAnchor, popupAlign, this, -1, -1);
00989       nscoord newHeight = menuPopup->GetRect().height;
00990 
00991       // if the height is different then reflow. It might need scrollbars force a reflow
00992       if (curRect.height != newHeight || mLastPref.height != newHeight)
00993       {
00994          menuPopup->MarkDirty(state);
00995          mPresContext->PresShell()->FlushPendingNotifications(Flush_OnlyReflow);
00996       }
00997 
00998       ActivateMenu(PR_TRUE);
00999       ENSURE_TRUE(weakFrame.IsAlive());
01000 
01001       nsIMenuParent *childPopup = nsnull;
01002       CallQueryInterface(frame, &childPopup);
01003       UpdateDismissalListener(childPopup);
01004 
01005       OnCreated();
01006       ENSURE_TRUE(weakFrame.IsAlive());
01007     }
01008 
01009     // Set the focus back to our view's widget.
01010     if (nsMenuFrame::sDismissalListener)
01011       nsMenuFrame::sDismissalListener->EnableListener(PR_TRUE);
01012     
01013   }
01014   else {
01015 
01016     // Close the menu. 
01017     // Execute the ondestroy handler, but only if we're actually open
01018     if ( !mCreateHandlerSucceeded || !OnDestroy() || !weakFrame.IsAlive())
01019       return;
01020 
01021     // Set the focus back to our view's widget.
01022     if (nsMenuFrame::sDismissalListener) {
01023       nsMenuFrame::sDismissalListener->EnableListener(PR_FALSE);
01024       nsMenuFrame::sDismissalListener->SetCurrentMenuParent(mMenuParent);
01025     }
01026 
01027     nsIFrame* frame = mPopupFrames.FirstChild();
01028     nsMenuPopupFrame* menuPopup = (nsMenuPopupFrame*)frame;
01029   
01030     // Make sure we clear out our own items.
01031     if (menuPopup) {
01032       menuPopup->SetCurrentMenuItem(nsnull);
01033       ENSURE_TRUE(weakFrame.IsAlive());
01034       menuPopup->KillCloseTimer();
01035 
01036       PRBool onMenuBar = PR_TRUE;
01037       if (mMenuParent)
01038         mMenuParent->IsMenuBar(onMenuBar);
01039 
01040       if (mMenuParent && onMenuBar)
01041         mMenuParent->RemoveKeyboardNavigator();
01042       else if (!mMenuParent)
01043         menuPopup->RemoveKeyboardNavigator();
01044 
01045       // XXX, bug 137033, In Windows, if mouse is outside the window when the menupopup closes, no
01046       // mouse_enter/mouse_exit event will be fired to clear current hover state, we should clear it manually.
01047       // This code may not the best solution, but we can leave it here untill we find the better approach.
01048 
01049       nsIEventStateManager *esm = mPresContext->EventStateManager();
01050 
01051       PRInt32 state;
01052       esm->GetContentState(menuPopup->GetContent(), state);
01053 
01054       if (state & NS_EVENT_STATE_HOVER)
01055         esm->SetContentState(nsnull, NS_EVENT_STATE_HOVER);
01056     }
01057 
01058     ActivateMenu(PR_FALSE);
01059     ENSURE_TRUE(weakFrame.IsAlive());
01060     // XXX hack: ensure that mMenuOpen is set to false, in case where
01061     // there is actually no popup. because ActivateMenu() will return 
01062     // early without setting it. It could be that mMenuOpen is true
01063     // in that case, because OpenMenuInternal(true) gets called if
01064     // the attribute open="true", whether there is a popup or not.
01065     // We should not allow mMenuOpen unless there is a popup in the first place,
01066     // in which case this line would not be necessary.
01067     mMenuOpen = PR_FALSE;
01068 
01069     OnDestroyed();
01070     ENSURE_TRUE(weakFrame.IsAlive());
01071 
01072     if (nsMenuFrame::sDismissalListener)
01073       nsMenuFrame::sDismissalListener->EnableListener(PR_TRUE);
01074 
01075     mCreateHandlerSucceeded = PR_FALSE;
01076   }
01077 
01078 }
01079 
01080 void
01081 nsMenuFrame::GetMenuChildrenElement(nsIContent** aResult)
01082 {
01083   if (!mContent)
01084   {
01085     *aResult = nsnull;
01086     return;
01087   }
01088   
01089   nsresult rv;
01090   nsCOMPtr<nsIXBLService> xblService = 
01091            do_GetService("@mozilla.org/xbl;1", &rv);
01092   PRInt32 dummy;
01093   PRUint32 count = mContent->GetChildCount();
01094 
01095   for (PRUint32 i = 0; i < count; i++) {
01096     nsIContent *child = mContent->GetChildAt(i);
01097     nsCOMPtr<nsIAtom> tag;
01098     xblService->ResolveTag(child, &dummy, getter_AddRefs(tag));
01099     if (tag == nsXULAtoms::menupopup) {
01100       *aResult = child;
01101       NS_ADDREF(*aResult);
01102       return;
01103     }
01104   }
01105 }
01106 
01107 PRBool
01108 nsMenuFrame::IsSizedToPopup(nsIContent* aContent, PRBool aRequireAlways)
01109 {
01110   PRBool sizeToPopup;
01111   if (aContent->Tag() == nsHTMLAtoms::select)
01112     sizeToPopup = PR_TRUE;
01113   else {
01114     nsAutoString sizedToPopup;
01115     aContent->GetAttr(kNameSpaceID_None, nsXULAtoms::sizetopopup, sizedToPopup);
01116     sizeToPopup = sizedToPopup.EqualsLiteral("always") ||
01117                   !aRequireAlways && sizedToPopup.EqualsLiteral("pref");
01118   }
01119   
01120   return sizeToPopup;
01121 }
01122 
01123 NS_IMETHODIMP
01124 nsMenuFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState, nsSize& aSize)
01125 {
01126   nsresult rv = nsBoxFrame::GetMinSize(aBoxLayoutState, aSize);
01127 
01128   if (IsSizedToPopup(mContent, PR_TRUE))
01129     SizeToPopup(aBoxLayoutState, aSize);
01130 
01131   return rv;
01132 }
01133 
01134 NS_IMETHODIMP
01135 nsMenuFrame::DoLayout(nsBoxLayoutState& aState)
01136 {
01137   nsRect contentRect;
01138   GetContentRect(contentRect);
01139 
01140   // lay us out
01141   nsresult rv = nsBoxFrame::DoLayout(aState);
01142 
01143   // layout the popup. First we need to get it.
01144   nsIFrame* popupChild = mPopupFrames.FirstChild();
01145 
01146   if (popupChild) {
01147     PRBool sizeToPopup = IsSizedToPopup(mContent, PR_FALSE);
01148     
01149     NS_ASSERTION(popupChild->IsBoxFrame(), "popupChild is not box!!");
01150 
01151     // then get its preferred size
01152     nsSize prefSize(0,0);
01153     nsSize minSize(0,0);
01154     nsSize maxSize(0,0);
01155 
01156     popupChild->GetPrefSize(aState, prefSize);
01157     popupChild->GetMinSize(aState, minSize);
01158     popupChild->GetMaxSize(aState, maxSize);
01159 
01160     BoundsCheck(minSize, prefSize, maxSize);
01161 
01162     if (sizeToPopup)
01163         prefSize.width = contentRect.width;
01164 
01165     // if the pref size changed then set bounds to be the pref size
01166     // and sync the view. And set new pref size.
01167     if (mLastPref != prefSize) {
01168       popupChild->SetBounds(aState, nsRect(0,0,prefSize.width, prefSize.height));
01169       RePositionPopup(aState);
01170       mLastPref = prefSize;
01171     }
01172 
01173     // is the new size too small? Make sure we handle scrollbars correctly
01174     nsIBox* child;
01175     popupChild->GetChildBox(&child);
01176 
01177     nsRect bounds(popupChild->GetRect());
01178 
01179     nsCOMPtr<nsIScrollableFrame> scrollframe(do_QueryInterface(child));
01180     if (scrollframe &&
01181         scrollframe->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_AUTO) {
01182       if (bounds.height < prefSize.height) {
01183         // layout the child
01184         popupChild->Layout(aState);
01185 
01186         nsMargin scrollbars = scrollframe->GetActualScrollbarSizes();
01187         if (bounds.width < prefSize.width + scrollbars.left + scrollbars.right)
01188         {
01189           bounds.width += scrollbars.left + scrollbars.right;
01190           //printf("Width=%d\n",width);
01191           popupChild->SetBounds(aState, bounds);
01192         }
01193       }
01194     }
01195     
01196     // layout the child
01197     popupChild->Layout(aState);
01198 
01199     // Only size the popups view if open.
01200     if (mMenuOpen) {
01201       nsIView* view = popupChild->GetView();
01202       nsRect r(0, 0, bounds.width, bounds.height);
01203       view->GetViewManager()->ResizeView(view, r);
01204     }
01205 
01206   }
01207 
01208   SyncLayout(aState);
01209 
01210   return rv;
01211 }
01212 
01213 NS_IMETHODIMP
01214 nsMenuFrame::MarkChildrenStyleChange()  
01215 {
01216   nsresult rv = nsBoxFrame::MarkChildrenStyleChange();
01217   if (NS_FAILED(rv))
01218     return rv;
01219    
01220   nsIFrame* popupChild = mPopupFrames.FirstChild();
01221 
01222   if (popupChild) {
01223     NS_ASSERTION(popupChild->IsBoxFrame(), "popupChild is not box!!");
01224     return popupChild->MarkChildrenStyleChange();
01225   }
01226 
01227   return rv;
01228 }
01229 
01230 #ifdef DEBUG_LAYOUT
01231 NS_IMETHODIMP
01232 nsMenuFrame::SetDebug(nsBoxLayoutState& aState, PRBool aDebug)
01233 {
01234   // see if our state matches the given debug state
01235   PRBool debugSet = mState & NS_STATE_CURRENTLY_IN_DEBUG;
01236   PRBool debugChanged = (!aDebug && debugSet) || (aDebug && !debugSet);
01237 
01238   // if it doesn't then tell each child below us the new debug state
01239   if (debugChanged)
01240   {
01241       nsBoxFrame::SetDebug(aState, aDebug);
01242       SetDebug(aState, mPopupFrames.FirstChild(), aDebug);
01243   }
01244 
01245   return NS_OK;
01246 }
01247 
01248 nsresult
01249 nsMenuFrame::SetDebug(nsBoxLayoutState& aState, nsIFrame* aList, PRBool aDebug)
01250 {
01251       if (!aList)
01252           return NS_OK;
01253 
01254       while (aList) {
01255         if (aList->IsBoxFrame())
01256           aList->SetDebug(aState, aDebug);
01257 
01258         aList = aList->GetNextSibling();
01259       }
01260 
01261       return NS_OK;
01262 }
01263 #endif
01264 
01265 void
01266 nsMenuFrame::ConvertPosition(nsIContent* aPopupElt, nsString& aAnchor, nsString& aAlign)
01267 {
01268   nsAutoString position;
01269   aPopupElt->GetAttr(kNameSpaceID_None, nsXULAtoms::position, position);
01270   if (position.IsEmpty())
01271     return;
01272 
01273   if (position.EqualsLiteral("before_start")) {
01274     aAnchor.AssignLiteral("topleft");
01275     aAlign.AssignLiteral("bottomleft");
01276   }
01277   else if (position.EqualsLiteral("before_end")) {
01278     aAnchor.AssignLiteral("topright");
01279     aAlign.AssignLiteral("bottomright");
01280   }
01281   else if (position.EqualsLiteral("after_start")) {
01282     aAnchor.AssignLiteral("bottomleft");
01283     aAlign.AssignLiteral("topleft");
01284   }
01285   else if (position.EqualsLiteral("after_end")) {
01286     aAnchor.AssignLiteral("bottomright");
01287     aAlign.AssignLiteral("topright");
01288   }
01289   else if (position.EqualsLiteral("start_before")) {
01290     aAnchor.AssignLiteral("topleft");
01291     aAlign.AssignLiteral("topright");
01292   }
01293   else if (position.EqualsLiteral("start_after")) {
01294     aAnchor.AssignLiteral("bottomleft");
01295     aAlign.AssignLiteral("bottomright");
01296   }
01297   else if (position.EqualsLiteral("end_before")) {
01298     aAnchor.AssignLiteral("topright");
01299     aAlign.AssignLiteral("topleft");
01300   }
01301   else if (position.EqualsLiteral("end_after")) {
01302     aAnchor.AssignLiteral("bottomright");
01303     aAlign.AssignLiteral("bottomleft");
01304   }
01305   else if (position.EqualsLiteral("overlap")) {
01306     aAnchor.AssignLiteral("topleft");
01307     aAlign.AssignLiteral("topleft");
01308   }
01309 }
01310 
01311 void
01312 nsMenuFrame::RePositionPopup(nsBoxLayoutState& aState)
01313 {  
01314   nsPresContext* presContext = aState.PresContext();
01315 
01316   // Sync up the view.
01317   nsIFrame* frame = mPopupFrames.FirstChild();
01318   nsMenuPopupFrame* menuPopup = (nsMenuPopupFrame*)frame;
01319   if (mMenuOpen && menuPopup) {
01320     nsIContent* menuPopupContent = menuPopup->GetContent();
01321     nsAutoString popupAnchor, popupAlign;
01322       
01323     menuPopupContent->GetAttr(kNameSpaceID_None, nsXULAtoms::popupanchor, popupAnchor);
01324     menuPopupContent->GetAttr(kNameSpaceID_None, nsXULAtoms::popupalign, popupAlign);
01325 
01326     ConvertPosition(menuPopupContent, popupAnchor, popupAlign);
01327 
01328     PRBool onMenuBar = PR_TRUE;
01329     if (mMenuParent)
01330       mMenuParent->IsMenuBar(onMenuBar);
01331 
01332     if (onMenuBar) {
01333       if (popupAnchor.IsEmpty())
01334           popupAnchor.AssignLiteral("bottomleft");
01335       if (popupAlign.IsEmpty())
01336           popupAlign.AssignLiteral("topleft");
01337     }
01338     else {
01339       if (popupAnchor.IsEmpty())
01340         popupAnchor.AssignLiteral("topright");
01341       if (popupAlign.IsEmpty())
01342         popupAlign.AssignLiteral("topleft");
01343     }
01344 
01345     menuPopup->SyncViewWithFrame(presContext, popupAnchor, popupAlign, this, -1, -1);
01346   }
01347 }
01348 
01349 NS_IMETHODIMP
01350 nsMenuFrame::ShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, PRBool& aHandledFlag)
01351 {
01352   nsIFrame* frame = mPopupFrames.FirstChild();
01353   if (frame) {
01354     nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame;
01355     popup->ShortcutNavigation(aKeyEvent, aHandledFlag);
01356   } 
01357 
01358   return NS_OK;
01359 }
01360 
01361 NS_IMETHODIMP
01362 nsMenuFrame::KeyboardNavigation(PRUint32 aKeyCode, PRBool& aHandledFlag)
01363 {
01364   nsIFrame* frame = mPopupFrames.FirstChild();
01365   if (frame) {
01366     nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame;
01367     popup->KeyboardNavigation(aKeyCode, aHandledFlag);
01368   }
01369 
01370   return NS_OK;
01371 }
01372 
01373 NS_IMETHODIMP
01374 nsMenuFrame::Escape(PRBool& aHandledFlag)
01375 {
01376   if (mMenuParent) {
01377     mMenuParent->ClearRecentlyRolledUp();
01378   }
01379   nsIFrame* frame = mPopupFrames.FirstChild();
01380   if (frame) {
01381     nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame;
01382     popup->Escape(aHandledFlag);
01383   }
01384 
01385   return NS_OK;
01386 }
01387 
01388 
01389 //
01390 // Enter
01391 //
01392 // Called when the user hits the <Enter>/<Return> keys or presses the
01393 // shortcut key. If this is a leaf item, the item's action will be executed.
01394 // If it is a submenu parent, open the submenu and select the first time.
01395 // In either case, do nothing if the item is disabled.
01396 //
01397 NS_IMETHODIMP
01398 nsMenuFrame::Enter()
01399 {
01400   if (IsDisabled()) {
01401 #ifdef XP_WIN
01402     // behavior on Windows - close the popup chain
01403     if (mMenuParent)
01404       mMenuParent->DismissChain();
01405 #endif   // #ifdef XP_WIN
01406     // this menu item was disabled - exit
01407     return NS_OK;
01408   }
01409     
01410   if (!mMenuOpen) {
01411     // The enter key press applies to us.
01412     if (!IsMenu() && mMenuParent)
01413       Execute(0);          // Execute our event handler
01414     else {
01415       OpenMenu(PR_TRUE);
01416       SelectFirstItem();
01417     }
01418 
01419     return NS_OK;
01420   }
01421 
01422   nsIFrame* frame = mPopupFrames.FirstChild();
01423   if (frame) {
01424     nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame;
01425     popup->Enter();
01426   }
01427 
01428   return NS_OK;
01429 }
01430 
01431 NS_IMETHODIMP
01432 nsMenuFrame::SelectFirstItem()
01433 {
01434   nsIFrame* frame = mPopupFrames.FirstChild();
01435   if (frame) {
01436     nsMenuPopupFrame* popup = (nsMenuPopupFrame*)frame;
01437     popup->SetCurrentMenuItem(popup->GetNextMenuItem(nsnull));
01438   }
01439 
01440   return NS_OK;
01441 }
01442 
01443 PRBool
01444 nsMenuFrame::IsMenu()
01445 {
01446   return mIsMenu;
01447 }
01448 
01449 nsresult
01450 nsMenuFrame::Notify(nsITimer* aTimer)
01451 {
01452   // Our timer has fired.
01453   if (aTimer == mOpenTimer.get()) {
01454     if (!mMenuOpen && mMenuParent) {
01455       // make sure we didn't open a context menu in the meantime
01456       // (i.e. the user right-clicked while hovering over a submenu).
01457       // However, also make sure that we're not the context menu itself,
01458       // to allow context submenus to open.
01459       nsIMenuParent *ctxMenu = nsMenuFrame::GetContextMenu();
01460       PRBool parentIsContextMenu = PR_FALSE;
01461 
01462       if (ctxMenu)
01463         mMenuParent->GetIsContextMenu(parentIsContextMenu);
01464 
01465       if (ctxMenu == nsnull || parentIsContextMenu) {
01466         nsAutoString active;
01467         mContent->GetAttr(kNameSpaceID_None, nsXULAtoms::menuactive, active);
01468         if (active.Equals(NS_LITERAL_STRING("true"))) {
01469           // We're still the active menu. Make sure all submenus/timers are closed
01470           // before opening this one
01471           mMenuParent->KillPendingTimers();
01472           OpenMenu(PR_TRUE);
01473         }
01474       }
01475     }
01476     mOpenTimer->Cancel();
01477     mOpenTimer = nsnull;
01478   }
01479   
01480   mOpenTimer = nsnull;
01481   return NS_OK;
01482 }
01483 
01484 PRBool 
01485 nsMenuFrame::IsDisabled()
01486 {
01487   nsAutoString disabled;
01488   mContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::disabled, disabled);
01489   if (disabled.EqualsLiteral("true"))
01490     return PR_TRUE;
01491   return PR_FALSE;
01492 }
01493 
01494 void
01495 nsMenuFrame::UpdateMenuType(nsPresContext* aPresContext)
01496 {
01497   nsAutoString value;
01498   mContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::type, value);
01499   if (value.EqualsLiteral("checkbox"))
01500     mType = eMenuType_Checkbox;
01501   else if (value.EqualsLiteral("radio")) {
01502     mType = eMenuType_Radio;
01503     mContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::name, mGroupName);
01504   } 
01505   else {
01506     if (mType != eMenuType_Normal) {
01507       nsWeakFrame weakFrame(this);
01508       mContent->UnsetAttr(kNameSpaceID_None, nsHTMLAtoms::checked,
01509                           PR_TRUE);
01510       ENSURE_TRUE(weakFrame.IsAlive());
01511     }
01512     mType = eMenuType_Normal;
01513   }
01514   UpdateMenuSpecialState(aPresContext);
01515 }
01516 
01517 /* update checked-ness for type="checkbox" and type="radio" */
01518 void
01519 nsMenuFrame::UpdateMenuSpecialState(nsPresContext* aPresContext) {
01520   nsAutoString value;
01521   PRBool newChecked;
01522 
01523   mContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::checked,
01524                     value);
01525   newChecked = (value.EqualsLiteral("true"));
01526 
01527   if (newChecked == mChecked) {
01528     /* checked state didn't change */
01529 
01530     if (mType != eMenuType_Radio)
01531       return; // only Radio possibly cares about other kinds of change
01532 
01533     if (!mChecked || mGroupName.IsEmpty())
01534       return;                   // no interesting change
01535   } else { 
01536     mChecked = newChecked;
01537     if (mType != eMenuType_Radio || !mChecked)
01538       /*
01539        * Unchecking something requires no further changes, and only
01540        * menuRadio has to do additional work when checked.
01541        */
01542       return;
01543   }
01544 
01545   /*
01546    * If we get this far, we're type=radio, and:
01547    * - our name= changed, or
01548    * - we went from checked="false" to checked="true"
01549    */
01550 
01551   /*
01552    * Behavioural note:
01553    * If we're checked and renamed _into_ an existing radio group, we are
01554    * made the new checked item, and we unselect the previous one.
01555    *
01556    * The only other reasonable behaviour would be to check for another selected
01557    * item in that group.  If found, unselect ourselves, otherwise we're the
01558    * selected item.  That, however, would be a lot more work, and I don't think
01559    * it's better at all.
01560    */
01561 
01562   /* walk siblings, looking for the other checked item with the same name */
01563   nsIMenuFrame *sibMenu;
01564   nsMenuType sibType;
01565   nsAutoString sibGroup;
01566   PRBool sibChecked;
01567   
01568   // get the first sibling in this menu popup. This frame may be it, and if we're
01569   // being called at creation time, this frame isn't yet in the parent's child list.
01570   // All I'm saying is that this may fail, but it's most likely alright.
01571   nsIFrame* sib = GetParent()->GetFirstChild(nsnull);
01572   if ( !sib )
01573     return;
01574 
01575   // XXX - egcs 1.1.2 & gcc 2.95.x -Oy builds, where y > 1, 
01576   // are known to break if we declare nsCOMPtrs inside this loop.  
01577   // Moving the declaration out of the loop works around this problem.
01578   // http://bugzilla.mozilla.org/show_bug.cgi?id=80988
01579 
01580   do {
01581     if (NS_FAILED(sib->QueryInterface(NS_GET_IID(nsIMenuFrame),
01582                                       (void **)&sibMenu)))
01583         continue;
01584         
01585     if (sibMenu != (nsIMenuFrame *)this &&        // correct way to check?
01586         (sibMenu->GetMenuType(sibType), sibType == eMenuType_Radio) &&
01587         (sibMenu->MenuIsChecked(sibChecked), sibChecked) &&
01588         (sibMenu->GetRadioGroupName(sibGroup), sibGroup == mGroupName)) {
01589       
01590       /* uncheck the old item */
01591       sib->GetContent()->UnsetAttr(kNameSpaceID_None, nsHTMLAtoms::checked,
01592                                    PR_TRUE);
01593 
01594       /* XXX in DEBUG, check to make sure that there aren't two checked items */
01595       return;
01596     }
01597 
01598   } while ((sib = sib->GetNextSibling()) != nsnull);
01599 
01600 }
01601 
01602 void 
01603 nsMenuFrame::BuildAcceleratorText()
01604 {
01605   nsAutoString accelText;
01606 
01607   if ((GetStateBits() & NS_STATE_ACCELTEXT_IS_DERIVED) == 0) {
01608     mContent->GetAttr(kNameSpaceID_None, nsXULAtoms::acceltext, accelText);
01609     if (!accelText.IsEmpty())
01610       return;
01611   }
01612   // accelText is definitely empty here.
01613 
01614   // Now we're going to compute the accelerator text, so remember that we did.
01615   AddStateBits(NS_STATE_ACCELTEXT_IS_DERIVED);
01616 
01617   // If anything below fails, just leave the accelerator text blank.
01618   nsWeakFrame weakFrame(this);
01619   mContent->UnsetAttr(kNameSpaceID_None, nsXULAtoms::acceltext, PR_FALSE);
01620   ENSURE_TRUE(weakFrame.IsAlive());
01621 
01622   // See if we have a key node and use that instead.
01623   nsAutoString keyValue;
01624   mContent->GetAttr(kNameSpaceID_None, nsXULAtoms::key, keyValue);
01625   if (keyValue.IsEmpty())
01626     return;
01627 
01628   // Turn the document into a DOM document so we can use getElementById
01629   nsCOMPtr<nsIDOMDocument> domDocument(do_QueryInterface(mContent->GetDocument()));
01630   if (!domDocument)
01631     return;
01632 
01633   nsCOMPtr<nsIDOMElement> keyDOMElement;
01634   domDocument->GetElementById(keyValue, getter_AddRefs(keyDOMElement));
01635   if (!keyDOMElement)
01636     return;
01637 
01638   nsCOMPtr<nsIContent> keyElement(do_QueryInterface(keyDOMElement));
01639   if (!keyElement)
01640     return;
01641 
01642   // get the string to display as accelerator text
01643   // check the key element's attributes in this order:
01644   // |keytext|, |key|, |keycode|
01645   nsAutoString accelString;
01646   keyElement->GetAttr(kNameSpaceID_None, nsXULAtoms::keytext, accelString);
01647 
01648   if (accelString.IsEmpty()) {
01649     keyElement->GetAttr(kNameSpaceID_None, nsXULAtoms::key, accelString);
01650 
01651     if (!accelString.IsEmpty()) {
01652       ToUpperCase(accelString);
01653     } else {
01654       nsAutoString keyCode;
01655       keyElement->GetAttr(kNameSpaceID_None, nsXULAtoms::keycode, keyCode);
01656       ToUpperCase(keyCode);
01657 
01658       nsresult rv;
01659       nsCOMPtr<nsIStringBundleService> bundleService(do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv));
01660       if (NS_SUCCEEDED(rv) && bundleService) {
01661         nsCOMPtr<nsIStringBundle> bundle;
01662         rv = bundleService->CreateBundle("chrome://global/locale/keys.properties",
01663                                          getter_AddRefs(bundle));
01664 
01665         if (NS_SUCCEEDED(rv) && bundle) {
01666           nsXPIDLString keyName;
01667           rv = bundle->GetStringFromName(keyCode.get(), getter_Copies(keyName));
01668           if (keyName)
01669             accelString = keyName;
01670         }
01671       }
01672 
01673       // nothing usable found, bail
01674       if (accelString.IsEmpty())
01675         return;
01676     }
01677   }
01678 
01679   static PRInt32 accelKey = 0;
01680 
01681   if (!accelKey)
01682   {
01683     // Compiled-in defaults, in case we can't get LookAndFeel --
01684     // command for mac, control for all other platforms.
01685 #if defined(XP_MAC) || defined(XP_MACOSX)
01686     accelKey = nsIDOMKeyEvent::DOM_VK_META;
01687 #else
01688     accelKey = nsIDOMKeyEvent::DOM_VK_CONTROL;
01689 #endif
01690 
01691     // Get the accelerator key value from prefs, overriding the default:
01692     accelKey = nsContentUtils::GetIntPref("ui.key.accelKey", accelKey);
01693   }
01694 
01695   nsAutoString modifiers;
01696   keyElement->GetAttr(kNameSpaceID_None, nsXULAtoms::modifiers, modifiers);
01697   
01698   char* str = ToNewCString(modifiers);
01699   char* newStr;
01700   char* token = nsCRT::strtok(str, ", ", &newStr);
01701   while (token) {
01702       
01703     if (PL_strcmp(token, "shift") == 0)
01704       accelText += *gShiftText;
01705     else if (PL_strcmp(token, "alt") == 0) 
01706       accelText += *gAltText; 
01707     else if (PL_strcmp(token, "meta") == 0) 
01708       accelText += *gMetaText; 
01709     else if (PL_strcmp(token, "control") == 0) 
01710       accelText += *gControlText; 
01711     else if (PL_strcmp(token, "accel") == 0) {
01712       switch (accelKey)
01713       {
01714         case nsIDOMKeyEvent::DOM_VK_META:
01715           accelText += *gMetaText;
01716           break;
01717 
01718         case nsIDOMKeyEvent::DOM_VK_ALT:
01719           accelText += *gAltText;
01720           break;
01721 
01722         case nsIDOMKeyEvent::DOM_VK_CONTROL:
01723         default:
01724           accelText += *gControlText;
01725           break;
01726       }
01727     }
01728     
01729     accelText += *gModifierSeparator;
01730 
01731     token = nsCRT::strtok(newStr, ", ", &newStr);
01732   }
01733 
01734   nsMemory::Free(str);
01735 
01736   accelText += accelString;
01737   
01738   mContent->SetAttr(kNameSpaceID_None, nsXULAtoms::acceltext, accelText, PR_FALSE);
01739 }
01740 
01741 void
01742 nsMenuFrame::Execute(nsGUIEvent *aEvent)
01743 {
01744   nsWeakFrame weakFrame(this);
01745   // flip "checked" state if we're a checkbox menu, or an un-checked radio menu
01746   if (mType == eMenuType_Checkbox || (mType == eMenuType_Radio && !mChecked)) {
01747     nsAutoString value;
01748     mContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::autocheck, value);
01749     if (!value.EqualsLiteral("false")) {
01750       if (mChecked) {
01751         mContent->UnsetAttr(kNameSpaceID_None, nsHTMLAtoms::checked,
01752                             PR_TRUE);
01753         ENSURE_TRUE(weakFrame.IsAlive());
01754       }
01755       else {
01756         mContent->SetAttr(kNameSpaceID_None, nsHTMLAtoms::checked, NS_LITERAL_STRING("true"),
01757                           PR_TRUE);
01758         ENSURE_TRUE(weakFrame.IsAlive());
01759       }        
01760       /* the AttributeChanged code will update all the internal state */
01761     }
01762   }
01763 
01764   // Temporarily disable rollup events on this menu.  This is
01765   // to suppress this menu getting removed in the case where
01766   // the oncommand handler opens a dialog, etc.
01767   if ( nsMenuFrame::sDismissalListener ) {
01768     nsMenuFrame::sDismissalListener->EnableListener(PR_FALSE);
01769   }
01770 
01771   // Get our own content node and hold on to it to keep it from going away.
01772   nsCOMPtr<nsIContent> content = mContent;
01773 
01774   // Deselect ourselves.
01775   SelectMenu(PR_FALSE);
01776   ENSURE_TRUE(weakFrame.IsAlive());
01777 
01778   // Now hide all of the open menus.
01779   if (mMenuParent) {
01780     mMenuParent->HideChain();
01781 
01782     // Since menu was not dismissed via click outside menu
01783     // we don't want to keep track of this rollup.
01784     // Otherwise, we keep track so that the same click 
01785     // won't both dismiss and then reopen a menu.
01786     mMenuParent->ClearRecentlyRolledUp();
01787   }
01788 
01789 
01790   nsEventStatus status = nsEventStatus_eIgnore;
01791   // Create a trusted event if the triggering event was trusted, or if
01792   // we're called from chrome code (since at least one of our caller
01793   // passes in a null event).
01794   nsXULCommandEvent event(aEvent ? NS_IS_TRUSTED_EVENT(aEvent) :
01795                           nsContentUtils::IsCallerChrome(), NS_XUL_COMMAND,
01796                           nsnull);
01797   if (aEvent && (aEvent->eventStructType == NS_MOUSE_EVENT ||
01798                  aEvent->eventStructType == NS_KEY_EVENT ||
01799                  aEvent->eventStructType == NS_ACCESSIBLE_EVENT)) {
01800 
01801     event.isShift = NS_STATIC_CAST(nsInputEvent *, aEvent)->isShift;
01802     event.isControl = NS_STATIC_CAST(nsInputEvent *, aEvent)->isControl;
01803     event.isAlt = NS_STATIC_CAST(nsInputEvent *, aEvent)->isAlt;
01804     event.isMeta = NS_STATIC_CAST(nsInputEvent *, aEvent)->isMeta;
01805   }
01806 
01807   // The order of the nsIViewManager and nsIPresShell COM pointers is
01808   // important below.  We want the pres shell to get released before the
01809   // associated view manager on exit from this function.
01810   // See bug 54233.
01811   nsCOMPtr<nsIViewManager> kungFuDeathGrip = mPresContext->GetViewManager();
01812   nsCOMPtr<nsIPresShell> shell = mPresContext->GetPresShell();
01813   if (shell) {
01814     shell->HandleDOMEventWithTarget(mContent, &event, &status);
01815     ENSURE_TRUE(weakFrame.IsAlive());
01816   }
01817 
01818   if (mMenuParent) {
01819     mMenuParent->DismissChain();
01820   }
01821 
01822   // Re-enable rollup events on this menu.
01823   if ( nsMenuFrame::sDismissalListener ) {
01824        nsMenuFrame::sDismissalListener->EnableListener(PR_TRUE);
01825   }
01826 }
01827 
01828 PRBool
01829 nsMenuFrame::OnCreate()
01830 {
01831   nsEventStatus status = nsEventStatus_eIgnore;
01832   nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_SHOWING, nsnull,
01833                      nsMouseEvent::eReal);
01834 
01835   nsCOMPtr<nsIContent> child;
01836   GetMenuChildrenElement(getter_AddRefs(child));
01837   
01838   nsresult rv = NS_OK;
01839 
01840   nsCOMPtr<nsIPresShell> shell = mPresContext->GetPresShell();
01841   if (shell) {
01842     if (child) {
01843       rv = shell->HandleDOMEventWithTarget(child, &event, &status);
01844     }
01845     else {
01846       rv = shell->HandleDOMEventWithTarget(mContent, &event, &status);
01847     }
01848   }
01849 
01850   if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault )
01851     return PR_FALSE;
01852 
01853   // The menu is going to show, and the create handler has executed.
01854   // We should now walk all of our menu item children, checking to see if any
01855   // of them has a command attribute.  If so, then several attributes must
01856   // potentially be updated.
01857   if (child) {
01858     nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(child->GetDocument()));
01859 
01860     PRUint32 count = child->GetChildCount();
01861     for (PRUint32 i = 0; i < count; i++) {
01862       nsCOMPtr<nsIContent> grandChild = child->GetChildAt(i);
01863 
01864       if (grandChild->Tag() == nsXULAtoms::menuitem) {
01865         // See if we have a command attribute.
01866         nsAutoString command;
01867         grandChild->GetAttr(kNameSpaceID_None, nsXULAtoms::command, command);
01868         if (!command.IsEmpty()) {
01869           // We do! Look it up in our document
01870           nsCOMPtr<nsIDOMElement> commandElt;
01871           domDoc->GetElementById(command, getter_AddRefs(commandElt));
01872           nsCOMPtr<nsIContent> commandContent(do_QueryInterface(commandElt));
01873 
01874           if ( commandContent ) {
01875             nsAutoString commandAttr, menuAttr;
01876             commandContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::disabled, commandAttr);
01877             grandChild->GetAttr(kNameSpaceID_None, nsHTMLAtoms::disabled, menuAttr);
01878             if (!commandAttr.Equals(menuAttr)) {
01879               // The menu's disabled state needs to be updated to match the command.
01880               if (commandAttr.IsEmpty()) 
01881                 grandChild->UnsetAttr(kNameSpaceID_None, nsHTMLAtoms::disabled, PR_TRUE);
01882               else grandChild->SetAttr(kNameSpaceID_None, nsHTMLAtoms::disabled, commandAttr, PR_TRUE);
01883             }
01884 
01885             // The menu's label, accesskey, and checked states need to be updated to match the command.
01886             // Note that (unlike the disabled state) if the command has *no* label for either, we
01887             // assume the menu is supplying its own.
01888             commandContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::checked, commandAttr);
01889             grandChild->GetAttr(kNameSpaceID_None, nsHTMLAtoms::checked, menuAttr);
01890             if (!commandAttr.Equals(menuAttr)) {
01891               if (!commandAttr.IsEmpty()) 
01892                 grandChild->SetAttr(kNameSpaceID_None, nsHTMLAtoms::checked, commandAttr, PR_TRUE);
01893             }
01894 
01895             commandContent->GetAttr(kNameSpaceID_None, nsHTMLAtoms::accesskey, commandAttr);
01896             grandChild->GetAttr(kNameSpaceID_None, nsHTMLAtoms::accesskey, menuAttr);
01897             if (!commandAttr.Equals(menuAttr)) {
01898               if (!commandAttr.IsEmpty()) 
01899                 grandChild->SetAttr(kNameSpaceID_None, nsHTMLAtoms::accesskey, commandAttr, PR_TRUE);
01900             }
01901 
01902             commandContent->GetAttr(kNameSpaceID_None, nsXULAtoms::label, commandAttr);
01903             grandChild->GetAttr(kNameSpaceID_None, nsXULAtoms::label, menuAttr);
01904             if (!commandAttr.Equals(menuAttr)) {
01905               if (!commandAttr.IsEmpty()) 
01906                 grandChild->SetAttr(kNameSpaceID_None, nsXULAtoms::label, commandAttr, PR_TRUE);
01907             }
01908           }
01909         }
01910       }
01911     }
01912   }
01913 
01914   return PR_TRUE;
01915 }
01916 
01917 PRBool
01918 nsMenuFrame::OnCreated()
01919 {
01920   nsEventStatus status = nsEventStatus_eIgnore;
01921   nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_SHOWN, nsnull,
01922                      nsMouseEvent::eReal);
01923 
01924   nsCOMPtr<nsIContent> child;
01925   GetMenuChildrenElement(getter_AddRefs(child));
01926   
01927   nsresult rv = NS_OK;
01928   nsCOMPtr<nsIPresShell> shell = mPresContext->GetPresShell();
01929   if (shell) {
01930     if (child) {
01931       rv = shell->HandleDOMEventWithTarget(child, &event, &status);
01932     }
01933     else {
01934       rv = shell->HandleDOMEventWithTarget(mContent, &event, &status);
01935     }
01936   }
01937 
01938   if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault )
01939     return PR_FALSE;
01940   return PR_TRUE;
01941 }
01942 
01943 PRBool
01944 nsMenuFrame::OnDestroy()
01945 {
01946   nsEventStatus status = nsEventStatus_eIgnore;
01947   nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_HIDING, nsnull,
01948                      nsMouseEvent::eReal);
01949 
01950   nsCOMPtr<nsIContent> child;
01951   GetMenuChildrenElement(getter_AddRefs(child));
01952   
01953   nsresult rv = NS_OK;
01954   nsCOMPtr<nsIPresShell> shell = mPresContext->GetPresShell();
01955   if (shell) {
01956     if (child) {
01957       rv = shell->HandleDOMEventWithTarget(child, &event, &status);
01958     }
01959     else {
01960       rv = shell->HandleDOMEventWithTarget(mContent, &event, &status);
01961     }
01962   }
01963 
01964   if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault )
01965     return PR_FALSE;
01966   return PR_TRUE;
01967 }
01968 
01969 PRBool
01970 nsMenuFrame::OnDestroyed()
01971 {
01972   nsEventStatus status = nsEventStatus_eIgnore;
01973   nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_HIDDEN, nsnull,
01974                      nsMouseEvent::eReal);
01975 
01976   nsCOMPtr<nsIContent> child;
01977   GetMenuChildrenElement(getter_AddRefs(child));
01978   
01979   nsresult rv = NS_OK;
01980   nsCOMPtr<nsIPresShell> shell = mPresContext->GetPresShell();
01981   if (shell) {
01982     if (child) {
01983       rv = shell->HandleDOMEventWithTarget(child, &event, &status);
01984     }
01985     else {
01986       rv = shell->HandleDOMEventWithTarget(mContent, &event, &status);
01987     }
01988   }
01989 
01990   if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault )
01991     return PR_FALSE;
01992   return PR_TRUE;
01993 }
01994 
01995 NS_IMETHODIMP
01996 nsMenuFrame::RemoveFrame(nsIAtom*        aListName,
01997                          nsIFrame*       aOldFrame)
01998 {
01999   nsresult  rv;
02000 
02001   if (mPopupFrames.ContainsFrame(aOldFrame)) {
02002     // Go ahead and remove this frame.
02003     nsPresContext* presContext = GetPresContext();
02004     mPopupFrames.DestroyFrame(presContext, aOldFrame);
02005     nsBoxLayoutState state(presContext);
02006     rv = MarkDirtyChildren(state);
02007   } else {
02008     rv = nsBoxFrame::RemoveFrame(aListName, aOldFrame);
02009   }
02010 
02011   return rv;
02012 }
02013 
02014 NS_IMETHODIMP
02015 nsMenuFrame::InsertFrames(nsIAtom*        aListName,
02016                           nsIFrame*       aPrevFrame,
02017                           nsIFrame*       aFrameList)
02018 {
02019   nsresult          rv;
02020 
02021   nsIMenuParent *menuPar;
02022   if (aFrameList && NS_SUCCEEDED(CallQueryInterface(aFrameList, &menuPar))) {
02023     NS_ASSERTION(aFrameList->IsBoxFrame(),"Popup is not a box!!!");
02024     mPopupFrames.InsertFrames(nsnull, nsnull, aFrameList);
02025 
02026     nsBoxLayoutState state(GetPresContext());
02027 #ifdef DEBUG_LAYOUT
02028     SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG);
02029 #endif
02030     rv = MarkDirtyChildren(state);
02031   } else {
02032     rv = nsBoxFrame::InsertFrames(aListName, aPrevFrame, aFrameList);  
02033   }
02034 
02035   return rv;
02036 }
02037 
02038 NS_IMETHODIMP
02039 nsMenuFrame::AppendFrames(nsIAtom*        aListName,
02040                           nsIFrame*       aFrameList)
02041 {
02042   if (!aFrameList)
02043     return NS_OK;
02044 
02045   nsresult          rv;
02046 
02047   nsIMenuParent *menuPar;
02048   if (aFrameList && NS_SUCCEEDED(CallQueryInterface(aFrameList, &menuPar))) {
02049     NS_ASSERTION(aFrameList->IsBoxFrame(),"Popup is not a box!!!");
02050 
02051     mPopupFrames.AppendFrames(nsnull, aFrameList);
02052     nsBoxLayoutState state(GetPresContext());
02053 #ifdef DEBUG_LAYOUT
02054     SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG);
02055 #endif
02056     rv = MarkDirtyChildren(state);
02057   } else {
02058     rv = nsBoxFrame::AppendFrames(aListName, aFrameList); 
02059   }
02060 
02061   return rv;
02062 }
02063 
02064 void
02065 nsMenuFrame::UpdateDismissalListener(nsIMenuParent* aMenuParent)
02066 {
02067   if (!nsMenuFrame::sDismissalListener) {
02068     if (!aMenuParent)
02069        return;
02070     // Create the listener and attach it to the outermost window.
02071     aMenuParent->CreateDismissalListener();
02072   }
02073   
02074   // Make sure the menu dismissal listener knows what the current
02075   // innermost menu popup frame is.
02076   nsMenuFrame::sDismissalListener->SetCurrentMenuParent(aMenuParent);
02077 }
02078 
02079 class nsASyncMenuGeneration : public nsIReflowCallback
02080 {
02081 public:
02082   nsASyncMenuGeneration(nsIFrame* aFrame)
02083     : mWeakFrame(aFrame)
02084   {
02085     nsIContent* content = aFrame ? aFrame->GetContent() : nsnull;
02086     mDocument = content ? content->GetCurrentDoc() : nsnull;
02087     if (mDocument) {
02088       mDocument->BlockOnload();
02089     }
02090   }
02091 
02092   NS_DECL_ISUPPORTS
02093 
02094   NS_IMETHOD ReflowFinished(nsIPresShell* aShell, PRBool* aFlushFlag) {
02095     nsIFrame* frame = mWeakFrame.GetFrame();
02096     if (frame) {
02097       PRBool collapsed = PR_FALSE;
02098       nsBoxLayoutState state(frame->GetPresContext());
02099       frame->IsCollapsed(state, collapsed);
02100       if (!collapsed) {
02101         nsIMenuFrame* imenu = nsnull;
02102         CallQueryInterface(frame, &imenu);
02103         if (imenu) {
02104           imenu->MarkAsGenerated();
02105           *aFlushFlag = PR_TRUE;
02106         }
02107       }
02108     }
02109     if (mDocument) {
02110       mDocument->UnblockOnload();
02111     }
02112     return NS_OK;
02113   }
02114 
02115   nsWeakFrame           mWeakFrame;
02116   nsCOMPtr<nsIDocument> mDocument;
02117 };
02118 
02119 NS_IMPL_ISUPPORTS1(nsASyncMenuGeneration, nsIReflowCallback)
02120 
02121 PRBool
02122 nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize)
02123 {
02124   PRBool collapsed = PR_FALSE;
02125   IsCollapsed(aState, collapsed);
02126   if (!collapsed) {
02127     nsSize tmpSize(-1, 0);
02128     nsIBox::AddCSSPrefSize(aState, this, tmpSize);
02129     nscoord flex;
02130     GetFlex(aState, flex);
02131     if (tmpSize.width == -1 && flex == 0) {
02132       nsIFrame* frame = mPopupFrames.FirstChild();
02133       if (!frame) {
02134         nsCOMPtr<nsIContent> child;
02135         GetMenuChildrenElement(getter_AddRefs(child));
02136         if (child) {
02137           nsAutoString genVal;
02138           child->GetAttr(kNameSpaceID_None, nsXULAtoms::menugenerated, genVal);
02139           if (genVal.IsEmpty()) {
02140             nsCOMPtr<nsIReflowCallback> cb = new nsASyncMenuGeneration(this);
02141             if (cb) {
02142               GetPresContext()->PresShell()->PostReflowCallback(cb);
02143             }
02144           }
02145         }
02146         return PR_FALSE;
02147       }
02148 
02149       NS_ASSERTION(frame->IsBoxFrame(), "popupChild is not box!!");
02150 
02151       frame->GetPrefSize(aState, tmpSize);
02152       aSize.width = tmpSize.width;
02153       return PR_TRUE;
02154     }
02155   }
02156 
02157   return PR_FALSE;
02158 }
02159 
02160 NS_IMETHODIMP
02161 nsMenuFrame::GetPrefSize(nsBoxLayoutState& aState, nsSize& aSize)
02162 {
02163   nsresult rv = nsBoxFrame::GetPrefSize(aState, aSize);
02164 
02165   // If we are using sizetopopup="always" then
02166   // nsBoxFrame will already have enforced the minimum size
02167   if (!IsSizedToPopup(mContent, PR_TRUE) &&
02168       IsSizedToPopup(mContent, PR_FALSE) &&
02169       SizeToPopup(aState, aSize)) {
02170     // We now need to ensure that aSize is within the min size - max size range.
02171     nsSize minSize, maxSize;
02172     nsBoxFrame::GetMinSize(aState, minSize);
02173     GetMaxSize(aState, maxSize);
02174     BoundsCheck(minSize, aSize, maxSize);
02175   }
02176 
02177   return rv;
02178 }
02179 
02180 NS_IMETHODIMP
02181 nsMenuFrame::GetActiveChild(nsIDOMElement** aResult)
02182 {
02183   nsIFrame* frame = mPopupFrames.FirstChild();
02184   nsMenuPopupFrame* menuPopup = (nsMenuPopupFrame*)frame;
02185   if (!frame)
02186     return NS_ERROR_FAILURE;
02187 
02188   nsIMenuFrame* menuFrame = menuPopup->GetCurrentMenuItem();
02189   
02190   if (!menuFrame) {
02191     *aResult = nsnull;
02192   }
02193   else {
02194     nsIFrame* f;
02195     menuFrame->QueryInterface(NS_GET_IID(nsIFrame), (void**)&f);
02196     nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(f->GetContent()));
02197     *aResult = elt;
02198     NS_IF_ADDREF(*aResult);
02199   }
02200 
02201   return NS_OK;
02202 }
02203 
02204 NS_IMETHODIMP
02205 nsMenuFrame::SetActiveChild(nsIDOMElement* aChild)
02206 {
02207   nsIFrame* frame = mPopupFrames.FirstChild();
02208   nsMenuPopupFrame* menuPopup = (nsMenuPopupFrame*)frame;
02209   if (!frame)
02210     return NS_ERROR_FAILURE;
02211 
02212   if (!aChild) {
02213     // Remove the current selection
02214     menuPopup->SetCurrentMenuItem(nsnull);
02215     return NS_OK;
02216   }
02217 
02218   nsCOMPtr<nsIContent> child(do_QueryInterface(aChild));
02219   
02220   nsIFrame* kid;
02221   mPresContext->PresShell()->GetPrimaryFrameFor(child, &kid);
02222   if (!kid)
02223     return NS_ERROR_FAILURE;
02224   nsIMenuFrame *menuFrame;
02225   nsresult rv = CallQueryInterface(kid, &menuFrame);
02226   if (NS_FAILED(rv))
02227     return rv;
02228   menuPopup->SetCurrentMenuItem(menuFrame);
02229   return NS_OK;
02230 }
02231 
02232 nsIScrollableView* nsMenuFrame::GetScrollableView()
02233 {
02234   if (!mPopupFrames.FirstChild())
02235     return nsnull;
02236 
02237   nsMenuPopupFrame* popup = (nsMenuPopupFrame*) mPopupFrames.FirstChild();
02238   nsIFrame* childFrame = popup->GetFirstChild(nsnull);
02239   if (childFrame) {
02240     return popup->GetScrollableView(childFrame);
02241   }
02242   return nsnull;
02243 }
02244 
02245 /* Need to figure out what this does.
02246 NS_IMETHODIMP
02247 nsMenuFrame::GetBoxInfo(nsPresContext* aPresContext, const nsHTMLReflowState& aReflowState, nsBoxInfo& aSize)
02248 {
02249   nsresult rv = nsBoxFrame::GetBoxInfo(aPresContext, aReflowState, aSize);
02250   nsCOMPtr<nsIDOMXULMenuListElement> menulist(do_QueryInterface(mContent));
02251   if (menulist) {
02252     nsCalculatedBoxInfo boxInfo(this);
02253     boxInfo.prefSize.width = NS_UNCONSTRAINEDSIZE;
02254     boxInfo.prefSize.height = NS_UNCONSTRAINEDSIZE;
02255     boxInfo.flex = 0;
02256     GetRedefinedMinPrefMax(aPresContext, this, boxInfo);
02257     if (boxInfo.prefSize.width == NS_UNCONSTRAINEDSIZE &&
02258         boxInfo.prefSize.height == NS_UNCONSTRAINEDSIZE &&
02259         boxInfo.flex == 0) {
02260       nsIFrame* frame = mPopupFrames.FirstChild();
02261       if (!frame) {
02262         MarkAsGenerated();
02263         frame = mPopupFrames.FirstChild();
02264       }
02265       
02266       nsCalculatedBoxInfo childInfo(frame);
02267       frame->GetBoxInfo(aPresContext, aReflowState, childInfo);
02268       GetRedefinedMinPrefMax(aPresContext, this, childInfo);
02269       aSize.prefSize.width = childInfo.prefSize.width;
02270     }
02271 
02272     // This retrieval guarantess that the selectedItem will
02273     // be set before we lay out.
02274     nsCOMPtr<nsIDOMElement> element;
02275     menulist->GetSelectedItem(getter_AddRefs(element));
02276   }
02277   return rv;
02278 }
02279 */
02280 
02281 nsIMenuParent*
02282 nsMenuFrame::GetContextMenu()
02283 {
02284   if (!nsMenuFrame::sDismissalListener)
02285     return nsnull;
02286 
02287   nsIMenuParent *menuParent = nsMenuFrame::sDismissalListener->GetCurrentMenuParent();
02288   if (!menuParent)
02289     return nsnull;
02290 
02291   PRBool isContextMenu;
02292   menuParent->GetIsContextMenu(isContextMenu);
02293   if (isContextMenu)
02294     return menuParent;
02295 
02296   return nsnull;
02297 }
02298 
02299 // nsMenuTimerMediator implementation.
02300 NS_IMPL_ISUPPORTS1(nsMenuTimerMediator, nsITimerCallback)
02301 
02302 
02306 nsMenuTimerMediator::nsMenuTimerMediator(nsMenuFrame *aFrame) :
02307   mFrame(aFrame)
02308 {
02309   NS_ASSERTION(mFrame, "Must have frame");
02310 }
02311 
02312 nsMenuTimerMediator::~nsMenuTimerMediator()
02313 {
02314 }
02315 
02321 NS_IMETHODIMP nsMenuTimerMediator::Notify(nsITimer* aTimer)
02322 {
02323   if (!mFrame)
02324     return NS_ERROR_FAILURE;
02325 
02326   return mFrame->Notify(aTimer);
02327 }
02328 
02333 void nsMenuTimerMediator::ClearFrame()
02334 {
02335   mFrame = nsnull;
02336 }