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