Back to index

lightning-sunbird  0.9+nobinonly
nsMenuX.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; 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  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either the GNU General Public License Version 2 or later (the "GPL"), or
00026  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "nsCOMPtr.h"
00039 #include "nsIDocument.h"
00040 #include "nsIContent.h"
00041 #include "nsIDOMDocument.h"
00042 #include "nsIDocumentViewer.h"
00043 #include "nsIDocumentObserver.h"
00044 #include "nsIComponentManager.h"
00045 #include "nsIDocShell.h"
00046 #include "prinrval.h"
00047 #include "nsIRollupListener.h"
00048 
00049 #include "nsMenuX.h"
00050 #include "nsMenuBarX.h"
00051 #include "nsIMenu.h"
00052 #include "nsIMenuBar.h"
00053 #include "nsIMenuItem.h"
00054 #include "nsIMenuListener.h"
00055 #include "nsPresContext.h"
00056 #include "nsIMenuCommandDispatcher.h"
00057 #include "nsMenuItemIcon.h"
00058 
00059 #include "nsString.h"
00060 #include "nsReadableUtils.h"
00061 #include "nsUnicharUtils.h"
00062 #include "plstr.h"
00063 
00064 #include "nsINameSpaceManager.h"
00065 #include "nsWidgetAtoms.h"
00066 #include "nsIXBLService.h"
00067 #include "nsIServiceManager.h"
00068 
00069 #include <Appearance.h>
00070 #include <ToolUtils.h>
00071 #include <UnicodeConverter.h>
00072 
00073 #include "nsGUIEvent.h"
00074 
00075 #include "nsCRT.h"
00076 #include "nsToolkit.h"
00077 
00078 // externs defined in nsWindow.cpp
00079 extern nsIRollupListener * gRollupListener;
00080 extern nsIWidget         * gRollupWidget;
00081 
00082 static OSStatus InstallMyMenuEventHandler(MenuRef menuRef, void* userData, EventHandlerRef* outHandler) ;
00083 
00084 // keep track of the menuID of the menu the mouse is currently over. Yes, this is ugly,
00085 // but necessary to work around bugs in Carbon with ::MenuSelect() sometimes returning
00086 // the wrong menuID.
00087 static MenuID gCurrentlyTrackedMenuID = 0;
00088 
00089 const PRInt16 kMacMenuIDX = nsMenuBarX::kAppleMenuID + 1;
00090 static PRInt16 gMacMenuIDCountX = kMacMenuIDX;
00091 static PRBool gConstructingMenu = PR_FALSE;
00092   
00093 #if DEBUG
00094 nsInstanceCounter   gMenuCounterX("nsMenuX");
00095 #endif
00096 
00097 // CIDs
00098 #include "nsWidgetsCID.h"
00099 static NS_DEFINE_CID(kMenuCID,     NS_MENU_CID);
00100 static NS_DEFINE_CID(kMenuItemCID, NS_MENUITEM_CID);
00101 
00102 // Refcounted class for dummy menu items, like separators and help menu items.
00103 class nsDummyMenuItemX : public nsISupports {
00104 public:
00105     NS_DECL_ISUPPORTS
00106 
00107     nsDummyMenuItemX()
00108     {
00109     }
00110 };
00111 
00112 NS_IMETHODIMP_(nsrefcnt) nsDummyMenuItemX::AddRef() { return ++mRefCnt; }
00113 NS_METHOD nsDummyMenuItemX::Release() { return --mRefCnt; }
00114 NS_IMPL_QUERY_INTERFACE0(nsDummyMenuItemX)
00115 static nsDummyMenuItemX gDummyMenuItemX;
00116 
00117 //-------------------------------------------------------------------------
00118 NS_IMPL_ISUPPORTS5(nsMenuX, nsIMenu, nsIMenu_MOZILLA_1_8_BRANCH, nsIMenuListener, nsIChangeObserver, nsISupportsWeakReference)
00119 
00120 //
00121 // nsMenuX constructor
00122 //
00123 nsMenuX::nsMenuX()
00124     :   mNumMenuItems(0), mParent(nsnull), mManager(nsnull),
00125         mMacMenuID(0), mMacMenuHandle(nsnull), mIsEnabled(PR_TRUE),
00126         mDestroyHandlerCalled(PR_FALSE), mNeedsRebuild(PR_TRUE),
00127         mConstructed(PR_FALSE), mVisible(PR_TRUE), mHandler(nsnull)
00128 {
00129 #if DEBUG
00130   ++gMenuCounterX;
00131 #endif 
00132 }
00133 
00134 
00135 //
00136 // nsMenuX destructor
00137 //
00138 nsMenuX::~nsMenuX()
00139 {
00140   RemoveAll();
00141 
00142   if ( mMacMenuHandle ) {
00143     if ( mHandler )
00144       ::RemoveEventHandler(mHandler);
00145     ::ReleaseMenu(mMacMenuHandle);
00146   }
00147   
00148   // alert the change notifier we don't care no more
00149   mManager->Unregister(mMenuContent);
00150 
00151 #if DEBUG
00152   --gMenuCounterX;
00153 #endif
00154 }
00155 
00156 
00157 //
00158 // Create
00159 //
00160 NS_METHOD 
00161 nsMenuX::Create(nsISupports * aParent, const nsAString &aLabel, const nsAString &aAccessKey, 
00162                 nsIChangeManager* aManager, nsIDocShell* aShell, nsIContent* aNode )
00163 {
00164   mDocShellWeakRef = do_GetWeakReference(aShell);
00165   mMenuContent = aNode;
00166 
00167   // register this menu to be notified when changes are made to our content object
00168   mManager = aManager;                    // weak ref
00169   nsCOMPtr<nsIChangeObserver> changeObs ( do_QueryInterface(NS_STATIC_CAST(nsIChangeObserver*, this)) );
00170   mManager->Register(mMenuContent, changeObs);
00171 
00172   NS_ASSERTION ( mMenuContent, "Menu not given a dom node at creation time" );
00173   NS_ASSERTION ( mManager, "No change manager given, can't tell content model updates" );
00174 
00175   mParent = aParent;
00176   // our parent could be either a menu bar (if we're toplevel) or a menu (if we're a submenu)
00177   nsCOMPtr<nsIMenuBar> menubar = do_QueryInterface(aParent);
00178   nsCOMPtr<nsIMenu_MOZILLA_1_8_BRANCH> menu = do_QueryInterface(aParent);
00179   NS_ASSERTION(menubar || menu, "Menu parent not a menu bar or menu!" );
00180 
00181   SetLabel(aLabel);
00182   SetAccessKey(aAccessKey);
00183 
00184   nsAutoString hiddenValue, collapsedValue;
00185   mMenuContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::hidden, hiddenValue);
00186   mMenuContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::collapsed, collapsedValue);
00187   if ( hiddenValue.EqualsLiteral("true") || collapsedValue.EqualsLiteral("true") )
00188     mVisible = PR_FALSE;
00189 
00190   if (menubar && mMenuContent->GetChildCount() == 0)
00191     mVisible = PR_FALSE;
00192 
00193   // We call MenuConstruct here because keyboard commands are dependent upon
00194   // native menu items being created. If we only call MenuConstruct when a menu
00195   // is actually selected, then we can't access keyboard commands until the
00196   // menu gets selected, which is bad.
00197   nsMenuEvent fake(PR_TRUE, 0, nsnull);
00198   MenuConstruct(fake, nsnull, nsnull, nsnull);
00199 
00200   if (menu)
00201     mIcon = new nsMenuItemIcon(NS_STATIC_CAST(nsIMenu*, this),
00202                                menu, mMenuContent);
00203 
00204   return NS_OK;
00205 }
00206 
00207 //-------------------------------------------------------------------------
00208 NS_METHOD nsMenuX::GetParent(nsISupports*& aParent)
00209 {
00210   aParent = mParent;
00211   NS_IF_ADDREF(aParent);
00212   return NS_OK;
00213 }
00214 
00215 //-------------------------------------------------------------------------
00216 NS_METHOD nsMenuX::GetLabel(nsString &aText)
00217 {
00218   aText = mLabel;
00219   return NS_OK;
00220 }
00221 
00222 
00223 //-------------------------------------------------------------------------
00224 NS_METHOD nsMenuX::SetLabel(const nsAString &aText)
00225 {
00226   mLabel = aText;
00227 
00228   // first time? create the menu handle, attach event handler to it.
00229   if (mMacMenuHandle == nsnull) {
00230     mMacMenuID = gMacMenuIDCountX++;
00231     mMacMenuHandle = NSStringNewMenu(mMacMenuID, mLabel);
00232   }
00233   
00234   return NS_OK;
00235 }
00236 
00237 //-------------------------------------------------------------------------
00238 NS_METHOD nsMenuX::GetAccessKey(nsString &aText)
00239 {
00240   return NS_OK;
00241 }
00242 
00243 //-------------------------------------------------------------------------
00244 NS_METHOD nsMenuX::SetAccessKey(const nsAString &aText)
00245 {
00246   return NS_OK;
00247 }
00248 
00249 //-------------------------------------------------------------------------
00250 NS_METHOD nsMenuX::AddItem(nsISupports* aItem)
00251 {
00252     nsresult rv = NS_ERROR_FAILURE;
00253     if (aItem) {
00254         // Figure out what we're adding
00255         nsCOMPtr<nsIMenuItem> menuItem(do_QueryInterface(aItem));
00256         if (menuItem) {
00257             rv = AddMenuItem(menuItem);
00258         } else {
00259             nsCOMPtr<nsIMenu> menu(do_QueryInterface(aItem));
00260             if (menu)
00261                 rv = AddMenu(menu);
00262         }
00263     }
00264     return rv;
00265 }
00266 
00267 //-------------------------------------------------------------------------
00268 NS_METHOD nsMenuX::AddMenuItem(nsIMenuItem * aMenuItem)
00269 {
00270   if(!aMenuItem) return NS_ERROR_NULL_POINTER;
00271 
00272   mMenuItemsArray.AppendElement(aMenuItem);    // owning ref
00273   PRUint32 currItemIndex;
00274   mMenuItemsArray.Count(&currItemIndex);
00275 
00276   mNumMenuItems++;
00277 
00278   nsAutoString label;
00279   aMenuItem->GetLabel(label);
00280   InsertMenuItemWithTruncation ( label, currItemIndex );
00281   
00282   // I want to be internationalized too!
00283   nsAutoString keyEquivalent(NS_LITERAL_STRING(" "));
00284   aMenuItem->GetShortcutChar(keyEquivalent);
00285   if (!keyEquivalent.EqualsLiteral(" ")) {
00286     ToUpperCase(keyEquivalent);
00287     char keyStr[2];
00288     keyEquivalent.ToCString(keyStr, sizeof(keyStr));
00289     short inKey = keyStr[0];
00290     ::SetItemCmd(mMacMenuHandle, currItemIndex, inKey);
00291     //::SetMenuItemKeyGlyph(mMacMenuHandle, mNumMenuItems, 0x61);
00292   }
00293 
00294   PRUint8 modifiers;
00295   aMenuItem->GetModifiers(&modifiers);
00296   PRUint8 macModifiers = kMenuNoModifiers;
00297   if (knsMenuItemShiftModifier & modifiers)
00298     macModifiers |= kMenuShiftModifier;
00299 
00300   if (knsMenuItemAltModifier & modifiers)
00301     macModifiers |= kMenuOptionModifier;
00302 
00303   if (knsMenuItemControlModifier & modifiers)
00304     macModifiers |= kMenuControlModifier;
00305 
00306   if (!(knsMenuItemCommandModifier & modifiers))
00307     macModifiers |= kMenuNoCommandModifier;
00308 
00309   ::SetMenuItemModifiers(mMacMenuHandle, currItemIndex, macModifiers);
00310 
00311   // set its command. we get the unique command id from the menubar
00312   nsCOMPtr<nsIMenuCommandDispatcher> dispatcher ( do_QueryInterface(mManager) );
00313   if ( dispatcher ) {
00314     PRUint32 commandID = 0L;
00315     dispatcher->Register(aMenuItem, &commandID);
00316     if ( commandID )
00317       ::SetMenuItemCommandID(mMacMenuHandle, currItemIndex, commandID);
00318   }
00319   
00320   PRBool isEnabled;
00321   aMenuItem->GetEnabled(&isEnabled);
00322   if(isEnabled)
00323     ::EnableMenuItem(mMacMenuHandle, currItemIndex);
00324   else
00325     ::DisableMenuItem(mMacMenuHandle, currItemIndex);
00326 
00327   PRBool isChecked;
00328   aMenuItem->GetChecked(&isChecked);
00329   if(isChecked)
00330     ::CheckMenuItem(mMacMenuHandle, currItemIndex, true);
00331   else
00332     ::CheckMenuItem(mMacMenuHandle, currItemIndex, false);
00333 
00334   return NS_OK;
00335 }
00336 
00337 //-------------------------------------------------------------------------
00338 NS_METHOD nsMenuX::AddMenu(nsIMenu * aMenu)
00339 {
00340   // Add a submenu
00341   if (!aMenu) return NS_ERROR_NULL_POINTER;
00342 
00343   nsCOMPtr<nsISupports>  supports = do_QueryInterface(aMenu);
00344   if (!supports) return NS_ERROR_NO_INTERFACE;
00345 
00346   mMenuItemsArray.AppendElement(supports);   // owning ref
00347   PRUint32 currItemIndex;
00348   mMenuItemsArray.Count(&currItemIndex);
00349 
00350   mNumMenuItems++;
00351 
00352   // We have to add it as a menu item and then associate it with the item
00353   nsAutoString label;
00354   aMenu->GetLabel(label);
00355   InsertMenuItemWithTruncation ( label, currItemIndex );
00356 
00357   PRBool isEnabled;
00358   aMenu->GetEnabled(&isEnabled);
00359   if (isEnabled)
00360     ::EnableMenuItem(mMacMenuHandle, currItemIndex);
00361   else
00362     ::DisableMenuItem(mMacMenuHandle, currItemIndex);       
00363 
00364   MenuHandle childMenu;
00365   if (aMenu->GetNativeData((void**)&childMenu) == NS_OK)
00366     ::SetMenuItemHierarchicalMenu((MenuHandle) mMacMenuHandle, currItemIndex, childMenu);
00367   
00368   return NS_OK;
00369 }
00370 
00371 
00372 //
00373 // InsertMenuItemWithTruncation
00374 //
00375 // Insert a new item in this menu with index |inItemIndex| with the text |inItemLabel|,
00376 // middle-truncated to a certain pixel width with an elipsis.
00377 //
00378 void
00379 nsMenuX :: InsertMenuItemWithTruncation ( nsAutoString & inItemLabel, PRUint32 inItemIndex )
00380 {
00381   // ::TruncateThemeText() doesn't take the number of characters to truncate to, it takes a pixel with
00382   // to fit the string in. Ugh. I talked it over with sfraser and we couldn't come up with an 
00383   // easy way to compute what this should be given the system font, etc, so we're just going
00384   // to hard code it to something reasonable and bigger fonts will just have to deal.
00385   const short kMaxItemPixelWidth = 300;
00386 
00387   CFMutableStringRef labelRef = ::CFStringCreateMutable ( kCFAllocatorDefault, inItemLabel.Length() );
00388   ::CFStringAppendCharacters ( labelRef, (UniChar*)inItemLabel.get(), inItemLabel.Length() );
00389   ::TruncateThemeText(labelRef, kThemeMenuItemFont, kThemeStateActive, kMaxItemPixelWidth, truncMiddle, NULL);
00390   ::InsertMenuItemTextWithCFString(mMacMenuHandle, labelRef, inItemIndex, 0, 0);
00391   ::CFRelease(labelRef);
00392 
00393 } // InsertMenuItemWithTruncation
00394 
00395 
00396 //-------------------------------------------------------------------------
00397 NS_METHOD nsMenuX::AddSeparator()
00398 {
00399   // HACK - We're not really appending an nsMenuItem but it 
00400   // needs to be here to make sure that event dispatching isn't off by one.
00401   mMenuItemsArray.AppendElement(&gDummyMenuItemX);   // owning ref
00402   PRUint32  numItems;
00403   mMenuItemsArray.Count(&numItems);
00404   ::InsertMenuItem(mMacMenuHandle, "\p(-", numItems);
00405   mNumMenuItems++;
00406   return NS_OK;
00407 }
00408 
00409 //-------------------------------------------------------------------------
00410 NS_METHOD nsMenuX::GetItemCount(PRUint32 &aCount)
00411 {
00412   return mMenuItemsArray.Count(&aCount);
00413 }
00414 
00415 //-------------------------------------------------------------------------
00416 NS_METHOD nsMenuX::GetItemAt(const PRUint32 aPos, nsISupports *& aMenuItem)
00417 {
00418   mMenuItemsArray.GetElementAt(aPos, &aMenuItem);
00419   return NS_OK;
00420 }
00421 
00422 //-------------------------------------------------------------------------
00423 NS_METHOD nsMenuX::InsertItemAt(const PRUint32 aPos, nsISupports * aMenuItem)
00424 {
00425   NS_ASSERTION(0, "Not implemented");
00426   return NS_OK;
00427 }
00428 
00429 //-------------------------------------------------------------------------
00430 NS_METHOD nsMenuX::RemoveItem(const PRUint32 aPos)
00431 {
00432   NS_WARNING("Not implemented");
00433   return NS_OK;
00434 }
00435 
00436 //-------------------------------------------------------------------------
00437 NS_METHOD nsMenuX::RemoveAll()
00438 {
00439   if (mMacMenuHandle != NULL) {    
00440     // clear command id's
00441     nsCOMPtr<nsIMenuCommandDispatcher> dispatcher ( do_QueryInterface(mManager) );
00442     if ( dispatcher ) {
00443       for ( unsigned int i = 1; i <= mNumMenuItems; ++i ) {
00444         PRUint32 commandID = 0L;
00445         OSErr err = ::GetMenuItemCommandID(mMacMenuHandle, i, (unsigned long*)&commandID);
00446         if ( !err )
00447           dispatcher->Unregister(commandID);
00448       }
00449     }
00450     ::DeleteMenuItems(mMacMenuHandle, 1, ::CountMenuItems(mMacMenuHandle));
00451   }
00452   
00453   mMenuItemsArray.Clear();    // remove all items
00454   return NS_OK;
00455 }
00456 
00457 //-------------------------------------------------------------------------
00458 NS_METHOD nsMenuX::GetNativeData(void ** aData)
00459 {
00460   *aData = mMacMenuHandle;
00461   return NS_OK;
00462 }
00463 
00464 //-------------------------------------------------------------------------
00465 NS_METHOD nsMenuX::SetNativeData(void * aData)
00466 {
00467   mMacMenuHandle = (MenuHandle) aData;
00468   return NS_OK;
00469 }
00470 
00471 //-------------------------------------------------------------------------
00472 NS_METHOD nsMenuX::AddMenuListener(nsIMenuListener * aMenuListener)
00473 {
00474   mListener = aMenuListener;    // strong ref
00475   return NS_OK;
00476 }
00477 
00478 //-------------------------------------------------------------------------
00479 NS_METHOD nsMenuX::RemoveMenuListener(nsIMenuListener * aMenuListener)
00480 {
00481   if (aMenuListener == mListener)
00482     mListener = nsnull;
00483   return NS_OK;
00484 }
00485 
00486 
00487 //-------------------------------------------------------------------------
00488 //
00489 // nsIMenuListener interface
00490 //
00491 //-------------------------------------------------------------------------
00492 nsEventStatus nsMenuX::MenuItemSelected(const nsMenuEvent & aMenuEvent)
00493 {
00494   // all this is now handled by Carbon Events.
00495   return nsEventStatus_eConsumeNoDefault;
00496 }
00497 
00498 //-------------------------------------------------------------------------
00499 nsEventStatus nsMenuX::MenuSelected(const nsMenuEvent & aMenuEvent)
00500 {
00501   //printf("MenuSelected called for %s \n", NS_LossyConvertUCS2toASCII(mLabel).get());
00502   nsEventStatus eventStatus = nsEventStatus_eIgnore;
00503 
00504   // Determine if this is the correct menu to handle the event
00505   MenuHandle selectedMenuHandle = (MenuHandle) aMenuEvent.mCommand;
00506 
00507   if (mMacMenuHandle == selectedMenuHandle) {
00508     // Open the node.
00509     mMenuContent->SetAttr(kNameSpaceID_None, nsWidgetAtoms::open, NS_LITERAL_STRING("true"), PR_TRUE);
00510   
00511 
00512     // Fire our oncreate handler. If we're told to stop, don't build the menu at all
00513     PRBool keepProcessing = OnCreate();
00514 
00515     if (!mNeedsRebuild || !keepProcessing)
00516       return nsEventStatus_eConsumeNoDefault;
00517 
00518     if(!mConstructed || mNeedsRebuild) {
00519       if (mNeedsRebuild)
00520         RemoveAll();
00521 
00522       nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShellWeakRef);
00523       if (!docShell) {
00524         NS_ERROR("No doc shell");
00525         return nsEventStatus_eConsumeNoDefault;
00526       }
00527 
00528       MenuConstruct(aMenuEvent, nsnull /* mParentWindow */, nsnull, docShell);
00529       mConstructed = true;
00530     } 
00531 
00532     OnCreated();  // Now that it's built, fire the popupShown event.
00533 
00534     eventStatus = nsEventStatus_eConsumeNoDefault;  
00535   } 
00536   else {
00537     // Make sure none of our submenus are the ones that should be handling this
00538     PRUint32    numItems;
00539     mMenuItemsArray.Count(&numItems);
00540     for (PRUint32 i = numItems; i > 0; i--) {
00541       nsCOMPtr<nsISupports>     menuSupports = getter_AddRefs(mMenuItemsArray.ElementAt(i - 1));    
00542       nsCOMPtr<nsIMenu>         submenu = do_QueryInterface(menuSupports);
00543       nsCOMPtr<nsIMenuListener> menuListener = do_QueryInterface(submenu);
00544       if (menuListener) {
00545         eventStatus = menuListener->MenuSelected(aMenuEvent);
00546         if (nsEventStatus_eIgnore != eventStatus)
00547           return eventStatus;
00548       }  
00549     }
00550   }
00551 
00552   return eventStatus;
00553 }
00554 
00555 //-------------------------------------------------------------------------
00556 nsEventStatus nsMenuX::MenuDeselected(const nsMenuEvent & aMenuEvent)
00557 {
00558   // Destroy the menu
00559   if (mConstructed) {
00560     MenuDestruct(aMenuEvent);
00561     mConstructed = false;
00562   }
00563   return nsEventStatus_eIgnore;
00564 }
00565 
00566 //-------------------------------------------------------------------------
00567 nsEventStatus nsMenuX::MenuConstruct(
00568     const nsMenuEvent & aMenuEvent,
00569     nsIWidget         * aParentWindow, 
00570     void              * /* menuNode */,
00571          void              * aDocShell)
00572 {
00573   mConstructed = false;
00574   gConstructingMenu = PR_TRUE;
00575   
00576   // reset destroy handler flag so that we'll know to fire it next time this menu goes away.
00577   mDestroyHandlerCalled = PR_FALSE;
00578   
00579   //printf("nsMenuX::MenuConstruct called for %s = %d \n", NS_LossyConvertUCS2toASCII(mLabel).get(), mMacMenuHandle);
00580   // Begin menuitem inner loop
00581   
00582   // Retrieve our menupopup.
00583   nsCOMPtr<nsIContent> menuPopup;
00584   GetMenuPopupContent(getter_AddRefs(menuPopup));
00585   if (!menuPopup)
00586     return nsEventStatus_eIgnore;
00587       
00588   // Iterate over the kids
00589   PRUint32 count = menuPopup->GetChildCount();
00590   for ( PRUint32 i = 0; i < count; ++i ) {
00591     nsIContent *child = menuPopup->GetChildAt(i);
00592     if ( child ) {
00593       // depending on the type, create a menu item, separator, or submenu
00594       nsIAtom *tag = child->Tag();
00595       if ( tag == nsWidgetAtoms::menuitem )
00596         LoadMenuItem(this, child);
00597       else if ( tag == nsWidgetAtoms::menuseparator )
00598         LoadSeparator(child);
00599       else if ( tag == nsWidgetAtoms::menu )
00600         LoadSubMenu(this, child);
00601     }
00602   } // for each menu item
00603   
00604   gConstructingMenu = PR_FALSE;
00605   mNeedsRebuild = PR_FALSE;
00606   //printf("  Done building, mMenuItemVoidArray.Count() = %d \n", mMenuItemVoidArray.Count());
00607   
00608   return nsEventStatus_eIgnore;
00609 }
00610 
00611 //-------------------------------------------------------------------------
00612 nsEventStatus nsMenuX::MenuDestruct(const nsMenuEvent & aMenuEvent)
00613 {
00614   //printf("nsMenuX::MenuDestruct() called for %s \n", NS_LossyConvertUCS2toASCII(mLabel).get());
00615   
00616   // Fire our ondestroy handler. If we're told to stop, don't destroy the menu
00617   PRBool keepProcessing = OnDestroy();
00618   if ( keepProcessing ) {
00619     if(mNeedsRebuild) {
00620         mConstructed = false;
00621         //printf("  mMenuItemVoidArray.Count() = %d \n", mMenuItemVoidArray.Count());
00622     } 
00623     // Close the node.
00624     mMenuContent->UnsetAttr(kNameSpaceID_None, nsWidgetAtoms::open, PR_TRUE);
00625 
00626     OnDestroyed();
00627   }
00628   
00629   return nsEventStatus_eIgnore;
00630 }
00631 
00632 //-------------------------------------------------------------------------
00633 nsEventStatus nsMenuX::CheckRebuild(PRBool & aNeedsRebuild)
00634 {
00635   aNeedsRebuild = PR_TRUE; //mNeedsRebuild;
00636   return nsEventStatus_eIgnore;
00637 }
00638 
00639 //-------------------------------------------------------------------------
00640 nsEventStatus nsMenuX::SetRebuild(PRBool aNeedsRebuild)
00641 {
00642   if(!gConstructingMenu)
00643     mNeedsRebuild = aNeedsRebuild;
00644   return nsEventStatus_eIgnore;
00645 }
00646 
00647 //-------------------------------------------------------------------------
00652 NS_METHOD nsMenuX::SetEnabled(PRBool aIsEnabled)
00653 {
00654   mIsEnabled = aIsEnabled;
00655 
00656   if ( aIsEnabled )
00657     ::EnableMenuItem(mMacMenuHandle, 0);
00658   else
00659     ::DisableMenuItem(mMacMenuHandle, 0);
00660 
00661   return NS_OK;
00662 }
00663 
00664 //-------------------------------------------------------------------------
00669 NS_METHOD nsMenuX::GetEnabled(PRBool* aIsEnabled)
00670 {
00671   NS_ENSURE_ARG_POINTER(aIsEnabled);
00672   *aIsEnabled = mIsEnabled;
00673   return NS_OK;
00674 }
00675 
00676 //-------------------------------------------------------------------------
00681 NS_METHOD nsMenuX::IsHelpMenu(PRBool* aIsHelpMenu)
00682 {
00683   return NS_ERROR_NOT_IMPLEMENTED;
00684 }
00685 
00686 
00687 //-------------------------------------------------------------------------
00692 NS_METHOD nsMenuX::GetMenuContent(nsIContent ** aMenuContent)
00693 {
00694   NS_ENSURE_ARG_POINTER(aMenuContent);
00695   NS_IF_ADDREF(*aMenuContent = mMenuContent);
00696        return NS_OK;
00697 }
00698 
00699 
00700 /*
00701     Support for Carbon Menu Manager.
00702  */
00703 
00704 static pascal OSStatus MyMenuEventHandler(EventHandlerCallRef myHandler, EventRef event, void* userData)
00705 {
00706   OSStatus result = eventNotHandledErr;
00707 
00708   UInt32 kind = ::GetEventKind(event);
00709   if (kind == kEventMenuTargetItem) {
00710     // get the position of the menu item we want
00711     nsIMenu* targetMenu = reinterpret_cast<nsIMenu*>(userData);
00712     PRUint16 aPos;
00713     ::GetEventParameter(event, kEventParamMenuItemIndex, typeMenuItemIndex, NULL, sizeof(MenuItemIndex), NULL, &aPos);
00714     aPos--; // subtract 1 from aPos because Carbon menu positions start at 1 not 0
00715     
00716     nsISupports* aTargetMenuItem;
00717     targetMenu->GetItemAt((PRUint32)aPos, aTargetMenuItem);
00718     
00719     // Send DOM event
00720     // If the QI fails, we're over a submenu and we shouldn't send the event
00721     nsCOMPtr<nsIMenuItem_MOZILLA_1_8_BRANCH> bTargetMenuItem(do_QueryInterface(aTargetMenuItem));
00722     if (bTargetMenuItem) {
00723       PRBool handlerCalledPreventDefault; // but we don't actually care
00724       bTargetMenuItem->DispatchDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), &handlerCalledPreventDefault);
00725     }
00726     
00727     // remember which menu ID we're over for later
00728     MenuRef menuRef;
00729     ::GetEventParameter(event, kEventParamDirectObject, typeMenuRef, NULL, sizeof(menuRef), NULL, &menuRef);
00730     gCurrentlyTrackedMenuID = ::GetMenuID(menuRef);
00731   }
00732   else if (kind == kEventMenuOpening || kind == kEventMenuClosed) {
00733     if (kind == kEventMenuOpening && gRollupListener != nsnull && gRollupWidget != nsnull) {
00734       gRollupListener->Rollup();
00735       // We can only return userCanceledErr on Tiger or later because it crashes on Panther.
00736       // See bug 351230.
00737       if (nsToolkit::OSXVersion() >= MAC_OS_X_VERSION_10_4_HEX)
00738         return userCanceledErr;
00739     }
00740 
00741     nsISupports* supports = reinterpret_cast<nsISupports*>(userData);
00742     nsCOMPtr<nsIMenuListener> listener(do_QueryInterface(supports));
00743     if (listener) {
00744       MenuRef menuRef;
00745       ::GetEventParameter(event, kEventParamDirectObject, typeMenuRef, NULL, sizeof(menuRef), NULL, &menuRef);
00746       nsMenuEvent menuEvent(PR_TRUE, NS_MENU_SELECTED, nsnull);
00747       menuEvent.time = PR_IntervalNow();
00748       menuEvent.mCommand = (PRUint32) menuRef;
00749       if (kind == kEventMenuOpening) {
00750         gCurrentlyTrackedMenuID = ::GetMenuID(menuRef);    // remember which menu ID we're over for later
00751         listener->MenuSelected(menuEvent);
00752       }
00753       else
00754         listener->MenuDeselected(menuEvent);
00755     }
00756   }
00757   
00758   return result;
00759 }
00760 
00761 static OSStatus InstallMyMenuEventHandler(MenuRef menuRef, void* userData, EventHandlerRef* outHandler)
00762 {
00763   // install the event handler for the various carbon menu events.
00764   static EventTypeSpec eventList[] = {
00765       { kEventClassMenu, kEventMenuOpening },
00766       { kEventClassMenu, kEventMenuClosed },
00767       { kEventClassMenu, kEventMenuTargetItem },
00768   };
00769   static EventHandlerUPP gMyMenuEventHandlerUPP = NewEventHandlerUPP(&MyMenuEventHandler);
00770   return ::InstallMenuEventHandler(menuRef, gMyMenuEventHandlerUPP,
00771                                    sizeof(eventList) / sizeof(EventTypeSpec), eventList,
00772                                    userData, outHandler);
00773 }
00774 
00775 //-------------------------------------------------------------------------
00776 MenuHandle nsMenuX::NSStringNewMenu(short menuID, nsString& menuTitle)
00777 {
00778   MenuRef menuRef;
00779   OSStatus status = ::CreateNewMenu(menuID, 0, &menuRef);
00780   NS_ASSERTION(status == noErr,"nsMenuX::NSStringNewMenu: NewMenu failed.");
00781   CFStringRef titleRef = ::CFStringCreateWithCharacters(kCFAllocatorDefault, (UniChar*)menuTitle.get(), menuTitle.Length());
00782   NS_ASSERTION(titleRef,"nsMenuX::NSStringNewMenu: CFStringCreateWithCharacters failed.");
00783   if (titleRef) {
00784     ::SetMenuTitleWithCFString(menuRef, titleRef);
00785     ::CFRelease(titleRef);
00786   }
00787   
00788   status = InstallMyMenuEventHandler(menuRef, this, &mHandler);
00789   NS_ASSERTION(status == noErr,"nsMenuX::NSStringNewMenu: InstallMyMenuEventHandler failed.");
00790 
00791   return menuRef;
00792 }
00793 
00794 
00795 //----------------------------------------
00796 void nsMenuX::LoadMenuItem( nsIMenu* inParentMenu, nsIContent* inMenuItemContent )
00797 {
00798   if ( !inMenuItemContent )
00799     return;
00800 
00801   // if menu should be hidden, bail
00802   nsAutoString hidden;
00803   inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::hidden, hidden);
00804   if ( hidden.EqualsLiteral("true") )
00805     return;
00806 
00807   // Create nsMenuItem
00808   nsCOMPtr<nsIMenuItem_MOZILLA_1_8_BRANCH> pnsMenuItem =
00809    do_CreateInstance ( kMenuItemCID ) ;
00810   if (pnsMenuItem) {
00811     nsCOMPtr<nsIDOMDocument> domDocument = do_QueryInterface(inMenuItemContent->GetDocument());
00812     if (!domDocument)
00813       return;
00814 
00815     // We want the enabled state from the command element, not the menu item element.
00816     // The command element is more up-to-date and it matters for keyboard shortcuts
00817     // that can be invoked without opening the menu for the item. Sync menu item's
00818     // enabled state with its command element now.
00819     nsAutoString ourCommand;
00820     inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::command, ourCommand);
00821     if (!ourCommand.IsEmpty()) {
00822       // get the command DOM element
00823       nsCOMPtr<nsIDOMElement> commandElt;
00824       domDocument->GetElementById(ourCommand, getter_AddRefs(commandElt));
00825       if (commandElt) {
00826         nsCOMPtr<nsIContent> commandContent = do_QueryInterface(commandElt);
00827         nsAutoString menuItemDisabled;
00828         nsAutoString commandDisabled;
00829         inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, menuItemDisabled);
00830         commandContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, commandDisabled);
00831         if (!commandDisabled.Equals(menuItemDisabled)) {
00832           // The menu's disabled state needs to be updated to match the command
00833           if (commandDisabled.IsEmpty()) 
00834             inMenuItemContent->UnsetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, PR_TRUE);
00835           else
00836             inMenuItemContent->SetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, commandDisabled, PR_TRUE);
00837         }
00838       }
00839     }
00840     
00841     nsAutoString disabled;
00842     nsAutoString checked;
00843     nsAutoString type;
00844     nsAutoString menuitemName;
00845     nsAutoString menuitemCmd;
00846     
00847     inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, disabled);
00848     inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::checked, checked);
00849     inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::type, type);
00850     inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::label, menuitemName);
00851     inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::command, menuitemCmd);
00852 
00853     // Bug 164155 - Carbon interprets a leading hyphen on a menu item as
00854     // a separator. We want a real menu item, so "escape" the hyphen by inserting
00855     // a zero width space (Unicode 8204) before it.
00856     if ( menuitemName[0] == '-' )
00857       menuitemName.Assign( NS_LITERAL_STRING("\u200c") + menuitemName );
00858 
00859     //printf("menuitem %s \n", NS_LossyConvertUCS2toASCII(menuitemName).get());
00860               
00861     PRBool enabled = ! (disabled.EqualsLiteral("true"));
00862     
00863     nsIMenuItem::EMenuItemType itemType = nsIMenuItem::eRegular;
00864     if ( type.EqualsLiteral("checkbox") )
00865       itemType = nsIMenuItem::eCheckbox;
00866     else if ( type.EqualsLiteral("radio") )
00867       itemType = nsIMenuItem::eRadio;
00868       
00869     nsCOMPtr<nsIDocShell>  docShell = do_QueryReferent(mDocShellWeakRef);
00870     if (!docShell)
00871       return;
00872 
00873     // Create the item.
00874     pnsMenuItem->Create(inParentMenu, menuitemName, PR_FALSE, itemType, 
00875                           enabled, mManager, docShell, inMenuItemContent);   
00876 
00877     //
00878     // Set key shortcut and modifiers
00879     //
00880     
00881     nsAutoString keyValue;
00882     inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::key, keyValue);
00883 
00884     // Try to find the key node
00885     nsCOMPtr<nsIDOMElement> keyElement;
00886     if (!keyValue.IsEmpty())
00887       domDocument->GetElementById(keyValue, getter_AddRefs(keyElement));
00888     if ( keyElement ) {
00889       nsCOMPtr<nsIContent> keyContent ( do_QueryInterface(keyElement) );
00890       nsAutoString keyChar(NS_LITERAL_STRING(" "));
00891       keyContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::key, keyChar);
00892            if(!keyChar.EqualsLiteral(" ")) 
00893         pnsMenuItem->SetShortcutChar(keyChar);
00894         
00895       PRUint8 modifiers = knsMenuItemNoModifier;
00896            nsAutoString modifiersStr;
00897       keyContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::modifiers, modifiersStr);
00898                 char* str = ToNewCString(modifiersStr);
00899                 char* newStr;
00900                 char* token = nsCRT::strtok( str, ", ", &newStr );
00901                 while( token != NULL ) {
00902                   if (PL_strcmp(token, "shift") == 0)
00903                     modifiers |= knsMenuItemShiftModifier;
00904                   else if (PL_strcmp(token, "alt") == 0) 
00905                     modifiers |= knsMenuItemAltModifier;
00906                   else if (PL_strcmp(token, "control") == 0) 
00907                     modifiers |= knsMenuItemControlModifier;
00908                   else if ((PL_strcmp(token, "accel") == 0) ||
00909                            (PL_strcmp(token, "meta") == 0)) {
00910           modifiers |= knsMenuItemCommandModifier;
00911                   }
00912                   
00913                   token = nsCRT::strtok( newStr, ", ", &newStr );
00914                 }
00915                 nsMemory::Free(str);
00916 
00917            pnsMenuItem->SetModifiers ( modifiers );
00918     }
00919 
00920     if ( checked.EqualsLiteral("true") )
00921       pnsMenuItem->SetChecked(PR_TRUE);
00922     else
00923       pnsMenuItem->SetChecked(PR_FALSE);
00924       
00925     nsCOMPtr<nsISupports> supports ( do_QueryInterface(pnsMenuItem) );
00926     inParentMenu->AddItem(supports);         // Parent now owns menu item
00927 
00928     pnsMenuItem->SetupIcon();
00929   }
00930 }
00931 
00932 
00933 void 
00934 nsMenuX::LoadSubMenu( nsIMenu * pParentMenu, nsIContent* inMenuItemContent )
00935 {
00936   // if menu should be hidden, bail
00937   nsAutoString hidden; 
00938   inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::hidden, hidden);
00939   if ( hidden.EqualsLiteral("true") )
00940     return;
00941   
00942   nsAutoString menuName; 
00943   inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::label, menuName);
00944   //printf("Creating Menu [%s] \n", NS_LossyConvertUCS2toASCII(menuName).get());
00945 
00946   // Create nsMenu
00947   nsCOMPtr<nsIMenu_MOZILLA_1_8_BRANCH> pnsMenu ( do_CreateInstance(kMenuCID) );
00948   if (pnsMenu) {
00949     // Call Create
00950     nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShellWeakRef);
00951     if (!docShell)
00952         return;
00953     nsCOMPtr<nsISupports> supports(do_QueryInterface(pParentMenu));
00954     pnsMenu->Create(supports, menuName, EmptyString(), mManager, docShell, inMenuItemContent);
00955 
00956     // set if it's enabled or disabled
00957     nsAutoString disabled;
00958     inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, disabled);
00959     if ( disabled.EqualsLiteral("true") )
00960       pnsMenu->SetEnabled ( PR_FALSE );
00961     else
00962       pnsMenu->SetEnabled ( PR_TRUE );
00963 
00964     // Make nsMenu a child of parent nsMenu. The parent takes ownership
00965     nsCOMPtr<nsISupports> supports2 ( do_QueryInterface(pnsMenu) );
00966          pParentMenu->AddItem(supports2);
00967 
00968     pnsMenu->SetupIcon();
00969   }     
00970 }
00971 
00972 
00973 void
00974 nsMenuX::LoadSeparator ( nsIContent* inMenuItemContent ) 
00975 {
00976   // if item should be hidden, bail
00977   nsAutoString hidden;
00978   inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::hidden, hidden);
00979   if ( hidden.EqualsLiteral("true") )
00980     return;
00981 
00982   AddSeparator();
00983 }
00984 
00985 
00986 
00987 //
00988 // OnCreate
00989 //
00990 // Fire our oncreate handler. Returns TRUE if we should keep processing the event,
00991 // FALSE if the handler wants to stop the creation of the menu
00992 //
00993 PRBool
00994 nsMenuX::OnCreate()
00995 {
00996   nsEventStatus status = nsEventStatus_eIgnore;
00997   nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_SHOWING, nsnull,
00998                      nsMouseEvent::eReal);
00999   
01000   nsCOMPtr<nsIContent> popupContent;
01001   GetMenuPopupContent(getter_AddRefs(popupContent));
01002 
01003   nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShellWeakRef);
01004   if (!docShell) {
01005     NS_ERROR("No doc shell");
01006     return PR_FALSE;
01007   }
01008   nsCOMPtr<nsPresContext> presContext;
01009   MenuHelpersX::DocShellToPresContext(docShell, getter_AddRefs(presContext) );
01010   if ( presContext ) {
01011     nsresult rv = NS_OK;
01012     nsIContent* dispatchTo = popupContent ? popupContent : mMenuContent;
01013     rv = dispatchTo->HandleDOMEvent(presContext, &event, nsnull, NS_EVENT_FLAG_INIT, &status);
01014     if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault )
01015       return PR_FALSE;
01016  }
01017 
01018   // the menu is going to show and the oncreate handler has executed. We
01019   // now need to walk our menu items, checking to see if any of them have
01020   // a command attribute. If so, several apptributes must potentially
01021   // be updated.
01022   if (popupContent) {
01023     nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(popupContent->GetDocument()));
01024 
01025     PRUint32 count = popupContent->GetChildCount();
01026     for (PRUint32 i = 0; i < count; i++) {
01027       nsIContent *grandChild = popupContent->GetChildAt(i);
01028       if (grandChild->Tag() == nsWidgetAtoms::menuitem) {
01029         // See if we have a command attribute.
01030         nsAutoString command;
01031         grandChild->GetAttr(kNameSpaceID_None, nsWidgetAtoms::command, command);
01032         if (!command.IsEmpty()) {
01033           // We do! Look it up in our document
01034           nsCOMPtr<nsIDOMElement> commandElt;
01035           domDoc->GetElementById(command, getter_AddRefs(commandElt));
01036           nsCOMPtr<nsIContent> commandContent(do_QueryInterface(commandElt));
01037 
01038           if ( commandContent ) {
01039             nsAutoString commandDisabled, menuDisabled;
01040             commandContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, commandDisabled);
01041             grandChild->GetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, menuDisabled);
01042             if (!commandDisabled.Equals(menuDisabled)) {
01043               // The menu's disabled state needs to be updated to match the command.
01044               if (commandDisabled.IsEmpty()) 
01045                 grandChild->UnsetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, PR_TRUE);
01046               else grandChild->SetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, commandDisabled, PR_TRUE);
01047             }
01048 
01049             // The menu's value and checked states need to be updated to match the command.
01050             // Note that (unlike the disabled state) if the command has *no* value for either, we
01051             // assume the menu is supplying its own.
01052             nsAutoString commandChecked, menuChecked;
01053             commandContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::checked, commandChecked);
01054             grandChild->GetAttr(kNameSpaceID_None, nsWidgetAtoms::checked, menuChecked);
01055             if (!commandChecked.Equals(menuChecked)) {
01056               if (!commandChecked.IsEmpty()) 
01057                 grandChild->SetAttr(kNameSpaceID_None, nsWidgetAtoms::checked, commandChecked, PR_TRUE);
01058             }
01059 
01060             nsAutoString commandValue, menuValue;
01061             commandContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::label, commandValue);
01062             grandChild->GetAttr(kNameSpaceID_None, nsWidgetAtoms::label, menuValue);
01063             if (!commandValue.Equals(menuValue)) {
01064               if (!commandValue.IsEmpty()) 
01065                 grandChild->SetAttr(kNameSpaceID_None, nsWidgetAtoms::label, commandValue, PR_TRUE);
01066             }
01067           }
01068         }
01069       }
01070     }
01071   }
01072   
01073   return PR_TRUE;
01074 }
01075 
01076 PRBool
01077 nsMenuX::OnCreated()
01078 {
01079   nsEventStatus status = nsEventStatus_eIgnore;
01080   nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_SHOWN, nsnull, nsMouseEvent::eReal);
01081   
01082   nsCOMPtr<nsIContent> popupContent;
01083   GetMenuPopupContent(getter_AddRefs(popupContent));
01084 
01085   nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShellWeakRef);
01086   if (!docShell) {
01087     NS_ERROR("No doc shell");
01088     return PR_FALSE;
01089   }
01090   nsCOMPtr<nsPresContext> presContext;
01091   MenuHelpersX::DocShellToPresContext(docShell, getter_AddRefs(presContext) );
01092   if ( presContext ) {
01093     nsresult rv = NS_OK;
01094     nsIContent* dispatchTo = popupContent ? popupContent : mMenuContent;
01095     rv = dispatchTo->HandleDOMEvent(presContext, &event, nsnull, NS_EVENT_FLAG_INIT, &status);
01096     if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault )
01097       return PR_FALSE;
01098  }
01099   
01100   return PR_TRUE;
01101 }
01102 
01103 //
01104 // OnDestroy
01105 //
01106 // Fire our ondestroy handler. Returns TRUE if we should keep processing the event,
01107 // FALSE if the handler wants to stop the destruction of the menu
01108 //
01109 PRBool
01110 nsMenuX::OnDestroy()
01111 {
01112   if ( mDestroyHandlerCalled )
01113     return PR_TRUE;
01114 
01115   nsEventStatus status = nsEventStatus_eIgnore;
01116   nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_HIDING, nsnull,
01117                      nsMouseEvent::eReal);
01118   
01119   nsCOMPtr<nsIDocShell>  docShell = do_QueryReferent(mDocShellWeakRef);
01120   if (!docShell) {
01121     NS_WARNING("No doc shell so can't run the OnDestroy");
01122     return PR_FALSE;
01123   }
01124 
01125   nsCOMPtr<nsIContent> popupContent;
01126   GetMenuPopupContent(getter_AddRefs(popupContent));
01127 
01128   nsCOMPtr<nsPresContext> presContext;
01129   MenuHelpersX::DocShellToPresContext (docShell, getter_AddRefs(presContext) );
01130   if (presContext )  {
01131     nsresult rv = NS_OK;
01132     nsIContent* dispatchTo = popupContent ? popupContent : mMenuContent;
01133     rv = dispatchTo->HandleDOMEvent(presContext, &event, nsnull, NS_EVENT_FLAG_INIT, &status);
01134 
01135     mDestroyHandlerCalled = PR_TRUE;
01136     
01137     if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault )
01138       return PR_FALSE;
01139   }
01140   return PR_TRUE;
01141 }
01142 
01143 PRBool
01144 nsMenuX::OnDestroyed()
01145 {
01146   nsEventStatus status = nsEventStatus_eIgnore;
01147   nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_HIDDEN, nsnull,
01148                      nsMouseEvent::eReal);
01149   
01150   nsCOMPtr<nsIDocShell>  docShell = do_QueryReferent(mDocShellWeakRef);
01151   if (!docShell) {
01152     NS_WARNING("No doc shell so can't run the OnDestroy");
01153     return PR_FALSE;
01154   }
01155 
01156   nsCOMPtr<nsIContent> popupContent;
01157   GetMenuPopupContent(getter_AddRefs(popupContent));
01158 
01159   nsCOMPtr<nsPresContext> presContext;
01160   MenuHelpersX::DocShellToPresContext (docShell, getter_AddRefs(presContext) );
01161   if (presContext )  {
01162     nsresult rv = NS_OK;
01163     nsIContent* dispatchTo = popupContent ? popupContent : mMenuContent;
01164     rv = dispatchTo->HandleDOMEvent(presContext, &event, nsnull, NS_EVENT_FLAG_INIT, &status);
01165 
01166     mDestroyHandlerCalled = PR_TRUE;
01167     
01168     if ( NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault )
01169       return PR_FALSE;
01170   }
01171   return PR_TRUE;
01172 }
01173 //
01174 // GetMenuPopupContent
01175 //
01176 // Find the |menupopup| child in the |popup| representing this menu. It should be one
01177 // of a very few children so we won't be iterating over a bazillion menu items to find
01178 // it (so the strcmp won't kill us).
01179 //
01180 void
01181 nsMenuX::GetMenuPopupContent(nsIContent** aResult)
01182 {
01183   if (!aResult )
01184     return;
01185   *aResult = nsnull;
01186   
01187   nsresult rv;
01188   nsCOMPtr<nsIXBLService> xblService = 
01189            do_GetService("@mozilla.org/xbl;1", &rv);
01190   if ( !xblService )
01191     return;
01192   
01193   PRUint32 count = mMenuContent->GetChildCount();
01194 
01195   for (PRUint32 i = 0; i < count; i++) {
01196     PRInt32 dummy;
01197     nsIContent *child = mMenuContent->GetChildAt(i);
01198     nsCOMPtr<nsIAtom> tag;
01199     xblService->ResolveTag(child, &dummy, getter_AddRefs(tag));
01200     if (tag == nsWidgetAtoms::menupopup) {
01201       *aResult = child;
01202       NS_ADDREF(*aResult);
01203       return;
01204     }
01205   }
01206 
01207 } // GetMenuPopupContent
01208 
01209 
01210 //
01211 // CountVisibleBefore
01212 //
01213 // Determines how many menus are visible among the siblings that are before me.
01214 // It doesn't matter if I am visible. Note that this will always count the Apple
01215 // menu, since we always put it in there.
01216 //
01217 nsresult 
01218 nsMenuX :: CountVisibleBefore ( PRUint32* outVisibleBefore )
01219 {
01220   NS_ASSERTION ( outVisibleBefore, "bad index param" );
01221   
01222   nsCOMPtr<nsIMenuBar> menubarParent = do_QueryInterface(mParent);
01223   if (!menubarParent) return NS_ERROR_FAILURE;
01224 
01225   PRUint32 numMenus = 0;
01226   menubarParent->GetMenuCount(numMenus);
01227   
01228   // Find this menu among the children of my parent menubar
01229   PRBool gotThisMenu = PR_FALSE;
01230   *outVisibleBefore = 1;                            // start at 1, the apple menu will always be there
01231   for ( PRUint32 i = 0; i < numMenus; ++i ) {
01232     nsCOMPtr<nsIMenu> currMenu;
01233     menubarParent->GetMenuAt(i, *getter_AddRefs(currMenu));
01234     
01235     // we found ourselves, break out
01236     if ( currMenu == NS_STATIC_CAST(nsIMenu*, this) ) {
01237       gotThisMenu = PR_TRUE;
01238       break;
01239     }
01240       
01241     // check the current menu to see if it is visible (not hidden, not collapsed). If
01242     // it is, count it.
01243     if (currMenu) {
01244       nsCOMPtr<nsIContent> menuContent;
01245       currMenu->GetMenuContent(getter_AddRefs(menuContent));
01246       if ( menuContent ) {
01247         nsAutoString hiddenValue, collapsedValue;
01248         menuContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::hidden, hiddenValue);
01249         menuContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::collapsed, collapsedValue);
01250         if ( menuContent->GetChildCount() > 0 ||
01251              !hiddenValue.EqualsLiteral("true") &&
01252              !collapsedValue.EqualsLiteral("true"))
01253           ++(*outVisibleBefore);
01254       }
01255     }
01256     
01257   } // for each menu
01258 
01259   return gotThisMenu ? NS_OK : NS_ERROR_FAILURE;
01260 
01261 } // CountVisibleBefore
01262 
01263 NS_IMETHODIMP
01264 nsMenuX::ChangeNativeEnabledStatusForMenuItem(nsIMenuItem* aMenuItem,
01265                                               PRBool aEnabled)
01266 {
01267   MenuRef menuRef;
01268   PRUint16 menuItemIndex;
01269   nsresult rv = GetMenuRefAndItemIndexForMenuItem(aMenuItem,
01270                                                   (void**)&menuRef,
01271                                                   &menuItemIndex);
01272   if (NS_FAILED(rv)) return rv;
01273 
01274   if (aEnabled)
01275     ::EnableMenuItem(menuRef, menuItemIndex);
01276   else
01277     ::DisableMenuItem(menuRef, menuItemIndex);
01278 
01279   return NS_OK;
01280 }
01281 
01282 
01283 NS_IMETHODIMP
01284 nsMenuX::GetMenuRefAndItemIndexForMenuItem(nsISupports* aMenuItem,
01285                                            void**       aMenuRef,
01286                                            PRUint16*    aMenuItemIndex)
01287 {
01288   // look for the menu item given
01289   PRUint32 menuItemCount;
01290   mMenuItemsArray.Count(&menuItemCount);
01291 
01292   for (PRUint32 i = 0; i < menuItemCount; i++) {
01293     nsCOMPtr<nsISupports> currItem; 
01294     mMenuItemsArray.GetElementAt(i, getter_AddRefs(currItem));
01295     if (currItem == aMenuItem) {
01296       *aMenuRef = (void*)mMacMenuHandle;
01297       *aMenuItemIndex = i + 1;
01298       return NS_OK;
01299     }
01300   }
01301 
01302   return NS_ERROR_FAILURE;
01303 }
01304 
01305 
01306 #pragma mark -
01307 
01308 //
01309 // nsIChangeObserver
01310 //
01311 
01312 
01313 NS_IMETHODIMP
01314 nsMenuX::AttributeChanged(nsIDocument *aDocument, PRInt32 aNameSpaceID, nsIContent* aContent, nsIAtom *aAttribute)
01315 {
01316   if (gConstructingMenu)
01317     return NS_OK;
01318 
01319   // ignore the |open| attribute, which is by far the most common
01320   if ( aAttribute == nsWidgetAtoms::open )
01321     return NS_OK;
01322     
01323   nsCOMPtr<nsIMenuBar> menubarParent = do_QueryInterface(mParent);
01324 
01325   if(aAttribute == nsWidgetAtoms::disabled) {
01326     SetRebuild(PR_TRUE);
01327    
01328     nsAutoString valueString;
01329     mMenuContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, valueString);
01330     if(valueString.EqualsLiteral("true"))
01331       SetEnabled(PR_FALSE);
01332     else
01333       SetEnabled(PR_TRUE);
01334       
01335     ::DrawMenuBar();
01336   } 
01337   else if(aAttribute == nsWidgetAtoms::label) {
01338     SetRebuild(PR_TRUE);
01339     
01340     mMenuContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::label, mLabel);
01341 
01342     // invalidate my parent. If we're a submenu parent, we have to rebuild
01343     // the parent menu in order for the changes to be picked up. If we're
01344     // a regular menu, just change the title and redraw the menubar.
01345     if ( !menubarParent ) {
01346       nsCOMPtr<nsIMenuListener> parentListener ( do_QueryInterface(mParent) );
01347       parentListener->SetRebuild(PR_TRUE);
01348     }
01349     else {
01350       // reuse the existing menu, to avoid rebuilding the root menu bar.
01351       NS_ASSERTION(mMacMenuHandle != NULL, "nsMenuX::AttributeChanged: invalid menu handle.");
01352       RemoveAll();
01353       CFStringRef titleRef = ::CFStringCreateWithCharacters(kCFAllocatorDefault, (UniChar*)mLabel.get(), mLabel.Length());
01354       NS_ASSERTION(titleRef, "nsMenuX::AttributeChanged: CFStringCreateWithCharacters failed.");
01355       ::SetMenuTitleWithCFString(mMacMenuHandle, titleRef);
01356       ::CFRelease(titleRef);
01357       
01358       ::DrawMenuBar();
01359     }
01360   }
01361   else if(aAttribute == nsWidgetAtoms::hidden || aAttribute == nsWidgetAtoms::collapsed) {
01362     SetRebuild(PR_TRUE);
01363       
01364       nsAutoString hiddenValue, collapsedValue;
01365       mMenuContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::hidden, hiddenValue);
01366       mMenuContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::collapsed, collapsedValue);
01367         
01368       if (hiddenValue.EqualsLiteral("true") || collapsedValue.EqualsLiteral("true")) {
01369         if ( mVisible ) {
01370           if ( menubarParent ) {
01371             PRUint32 indexToRemove = 0;
01372             if ( NS_SUCCEEDED(CountVisibleBefore(&indexToRemove)) ) {
01373               ++indexToRemove;                // if there are N siblings before me, my index is N+1
01374               void *clientData = nsnull;
01375               menubarParent->GetNativeData ( clientData );
01376               if ( clientData ) {
01377                 MenuRef menubar = reinterpret_cast<MenuRef>(clientData);
01378                 ::SetMenuItemHierarchicalMenu(menubar, indexToRemove, nsnull);
01379                 ::DeleteMenuItem(menubar, indexToRemove);
01380                 mVisible = PR_FALSE;
01381               }
01382             }
01383           } // if on the menubar
01384           else {
01385             // hide this submenu
01386             NS_ASSERTION(PR_FALSE, "nsMenuX::AttributeChanged: WRITE HIDE CODE FOR SUBMENU.");
01387           }
01388         } // if visible
01389         else
01390           NS_WARNING("You're hiding the menu twice, please stop");
01391       } // if told to hide menu
01392       else {
01393         if ( !mVisible ) {
01394           if ( menubarParent ) {
01395             PRUint32 insertAfter = 0;
01396             if ( NS_SUCCEEDED(CountVisibleBefore(&insertAfter)) ) {
01397               void *clientData = nsnull;
01398               menubarParent->GetNativeData ( clientData );
01399               if ( clientData ) {
01400                 MenuRef menubar = reinterpret_cast<MenuRef>(clientData);
01401                 // Shove this menu into its rightful place in the menubar. It doesn't matter
01402                 // what title we pass to InsertMenuItem() because when we stuff the actual menu
01403                 // handle in, the correct title goes with it.
01404                 ::InsertMenuItem(menubar, "\pPlaceholder", insertAfter);
01405                 ::SetMenuItemHierarchicalMenu(menubar, insertAfter + 1, mMacMenuHandle);  // add 1 to get index of inserted item
01406                 mVisible = PR_TRUE;
01407               }
01408             }
01409           } // if on menubar
01410           else {
01411             // show this submenu
01412             NS_ASSERTION(PR_FALSE, "nsMenuX::AttributeChanged: WRITE SHOW CODE FOR SUBMENU.");
01413           }
01414         } // if not visible
01415       } // if told to show menu
01416 
01417       if (menubarParent) {
01418         ::DrawMenuBar();
01419       }
01420   }
01421   else if (aAttribute == nsWidgetAtoms::image) {
01422     SetupIcon();
01423   }
01424 
01425   return NS_OK;
01426   
01427 } // AttributeChanged
01428 
01429 
01430 NS_IMETHODIMP
01431 nsMenuX :: ContentRemoved(nsIDocument *aDocument, nsIContent *aChild, PRInt32 aIndexInContainer)
01432 {  
01433   if (gConstructingMenu)
01434     return NS_OK;
01435 
01436     SetRebuild(PR_TRUE);
01437 
01438   RemoveItem(aIndexInContainer);
01439   mManager->Unregister(aChild);
01440 
01441   return NS_OK;
01442   
01443 } // ContentRemoved
01444 
01445 
01446 NS_IMETHODIMP
01447 nsMenuX :: ContentInserted(nsIDocument *aDocument, nsIContent *aChild, PRInt32 aIndexInContainer)
01448 {  
01449   if(gConstructingMenu)
01450     return NS_OK;
01451 
01452     SetRebuild(PR_TRUE);
01453   
01454   return NS_OK;
01455   
01456 } // ContentInserted
01457 
01458 
01459 NS_IMETHODIMP
01460 nsMenuX::SetupIcon()
01461 {
01462   // In addition to out-of-memory, menus that are children of the menu bar
01463   // will not have mIcon set.
01464 
01465   if (!mIcon) return NS_ERROR_OUT_OF_MEMORY;
01466 
01467   return mIcon->SetupIcon();
01468 }