michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: michael@0: #include "nsMenuBarX.h" michael@0: #include "nsMenuX.h" michael@0: #include "nsMenuItemX.h" michael@0: #include "nsMenuUtilsX.h" michael@0: #include "nsCocoaFeatures.h" michael@0: #include "nsCocoaUtils.h" michael@0: #include "nsCocoaWindow.h" michael@0: #include "nsChildView.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsString.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsObjCExceptions.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: #include "nsIContent.h" michael@0: #include "nsIWidget.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMElement.h" michael@0: michael@0: NativeMenuItemTarget* nsMenuBarX::sNativeEventTarget = nil; michael@0: nsMenuBarX* nsMenuBarX::sLastGeckoMenuBarPainted = nullptr; // Weak michael@0: nsMenuBarX* nsMenuBarX::sCurrentPaintDelayedMenuBar = nullptr; // Weak michael@0: NSMenu* sApplicationMenu = nil; michael@0: BOOL gSomeMenuBarPainted = NO; michael@0: michael@0: // We keep references to the first quit and pref item content nodes we find, which michael@0: // will be from the hidden window. We use these when the document for the current michael@0: // window does not have a quit or pref item. We don't need strong refs here because michael@0: // these items are always strong ref'd by their owning menu bar (instance variable). michael@0: static nsIContent* sAboutItemContent = nullptr; michael@0: static nsIContent* sUpdateItemContent = nullptr; michael@0: static nsIContent* sPrefItemContent = nullptr; michael@0: static nsIContent* sQuitItemContent = nullptr; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsNativeMenuServiceX, nsINativeMenuService) michael@0: michael@0: NS_IMETHODIMP nsNativeMenuServiceX::CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Attempting to create native menu bar on wrong thread!"); michael@0: michael@0: nsRefPtr mb = new nsMenuBarX(); michael@0: if (!mb) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: return mb->Create(aParent, aMenuBarNode); michael@0: } michael@0: michael@0: nsMenuBarX::nsMenuBarX() michael@0: : nsMenuGroupOwnerX(), mParentWindow(nullptr), mAwaitingDelayedPaint(false) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: mNativeMenu = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar" andMenuBarOwner:this]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: nsMenuBarX::~nsMenuBarX() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (nsMenuBarX::sLastGeckoMenuBarPainted == this) michael@0: nsMenuBarX::sLastGeckoMenuBarPainted = nullptr; michael@0: michael@0: // the quit/pref items of a random window might have been used if there was no michael@0: // hidden window, thus we need to invalidate the weak references. michael@0: if (sAboutItemContent == mAboutItemContent) michael@0: sAboutItemContent = nullptr; michael@0: if (sUpdateItemContent == mUpdateItemContent) michael@0: sUpdateItemContent = nullptr; michael@0: if (sQuitItemContent == mQuitItemContent) michael@0: sQuitItemContent = nullptr; michael@0: if (sPrefItemContent == mPrefItemContent) michael@0: sPrefItemContent = nullptr; michael@0: michael@0: // make sure we unregister ourselves as a content observer michael@0: UnregisterForContentChanges(mContent); michael@0: michael@0: // We have to manually clear the array here because clearing causes menu items michael@0: // to call back into the menu bar to unregister themselves. We don't want to michael@0: // depend on member variable ordering to ensure that the array gets cleared michael@0: // before the registration hash table is destroyed. michael@0: mMenuArray.Clear(); michael@0: michael@0: [mNativeMenu resetMenuBarOwner]; michael@0: [mNativeMenu release]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: nsresult nsMenuBarX::Create(nsIWidget* aParent, nsIContent* aContent) michael@0: { michael@0: if (!aParent || !aContent) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: mParentWindow = aParent; michael@0: mContent = aContent; michael@0: michael@0: AquifyMenuBar(); michael@0: michael@0: nsresult rv = nsMenuGroupOwnerX::Create(aContent); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: RegisterForContentChanges(aContent, this); michael@0: michael@0: ConstructNativeMenus(); michael@0: michael@0: // Give this to the parent window. The parent takes ownership. michael@0: static_cast(mParentWindow)->SetMenuBar(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsMenuBarX::ConstructNativeMenus() michael@0: { michael@0: uint32_t count = mContent->GetChildCount(); michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: nsIContent *menuContent = mContent->GetChildAt(i); michael@0: if (menuContent && michael@0: menuContent->Tag() == nsGkAtoms::menu && michael@0: menuContent->IsXUL()) { michael@0: nsMenuX* newMenu = new nsMenuX(); michael@0: if (newMenu) { michael@0: nsresult rv = newMenu->Create(this, this, menuContent); michael@0: if (NS_SUCCEEDED(rv)) michael@0: InsertMenuAtIndex(newMenu, GetMenuCount()); michael@0: else michael@0: delete newMenu; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: uint32_t nsMenuBarX::GetMenuCount() michael@0: { michael@0: return mMenuArray.Length(); michael@0: } michael@0: michael@0: bool nsMenuBarX::MenuContainsAppMenu() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: return ([mNativeMenu numberOfItems] > 0 && michael@0: [[mNativeMenu itemAtIndex:0] submenu] == sApplicationMenu); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); michael@0: } michael@0: michael@0: nsresult nsMenuBarX::InsertMenuAtIndex(nsMenuX* aMenu, uint32_t aIndex) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: // If we haven't created a global Application menu yet, do it. michael@0: if (!sApplicationMenu) { michael@0: nsresult rv = NS_OK; // avoid warning about rv being unused michael@0: rv = CreateApplicationMenu(aMenu); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "Can't create Application menu"); michael@0: michael@0: // Hook the new Application menu up to the menu bar. michael@0: NSMenu* mainMenu = [NSApp mainMenu]; michael@0: NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!"); michael@0: [[mainMenu itemAtIndex:0] setSubmenu:sApplicationMenu]; michael@0: } michael@0: michael@0: // add menu to array that owns our menus michael@0: mMenuArray.InsertElementAt(aIndex, aMenu); michael@0: michael@0: // hook up submenus michael@0: nsIContent* menuContent = aMenu->Content(); michael@0: if (menuContent->GetChildCount() > 0 && michael@0: !nsMenuUtilsX::NodeIsHiddenOrCollapsed(menuContent)) { michael@0: int insertionIndex = nsMenuUtilsX::CalculateNativeInsertionPoint(this, aMenu); michael@0: if (MenuContainsAppMenu()) michael@0: insertionIndex++; michael@0: [mNativeMenu insertItem:aMenu->NativeMenuItem() atIndex:insertionIndex]; michael@0: } michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: void nsMenuBarX::RemoveMenuAtIndex(uint32_t aIndex) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NS_ASSERTION(aIndex < mMenuArray.Length(), "Attempting submenu removal with bad index!"); michael@0: michael@0: // Our native menu and our internal menu object array might be out of sync. michael@0: // This happens, for example, when a submenu is hidden. Because of this we michael@0: // should not assume that a native submenu is hooked up. michael@0: NSMenuItem* nativeMenuItem = mMenuArray[aIndex]->NativeMenuItem(); michael@0: int nativeMenuItemIndex = [mNativeMenu indexOfItem:nativeMenuItem]; michael@0: if (nativeMenuItemIndex != -1) michael@0: [mNativeMenu removeItemAtIndex:nativeMenuItemIndex]; michael@0: michael@0: mMenuArray.RemoveElementAt(aIndex); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void nsMenuBarX::ObserveAttributeChanged(nsIDocument* aDocument, michael@0: nsIContent* aContent, michael@0: nsIAtom* aAttribute) michael@0: { michael@0: } michael@0: michael@0: void nsMenuBarX::ObserveContentRemoved(nsIDocument* aDocument, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer) michael@0: { michael@0: RemoveMenuAtIndex(aIndexInContainer); michael@0: } michael@0: michael@0: void nsMenuBarX::ObserveContentInserted(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild) michael@0: { michael@0: nsMenuX* newMenu = new nsMenuX(); michael@0: if (newMenu) { michael@0: nsresult rv = newMenu->Create(this, this, aChild); michael@0: if (NS_SUCCEEDED(rv)) michael@0: InsertMenuAtIndex(newMenu, aContainer->IndexOf(aChild)); michael@0: else michael@0: delete newMenu; michael@0: } michael@0: } michael@0: michael@0: void nsMenuBarX::ForceUpdateNativeMenuAt(const nsAString& indexString) michael@0: { michael@0: NSString* locationString = [NSString stringWithCharacters:reinterpret_cast(indexString.BeginReading()) michael@0: length:indexString.Length()]; michael@0: NSArray* indexes = [locationString componentsSeparatedByString:@"|"]; michael@0: unsigned int indexCount = [indexes count]; michael@0: if (indexCount == 0) michael@0: return; michael@0: michael@0: nsMenuX* currentMenu = NULL; michael@0: int targetIndex = [[indexes objectAtIndex:0] intValue]; michael@0: int visible = 0; michael@0: uint32_t length = mMenuArray.Length(); michael@0: // first find a menu in the menu bar michael@0: for (unsigned int i = 0; i < length; i++) { michael@0: nsMenuX* menu = mMenuArray[i]; michael@0: if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(menu->Content())) { michael@0: visible++; michael@0: if (visible == (targetIndex + 1)) { michael@0: currentMenu = menu; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!currentMenu) michael@0: return; michael@0: michael@0: // fake open/close to cause lazy update to happen so submenus populate michael@0: currentMenu->MenuOpened(); michael@0: currentMenu->MenuClosed(); michael@0: michael@0: // now find the correct submenu michael@0: for (unsigned int i = 1; currentMenu && i < indexCount; i++) { michael@0: targetIndex = [[indexes objectAtIndex:i] intValue]; michael@0: visible = 0; michael@0: length = currentMenu->GetItemCount(); michael@0: for (unsigned int j = 0; j < length; j++) { michael@0: nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j); michael@0: if (!targetMenu) michael@0: return; michael@0: if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) { michael@0: visible++; michael@0: if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) { michael@0: currentMenu = static_cast(targetMenu); michael@0: // fake open/close to cause lazy update to happen michael@0: currentMenu->MenuOpened(); michael@0: currentMenu->MenuClosed(); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Calling this forces a full reload of the menu system, reloading all native michael@0: // menus and their items. michael@0: // Without this testing is hard because changes to the DOM affect the native michael@0: // menu system lazily. michael@0: void nsMenuBarX::ForceNativeMenuReload() michael@0: { michael@0: // tear down everything michael@0: while (GetMenuCount() > 0) michael@0: RemoveMenuAtIndex(0); michael@0: michael@0: // construct everything michael@0: ConstructNativeMenus(); michael@0: } michael@0: michael@0: nsMenuX* nsMenuBarX::GetMenuAt(uint32_t aIndex) michael@0: { michael@0: if (mMenuArray.Length() <= aIndex) { michael@0: NS_ERROR("Requesting menu at invalid index!"); michael@0: return NULL; michael@0: } michael@0: return mMenuArray[aIndex]; michael@0: } michael@0: michael@0: nsMenuX* nsMenuBarX::GetXULHelpMenu() michael@0: { michael@0: // The Help menu is usually (always?) the last one, so we start there and michael@0: // count back. michael@0: for (int32_t i = GetMenuCount() - 1; i >= 0; --i) { michael@0: nsMenuX* aMenu = GetMenuAt(i); michael@0: if (aMenu && nsMenuX::IsXULHelpMenu(aMenu->Content())) michael@0: return aMenu; michael@0: } michael@0: return nil; michael@0: } michael@0: michael@0: // On SnowLeopard and later we must tell the OS which is our Help menu. michael@0: // Otherwise it will only add Spotlight for Help (the Search item) to our michael@0: // Help menu if its label/title is "Help" -- i.e. if the menu is in English. michael@0: // This resolves bugs 489196 and 539317. michael@0: void nsMenuBarX::SetSystemHelpMenu() michael@0: { michael@0: nsMenuX* xulHelpMenu = GetXULHelpMenu(); michael@0: if (xulHelpMenu) { michael@0: NSMenu* helpMenu = (NSMenu*)xulHelpMenu->NativeData(); michael@0: if (helpMenu) michael@0: [NSApp setHelpMenu:helpMenu]; michael@0: } michael@0: } michael@0: michael@0: nsresult nsMenuBarX::Paint(bool aDelayed) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: if (!aDelayed && mAwaitingDelayedPaint) { michael@0: return NS_OK; michael@0: } michael@0: mAwaitingDelayedPaint = false; michael@0: michael@0: // Don't try to optimize anything in this painting by checking michael@0: // sLastGeckoMenuBarPainted because the menubar can be manipulated by michael@0: // native dialogs and sheet code and other things besides this paint method. michael@0: michael@0: // We have to keep the same menu item for the Application menu so we keep michael@0: // passing it along. michael@0: NSMenu* outgoingMenu = [NSApp mainMenu]; michael@0: NS_ASSERTION([outgoingMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!"); michael@0: michael@0: // To work around bug 722676, we sometimes need to delay making mNativeMenu michael@0: // the main menu. This is an Apple bug that sometimes causes a top-level michael@0: // menu item to remain highlighted after pressing a Cmd+key combination that michael@0: // opens a new window, then closing the window. The OS temporarily michael@0: // highlights the appropriate top-level menu item whenever you press the michael@0: // Cmd+key combination for one of its submenus. (It does this by setting a michael@0: // "pressed" attribute on it.) The OS then uses a timer to remove this michael@0: // "pressed" attribute. But without our workaround we sometimes change the michael@0: // main menu before the timer has fired, so when it fires the menu item it michael@0: // was intended to unhighlight is no longer present in the main menu. This michael@0: // causes the item to remain semi-permanently highlighted (until you quit michael@0: // Firefox or navigate the main menu by hand). michael@0: if ((outgoingMenu != mNativeMenu) && michael@0: [outgoingMenu isKindOfClass:[GeckoNSMenu class]]) { michael@0: if (aDelayed) { michael@0: [(GeckoNSMenu *)outgoingMenu setDelayResignMainMenu:false]; michael@0: } else if ([(GeckoNSMenu *)outgoingMenu delayResignMainMenu]) { michael@0: PaintMenuBarAfterDelay(); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: if (outgoingMenu != mNativeMenu) { michael@0: NSMenuItem* appMenuItem = [[outgoingMenu itemAtIndex:0] retain]; michael@0: [outgoingMenu removeItemAtIndex:0]; michael@0: [mNativeMenu insertItem:appMenuItem atIndex:0]; michael@0: [appMenuItem release]; michael@0: // Set menu bar and event target. michael@0: [NSApp setMainMenu:mNativeMenu]; michael@0: } michael@0: SetSystemHelpMenu(); michael@0: nsMenuBarX::sLastGeckoMenuBarPainted = this; michael@0: michael@0: gSomeMenuBarPainted = YES; michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: // Used to delay a call to nsMenuBarX::Paint(). Needed to work around michael@0: // bug 722676. michael@0: void nsMenuBarX::PaintMenuBarAfterDelay() michael@0: { michael@0: mAwaitingDelayedPaint = true; michael@0: nsMenuBarX::sCurrentPaintDelayedMenuBar = this; michael@0: [mNativeMenu retain]; michael@0: // The delay for Apple's unhighlight timer is 0.1f, so we make ours a bit longer. michael@0: [mNativeMenu performSelector:@selector(delayedPaintMenuBar:) michael@0: withObject:nil michael@0: afterDelay:0.15f]; michael@0: } michael@0: michael@0: // Returns the 'key' attribute of the 'shortcutID' object (if any) in the michael@0: // currently active menubar's DOM document. 'shortcutID' should be the id michael@0: // (i.e. the name) of a component that defines a commonly used (and michael@0: // localized) cmd+key shortcut, and belongs to a keyset containing similar michael@0: // objects. For example "key_selectAll". Returns a value that can be michael@0: // compared to the first character of [NSEvent charactersIgnoringModifiers] michael@0: // when [NSEvent modifierFlags] == NSCommandKeyMask. michael@0: char nsMenuBarX::GetLocalizedAccelKey(const char *shortcutID) michael@0: { michael@0: if (!sLastGeckoMenuBarPainted) michael@0: return 0; michael@0: michael@0: nsCOMPtr domDoc(do_QueryInterface(sLastGeckoMenuBarPainted->mDocument)); michael@0: if (!domDoc) michael@0: return 0; michael@0: michael@0: NS_ConvertASCIItoUTF16 shortcutIDStr((const char *)shortcutID); michael@0: nsCOMPtr shortcutElement; michael@0: domDoc->GetElementById(shortcutIDStr, getter_AddRefs(shortcutElement)); michael@0: nsCOMPtr shortcutContent = do_QueryInterface(shortcutElement); michael@0: if (!shortcutContent) michael@0: return 0; michael@0: michael@0: nsAutoString key; michael@0: shortcutContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key); michael@0: NS_LossyConvertUTF16toASCII keyASC(key.get()); michael@0: const char *keyASCPtr = keyASC.get(); michael@0: if (!keyASCPtr) michael@0: return 0; michael@0: // If keyID's 'key' attribute isn't exactly one character long, it's not michael@0: // what we're looking for. michael@0: if (strlen(keyASCPtr) != sizeof(char)) michael@0: return 0; michael@0: // Make sure retval is lower case. michael@0: char retval = tolower(keyASCPtr[0]); michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: // Hide the item in the menu by setting the 'hidden' attribute. Returns it in |outHiddenNode| so michael@0: // the caller can hang onto it if they so choose. It is acceptable to pass nsull michael@0: // for |outHiddenNode| if the caller doesn't care about the hidden node. michael@0: void nsMenuBarX::HideItem(nsIDOMDocument* inDoc, const nsAString & inID, nsIContent** outHiddenNode) michael@0: { michael@0: nsCOMPtr menuItem; michael@0: inDoc->GetElementById(inID, getter_AddRefs(menuItem)); michael@0: nsCOMPtr menuContent(do_QueryInterface(menuItem)); michael@0: if (menuContent) { michael@0: menuContent->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, NS_LITERAL_STRING("true"), false); michael@0: if (outHiddenNode) { michael@0: *outHiddenNode = menuContent.get(); michael@0: NS_IF_ADDREF(*outHiddenNode); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Do what is necessary to conform to the Aqua guidelines for menus. michael@0: void nsMenuBarX::AquifyMenuBar() michael@0: { michael@0: nsCOMPtr domDoc(do_QueryInterface(mContent->GetDocument())); michael@0: if (domDoc) { michael@0: // remove the "About..." item and its separator michael@0: HideItem(domDoc, NS_LITERAL_STRING("aboutSeparator"), nullptr); michael@0: HideItem(domDoc, NS_LITERAL_STRING("aboutName"), getter_AddRefs(mAboutItemContent)); michael@0: if (!sAboutItemContent) michael@0: sAboutItemContent = mAboutItemContent; michael@0: michael@0: // Hide the software update menu item, since it belongs in the application michael@0: // menu on Mac OS X. michael@0: HideItem(domDoc, NS_LITERAL_STRING("updateSeparator"), nullptr); michael@0: HideItem(domDoc, NS_LITERAL_STRING("checkForUpdates"), getter_AddRefs(mUpdateItemContent)); michael@0: if (!sUpdateItemContent) michael@0: sUpdateItemContent = mUpdateItemContent; michael@0: michael@0: // remove quit item and its separator michael@0: HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitSeparator"), nullptr); michael@0: HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitItem"), getter_AddRefs(mQuitItemContent)); michael@0: if (!sQuitItemContent) michael@0: sQuitItemContent = mQuitItemContent; michael@0: michael@0: // remove prefs item and its separator, but save off the pref content node michael@0: // so we can invoke its command later. michael@0: HideItem(domDoc, NS_LITERAL_STRING("menu_PrefsSeparator"), nullptr); michael@0: HideItem(domDoc, NS_LITERAL_STRING("menu_preferences"), getter_AddRefs(mPrefItemContent)); michael@0: if (!sPrefItemContent) michael@0: sPrefItemContent = mPrefItemContent; michael@0: michael@0: // hide items that we use for the Application menu michael@0: HideItem(domDoc, NS_LITERAL_STRING("menu_mac_services"), nullptr); michael@0: HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_app"), nullptr); michael@0: HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_others"), nullptr); michael@0: HideItem(domDoc, NS_LITERAL_STRING("menu_mac_show_all"), nullptr); michael@0: } michael@0: } michael@0: michael@0: // for creating menu items destined for the Application menu michael@0: NSMenuItem* nsMenuBarX::CreateNativeAppMenuItem(nsMenuX* inMenu, const nsAString& nodeID, SEL action, michael@0: int tag, NativeMenuItemTarget* target) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: nsCOMPtr doc = inMenu->Content()->GetDocument(); michael@0: if (!doc) michael@0: return nil; michael@0: michael@0: nsCOMPtr domdoc(do_QueryInterface(doc)); michael@0: if (!domdoc) michael@0: return nil; michael@0: michael@0: // Get information from the gecko menu item michael@0: nsAutoString label; michael@0: nsAutoString modifiers; michael@0: nsAutoString key; michael@0: nsCOMPtr menuItem; michael@0: domdoc->GetElementById(nodeID, getter_AddRefs(menuItem)); michael@0: if (menuItem) { michael@0: menuItem->GetAttribute(NS_LITERAL_STRING("label"), label); michael@0: menuItem->GetAttribute(NS_LITERAL_STRING("modifiers"), modifiers); michael@0: menuItem->GetAttribute(NS_LITERAL_STRING("key"), key); michael@0: } michael@0: else { michael@0: return nil; michael@0: } michael@0: michael@0: // Get more information about the key equivalent. Start by michael@0: // finding the key node we need. michael@0: NSString* keyEquiv = nil; michael@0: unsigned int macKeyModifiers = 0; michael@0: if (!key.IsEmpty()) { michael@0: nsCOMPtr keyElement; michael@0: domdoc->GetElementById(key, getter_AddRefs(keyElement)); michael@0: if (keyElement) { michael@0: nsCOMPtr keyContent (do_QueryInterface(keyElement)); michael@0: // first grab the key equivalent character michael@0: nsAutoString keyChar(NS_LITERAL_STRING(" ")); michael@0: keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyChar); michael@0: if (!keyChar.EqualsLiteral(" ")) { michael@0: keyEquiv = [[NSString stringWithCharacters:reinterpret_cast(keyChar.get()) michael@0: length:keyChar.Length()] lowercaseString]; michael@0: } michael@0: // now grab the key equivalent modifiers michael@0: nsAutoString modifiersStr; michael@0: keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr); michael@0: uint8_t geckoModifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr); michael@0: macKeyModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(geckoModifiers); michael@0: } michael@0: } michael@0: // get the label into NSString-form michael@0: NSString* labelString = [NSString stringWithCharacters:reinterpret_cast(label.get()) michael@0: length:label.Length()]; michael@0: michael@0: if (!labelString) michael@0: labelString = @""; michael@0: if (!keyEquiv) michael@0: keyEquiv = @""; michael@0: michael@0: // put together the actual NSMenuItem michael@0: NSMenuItem* newMenuItem = [[NSMenuItem alloc] initWithTitle:labelString action:action keyEquivalent:keyEquiv]; michael@0: michael@0: [newMenuItem setTag:tag]; michael@0: [newMenuItem setTarget:target]; michael@0: [newMenuItem setKeyEquivalentModifierMask:macKeyModifiers]; michael@0: michael@0: MenuItemInfo * info = [[MenuItemInfo alloc] initWithMenuGroupOwner:this]; michael@0: [newMenuItem setRepresentedObject:info]; michael@0: [info release]; michael@0: michael@0: return newMenuItem; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: // build the Application menu shared by all menu bars michael@0: nsresult nsMenuBarX::CreateApplicationMenu(nsMenuX* inMenu) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: // At this point, the application menu is the application menu from michael@0: // the nib in cocoa widgets. We do not have a way to create an application michael@0: // menu manually, so we grab the one from the nib and use that. michael@0: sApplicationMenu = [[[[NSApp mainMenu] itemAtIndex:0] submenu] retain]; michael@0: michael@0: /* michael@0: We support the following menu items here: michael@0: michael@0: Menu Item DOM Node ID Notes michael@0: michael@0: ======================== michael@0: = About This App = <- aboutName michael@0: = Check for Updates... = <- checkForUpdates michael@0: ======================== michael@0: = Preferences... = <- menu_preferences michael@0: ======================== michael@0: = Services > = <- menu_mac_services <- (do not define key equivalent) michael@0: ======================== michael@0: = Hide App = <- menu_mac_hide_app michael@0: = Hide Others = <- menu_mac_hide_others michael@0: = Show All = <- menu_mac_show_all michael@0: ======================== michael@0: = Quit = <- menu_FileQuitItem michael@0: ======================== michael@0: michael@0: If any of them are ommitted from the application's DOM, we just don't add michael@0: them. We always add a "Quit" item, but if an app developer does not provide a michael@0: DOM node with the right ID for the Quit item, we add it in English. App michael@0: developers need only add each node with a label and a key equivalent (if they michael@0: want one). Other attributes are optional. Like so: michael@0: michael@0: michael@0: michael@0: We need to use this system for localization purposes, until we have a better way michael@0: to define the Application menu to be used on Mac OS X. michael@0: */ michael@0: michael@0: if (sApplicationMenu) { michael@0: // This code reads attributes we are going to care about from the DOM elements michael@0: michael@0: NSMenuItem *itemBeingAdded = nil; michael@0: BOOL addAboutSeparator = FALSE; michael@0: michael@0: // Add the About menu item michael@0: itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("aboutName"), @selector(menuItemHit:), michael@0: eCommand_ID_About, nsMenuBarX::sNativeEventTarget); michael@0: if (itemBeingAdded) { michael@0: [sApplicationMenu addItem:itemBeingAdded]; michael@0: [itemBeingAdded release]; michael@0: itemBeingAdded = nil; michael@0: michael@0: addAboutSeparator = TRUE; michael@0: } michael@0: michael@0: // Add the software update menu item michael@0: itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("checkForUpdates"), @selector(menuItemHit:), michael@0: eCommand_ID_Update, nsMenuBarX::sNativeEventTarget); michael@0: if (itemBeingAdded) { michael@0: [sApplicationMenu addItem:itemBeingAdded]; michael@0: [itemBeingAdded release]; michael@0: itemBeingAdded = nil; michael@0: michael@0: addAboutSeparator = TRUE; michael@0: } michael@0: michael@0: // Add separator if either the About item or software update item exists michael@0: if (addAboutSeparator) michael@0: [sApplicationMenu addItem:[NSMenuItem separatorItem]]; michael@0: michael@0: // Add the Preferences menu item michael@0: itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_preferences"), @selector(menuItemHit:), michael@0: eCommand_ID_Prefs, nsMenuBarX::sNativeEventTarget); michael@0: if (itemBeingAdded) { michael@0: [sApplicationMenu addItem:itemBeingAdded]; michael@0: [itemBeingAdded release]; michael@0: itemBeingAdded = nil; michael@0: michael@0: // Add separator after Preferences menu michael@0: [sApplicationMenu addItem:[NSMenuItem separatorItem]]; michael@0: } michael@0: michael@0: // Add Services menu item michael@0: itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_services"), nil, michael@0: 0, nil); michael@0: if (itemBeingAdded) { michael@0: [sApplicationMenu addItem:itemBeingAdded]; michael@0: michael@0: // set this menu item up as the Mac OS X Services menu michael@0: NSMenu* servicesMenu = [[GeckoServicesNSMenu alloc] initWithTitle:@""]; michael@0: [itemBeingAdded setSubmenu:servicesMenu]; michael@0: [NSApp setServicesMenu:servicesMenu]; michael@0: michael@0: [itemBeingAdded release]; michael@0: itemBeingAdded = nil; michael@0: michael@0: // Add separator after Services menu michael@0: [sApplicationMenu addItem:[NSMenuItem separatorItem]]; michael@0: } michael@0: michael@0: BOOL addHideShowSeparator = FALSE; michael@0: michael@0: // Add menu item to hide this application michael@0: itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_app"), @selector(menuItemHit:), michael@0: eCommand_ID_HideApp, nsMenuBarX::sNativeEventTarget); michael@0: if (itemBeingAdded) { michael@0: [sApplicationMenu addItem:itemBeingAdded]; michael@0: [itemBeingAdded release]; michael@0: itemBeingAdded = nil; michael@0: michael@0: addHideShowSeparator = TRUE; michael@0: } michael@0: michael@0: // Add menu item to hide other applications michael@0: itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_others"), @selector(menuItemHit:), michael@0: eCommand_ID_HideOthers, nsMenuBarX::sNativeEventTarget); michael@0: if (itemBeingAdded) { michael@0: [sApplicationMenu addItem:itemBeingAdded]; michael@0: [itemBeingAdded release]; michael@0: itemBeingAdded = nil; michael@0: michael@0: addHideShowSeparator = TRUE; michael@0: } michael@0: michael@0: // Add menu item to show all applications michael@0: itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_show_all"), @selector(menuItemHit:), michael@0: eCommand_ID_ShowAll, nsMenuBarX::sNativeEventTarget); michael@0: if (itemBeingAdded) { michael@0: [sApplicationMenu addItem:itemBeingAdded]; michael@0: [itemBeingAdded release]; michael@0: itemBeingAdded = nil; michael@0: michael@0: addHideShowSeparator = TRUE; michael@0: } michael@0: michael@0: // Add a separator after the hide/show menus if at least one exists michael@0: if (addHideShowSeparator) michael@0: [sApplicationMenu addItem:[NSMenuItem separatorItem]]; michael@0: michael@0: // Add quit menu item michael@0: itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_FileQuitItem"), @selector(menuItemHit:), michael@0: eCommand_ID_Quit, nsMenuBarX::sNativeEventTarget); michael@0: if (itemBeingAdded) { michael@0: [sApplicationMenu addItem:itemBeingAdded]; michael@0: [itemBeingAdded release]; michael@0: itemBeingAdded = nil; michael@0: } michael@0: else { michael@0: // the current application does not have a DOM node for "Quit". Add one michael@0: // anyway, in English. michael@0: NSMenuItem* defaultQuitItem = [[[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(menuItemHit:) michael@0: keyEquivalent:@"q"] autorelease]; michael@0: [defaultQuitItem setTarget:nsMenuBarX::sNativeEventTarget]; michael@0: [defaultQuitItem setTag:eCommand_ID_Quit]; michael@0: [sApplicationMenu addItem:defaultQuitItem]; michael@0: } michael@0: } michael@0: michael@0: return (sApplicationMenu) ? NS_OK : NS_ERROR_FAILURE; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: void nsMenuBarX::SetParent(nsIWidget* aParent) michael@0: { michael@0: mParentWindow = aParent; michael@0: } michael@0: michael@0: michael@0: // michael@0: // Objective-C class used to allow us to have keyboard commands michael@0: // look like they are doing something but actually do nothing. michael@0: // We allow mouse actions to work normally. michael@0: // michael@0: michael@0: // Controls whether or not native menu items should invoke their commands. michael@0: static BOOL gMenuItemsExecuteCommands = YES; michael@0: michael@0: @implementation GeckoNSMenu michael@0: michael@0: - (id)initWithTitle:(NSString *)aTitle michael@0: { michael@0: if (self = [super initWithTitle:aTitle]) { michael@0: mMenuBarOwner = nullptr; michael@0: mDelayResignMainMenu = false; michael@0: } michael@0: return self; michael@0: } michael@0: michael@0: - (id)initWithTitle:(NSString *)aTitle andMenuBarOwner:(nsMenuBarX *)aMenuBarOwner michael@0: { michael@0: if (self = [super initWithTitle:aTitle]) { michael@0: mMenuBarOwner = aMenuBarOwner; michael@0: mDelayResignMainMenu = false; michael@0: } michael@0: return self; michael@0: } michael@0: michael@0: - (void)resetMenuBarOwner michael@0: { michael@0: mMenuBarOwner = nil; michael@0: } michael@0: michael@0: - (bool)delayResignMainMenu michael@0: { michael@0: return mDelayResignMainMenu; michael@0: } michael@0: michael@0: - (void)setDelayResignMainMenu:(bool)aShouldDelay michael@0: { michael@0: mDelayResignMainMenu = aShouldDelay; michael@0: } michael@0: michael@0: // Used to delay a call to nsMenuBarX::Paint(). Needed to work around michael@0: // bug 722676. michael@0: - (void)delayedPaintMenuBar:(id)unused michael@0: { michael@0: if (mMenuBarOwner) { michael@0: if (mMenuBarOwner == nsMenuBarX::sCurrentPaintDelayedMenuBar) { michael@0: mMenuBarOwner->Paint(true); michael@0: nsMenuBarX::sCurrentPaintDelayedMenuBar = nullptr; michael@0: } else { michael@0: mMenuBarOwner->ResetAwaitingDelayedPaint(); michael@0: } michael@0: } michael@0: [self release]; michael@0: } michael@0: michael@0: // Undocumented method, present unchanged since OS X 10.6, used to temporarily michael@0: // highlight a top-level menu item when an appropriate Cmd+key combination is michael@0: // pressed. michael@0: - (void)_performActionWithHighlightingForItemAtIndex:(NSInteger)index; michael@0: { michael@0: NSMenu *mainMenu = [NSApp mainMenu]; michael@0: if ([mainMenu isKindOfClass:[GeckoNSMenu class]]) { michael@0: [(GeckoNSMenu *)mainMenu setDelayResignMainMenu:true]; michael@0: } michael@0: [super _performActionWithHighlightingForItemAtIndex:index]; michael@0: } michael@0: michael@0: // Keyboard commands should not cause menu items to invoke their michael@0: // commands when there is a key window because we'd rather send michael@0: // the keyboard command to the window. We still have the menus michael@0: // go through the mechanics so they'll give the proper visual michael@0: // feedback. michael@0: - (BOOL)performKeyEquivalent:(NSEvent *)theEvent michael@0: { michael@0: // We've noticed that Mac OS X expects this check in subclasses before michael@0: // calling NSMenu's "performKeyEquivalent:". michael@0: // michael@0: // There is no case in which we'd need to do anything or return YES michael@0: // when we have no items so we can just do this check first. michael@0: if ([self numberOfItems] <= 0) { michael@0: return NO; michael@0: } michael@0: michael@0: NSWindow *keyWindow = [NSApp keyWindow]; michael@0: michael@0: // If there is no key window then just behave normally. This michael@0: // probably means that this menu is associated with Gecko's michael@0: // hidden window. michael@0: if (!keyWindow) { michael@0: return [super performKeyEquivalent:theEvent]; michael@0: } michael@0: michael@0: // Plugins normally eat all keyboard commands, this hack mitigates michael@0: // the problem. michael@0: BOOL handleForPluginHack = NO; michael@0: NSResponder *firstResponder = [keyWindow firstResponder]; michael@0: if (firstResponder && michael@0: [firstResponder isKindOfClass:[ChildView class]] && michael@0: [(ChildView*)firstResponder isPluginView]) { michael@0: handleForPluginHack = YES; michael@0: // Maintain a list of cmd+key combinations that we never act on (in the michael@0: // browser) when the keyboard focus is in a plugin. What a particular michael@0: // cmd+key combo means here (to the browser) is governed by browser.dtd, michael@0: // which "contains the browser main menu items". michael@0: UInt32 modifierFlags = [theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask; michael@0: if (modifierFlags == NSCommandKeyMask) { michael@0: NSString *unmodchars = [theEvent charactersIgnoringModifiers]; michael@0: if ([unmodchars length] == 1) { michael@0: if ([unmodchars characterAtIndex:0] == nsMenuBarX::GetLocalizedAccelKey("key_selectAll")) { michael@0: handleForPluginHack = NO; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: gMenuItemsExecuteCommands = handleForPluginHack; michael@0: [super performKeyEquivalent:theEvent]; michael@0: gMenuItemsExecuteCommands = YES; // return to default michael@0: michael@0: // Return YES if we invoked a command and there is now no key window or we changed michael@0: // the first responder. In this case we do not want to propagate the event because michael@0: // we don't want it handled again. michael@0: if (handleForPluginHack) { michael@0: if (![NSApp keyWindow] || [[NSApp keyWindow] firstResponder] != firstResponder) { michael@0: return YES; michael@0: } michael@0: } michael@0: michael@0: // Return NO so that we can handle the event via NSView's "keyDown:". michael@0: return NO; michael@0: } michael@0: michael@0: @end michael@0: michael@0: // michael@0: // Objective-C class used as action target for menu items michael@0: // michael@0: michael@0: @implementation NativeMenuItemTarget michael@0: michael@0: // called when some menu item in this menu gets hit michael@0: -(IBAction)menuItemHit:(id)sender michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: // menuGroupOwner below is an nsMenuBarX object, which we sometimes access michael@0: // after it's been deleted, causing crashes (see bug 704866 and bug 670914). michael@0: // To fix this "correctly", in nsMenuBarX::~nsMenuBarX() we'd need to michael@0: // iterate through every NSMenuItem in nsMenuBarX::mNativeMenu and its michael@0: // submenus, which might be quite time consuming. (For every NSMenuItem michael@0: // that has a "representedObject" that's a MenuItemInfo object, we'd need michael@0: // need to null out its "menuGroupOwner" if it's the same as the nsMenuBarX michael@0: // object being destroyed.) But if the nsMenuBarX object being destroyed michael@0: // corresponds to the currently focused window, it's likely that the michael@0: // nsMenuBarX destructor will null out sLastGeckoMenuBarPainted. So we can michael@0: // probably eliminate most of these crashes if we use this variable being michael@0: // null as an indicator that we're likely to crash below when we dereference michael@0: // menuGroupOwner. michael@0: if (!nsMenuBarX::sLastGeckoMenuBarPainted) { michael@0: return; michael@0: } michael@0: michael@0: if (!gMenuItemsExecuteCommands) { michael@0: return; michael@0: } michael@0: michael@0: int tag = [sender tag]; michael@0: michael@0: MenuItemInfo* info = [sender representedObject]; michael@0: if (!info) michael@0: return; michael@0: michael@0: nsMenuGroupOwnerX* menuGroupOwner = [info menuGroupOwner]; michael@0: if (!menuGroupOwner) michael@0: return; michael@0: michael@0: nsMenuBarX* menuBar = nullptr; michael@0: if (menuGroupOwner->MenuObjectType() == eMenuBarObjectType) michael@0: menuBar = static_cast(menuGroupOwner); michael@0: michael@0: // Do special processing if this is for an app-global command. michael@0: if (tag == eCommand_ID_About) { michael@0: nsIContent* mostSpecificContent = sAboutItemContent; michael@0: if (menuBar && menuBar->mAboutItemContent) michael@0: mostSpecificContent = menuBar->mAboutItemContent; michael@0: nsMenuUtilsX::DispatchCommandTo(mostSpecificContent); michael@0: return; michael@0: } michael@0: else if (tag == eCommand_ID_Update) { michael@0: nsIContent* mostSpecificContent = sUpdateItemContent; michael@0: if (menuBar && menuBar->mUpdateItemContent) michael@0: mostSpecificContent = menuBar->mUpdateItemContent; michael@0: nsMenuUtilsX::DispatchCommandTo(mostSpecificContent); michael@0: } michael@0: else if (tag == eCommand_ID_Prefs) { michael@0: nsIContent* mostSpecificContent = sPrefItemContent; michael@0: if (menuBar && menuBar->mPrefItemContent) michael@0: mostSpecificContent = menuBar->mPrefItemContent; michael@0: nsMenuUtilsX::DispatchCommandTo(mostSpecificContent); michael@0: return; michael@0: } michael@0: else if (tag == eCommand_ID_HideApp) { michael@0: [NSApp hide:sender]; michael@0: return; michael@0: } michael@0: else if (tag == eCommand_ID_HideOthers) { michael@0: [NSApp hideOtherApplications:sender]; michael@0: return; michael@0: } michael@0: else if (tag == eCommand_ID_ShowAll) { michael@0: [NSApp unhideAllApplications:sender]; michael@0: return; michael@0: } michael@0: else if (tag == eCommand_ID_Quit) { michael@0: nsIContent* mostSpecificContent = sQuitItemContent; michael@0: if (menuBar && menuBar->mQuitItemContent) michael@0: mostSpecificContent = menuBar->mQuitItemContent; michael@0: // If we have some content for quit we execute it. Otherwise we send a native app terminate michael@0: // message. If you want to stop a quit from happening, provide quit content and return michael@0: // the event as unhandled. michael@0: if (mostSpecificContent) { michael@0: nsMenuUtilsX::DispatchCommandTo(mostSpecificContent); michael@0: } michael@0: else { michael@0: [NSApp terminate:nil]; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // given the commandID, look it up in our hashtable and dispatch to michael@0: // that menu item. michael@0: if (menuGroupOwner) { michael@0: nsMenuItemX* menuItem = menuGroupOwner->GetMenuItemForCommandID(static_cast(tag)); michael@0: if (menuItem) michael@0: menuItem->DoCommand(); michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: @end michael@0: michael@0: // Objective-C class used for menu items on the Services menu to allow Gecko michael@0: // to override their standard behavior in order to stop key equivalents from michael@0: // firing in certain instances. When gMenuItemsExecuteCommands is NO, we return michael@0: // a dummy target and action instead of the actual target and action. michael@0: michael@0: @implementation GeckoServicesNSMenuItem michael@0: michael@0: - (id) target michael@0: { michael@0: id realTarget = [super target]; michael@0: if (gMenuItemsExecuteCommands) michael@0: return realTarget; michael@0: else michael@0: return realTarget ? self : nil; michael@0: } michael@0: michael@0: - (SEL) action michael@0: { michael@0: SEL realAction = [super action]; michael@0: if (gMenuItemsExecuteCommands) michael@0: return realAction; michael@0: else michael@0: return realAction ? @selector(_doNothing:) : NULL; michael@0: } michael@0: michael@0: - (void) _doNothing:(id)sender michael@0: { michael@0: } michael@0: michael@0: @end michael@0: michael@0: // Objective-C class used as the Services menu so that Gecko can override the michael@0: // standard behavior of the Services menu in order to stop key equivalents michael@0: // from firing in certain instances. michael@0: michael@0: @implementation GeckoServicesNSMenu michael@0: michael@0: - (void)addItem:(NSMenuItem *)newItem michael@0: { michael@0: [self _overrideClassOfMenuItem:newItem]; michael@0: [super addItem:newItem]; michael@0: } michael@0: michael@0: - (NSMenuItem *)addItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv michael@0: { michael@0: NSMenuItem * newItem = [super addItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv]; michael@0: [self _overrideClassOfMenuItem:newItem]; michael@0: return newItem; michael@0: } michael@0: michael@0: - (void)insertItem:(NSMenuItem *)newItem atIndex:(NSInteger)index michael@0: { michael@0: [self _overrideClassOfMenuItem:newItem]; michael@0: [super insertItem:newItem atIndex:index]; michael@0: } michael@0: michael@0: - (NSMenuItem *)insertItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv atIndex:(NSInteger)index michael@0: { michael@0: NSMenuItem * newItem = [super insertItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv atIndex:index]; michael@0: [self _overrideClassOfMenuItem:newItem]; michael@0: return newItem; michael@0: } michael@0: michael@0: - (void) _overrideClassOfMenuItem:(NSMenuItem *)menuItem michael@0: { michael@0: if ([menuItem class] == [NSMenuItem class]) michael@0: object_setClass(menuItem, [GeckoServicesNSMenuItem class]); michael@0: } michael@0: michael@0: @end