michael@0: /* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */ 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: #import michael@0: michael@0: #include "nsStandaloneNativeMenu.h" michael@0: #include "nsMenuUtilsX.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIMutationObserver.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsObjCExceptions.h" michael@0: michael@0: michael@0: NS_IMPL_ISUPPORTS(nsStandaloneNativeMenu, nsIMutationObserver, nsIStandaloneNativeMenu) michael@0: michael@0: nsStandaloneNativeMenu::nsStandaloneNativeMenu() michael@0: : mMenu(nullptr) michael@0: { michael@0: } michael@0: michael@0: nsStandaloneNativeMenu::~nsStandaloneNativeMenu() michael@0: { michael@0: if (mMenu) delete mMenu; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStandaloneNativeMenu::Init(nsIDOMElement * aDOMElement) michael@0: { michael@0: NS_ASSERTION(mMenu == nullptr, "nsNativeMenu::Init - mMenu not null!"); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr content = do_QueryInterface(aDOMElement, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsIAtom * tag = content->Tag(); michael@0: if (!content->IsXUL() || michael@0: (tag != nsGkAtoms::menu && tag != nsGkAtoms::menupopup)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: rv = nsMenuGroupOwnerX::Create(content); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: mMenu = new nsMenuX(); michael@0: rv = mMenu->Create(this, this, content); michael@0: if (NS_FAILED(rv)) { michael@0: delete mMenu; michael@0: mMenu = nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static void michael@0: UpdateMenu(nsMenuX * aMenu) michael@0: { michael@0: aMenu->MenuOpened(); michael@0: aMenu->MenuClosed(); michael@0: michael@0: uint32_t itemCount = aMenu->GetItemCount(); michael@0: for (uint32_t i = 0; i < itemCount; i++) { michael@0: nsMenuObjectX * menuObject = aMenu->GetItemAt(i); michael@0: if (menuObject->MenuObjectType() == eSubmenuObjectType) { michael@0: UpdateMenu(static_cast(menuObject)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStandaloneNativeMenu::MenuWillOpen(bool * aResult) michael@0: { michael@0: NS_ASSERTION(mMenu != nullptr, "nsStandaloneNativeMenu::OnOpen - mMenu is null!"); michael@0: michael@0: // Force an update on the mMenu by faking an open/close on all of michael@0: // its submenus. michael@0: UpdateMenu(mMenu); michael@0: michael@0: *aResult = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStandaloneNativeMenu::GetNativeMenu(void ** aVoidPointer) michael@0: { michael@0: if (mMenu) { michael@0: *aVoidPointer = mMenu->NativeData(); michael@0: [[(NSObject *)(*aVoidPointer) retain] autorelease]; michael@0: return NS_OK; michael@0: } else { michael@0: *aVoidPointer = nullptr; michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: } michael@0: michael@0: static NSMenuItem * michael@0: NativeMenuItemWithLocation(NSMenu * currentSubmenu, NSString * locationString) michael@0: { michael@0: NSArray * indexes = [locationString componentsSeparatedByString:@"|"]; michael@0: NSUInteger indexCount = [indexes count]; michael@0: if (indexCount == 0) michael@0: return nil; michael@0: michael@0: for (NSUInteger i = 0; i < indexCount; i++) { michael@0: NSInteger targetIndex = [[indexes objectAtIndex:i] integerValue]; michael@0: NSInteger itemCount = [currentSubmenu numberOfItems]; michael@0: if (targetIndex < itemCount) { michael@0: NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex]; michael@0: michael@0: // If this is the last index, just return the menu item. michael@0: if (i == (indexCount - 1)) michael@0: return menuItem; michael@0: michael@0: // If this is not the last index, find the submenu and keep going. michael@0: if ([menuItem hasSubmenu]) michael@0: currentSubmenu = [menuItem submenu]; michael@0: else michael@0: return nil; michael@0: } michael@0: } michael@0: michael@0: return nil; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStandaloneNativeMenu::ActivateNativeMenuItemAt(const nsAString& indexString) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: if (!mMenu) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: NSString * locationString = [NSString stringWithCharacters:reinterpret_cast(indexString.BeginReading()) michael@0: length:indexString.Length()]; michael@0: NSMenu * menu = static_cast (mMenu->NativeData()); michael@0: NSMenuItem * item = NativeMenuItemWithLocation(menu, locationString); michael@0: michael@0: // We can't perform an action on an item with a submenu, that will raise michael@0: // an obj-c exception. michael@0: if (item && ![item hasSubmenu]) { michael@0: NSMenu * parent = [item menu]; michael@0: if (parent) { michael@0: // NSLog(@"Performing action for native menu item titled: %@\n", michael@0: // [[currentSubmenu itemAtIndex:targetIndex] title]); michael@0: [parent performActionForItemAtIndex:[parent indexOfItem:item]]; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsStandaloneNativeMenu::ForceUpdateNativeMenuAt(const nsAString& indexString) michael@0: { michael@0: if (!mMenu) michael@0: return NS_ERROR_NOT_INITIALIZED; 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 NS_OK; michael@0: michael@0: nsMenuX* currentMenu = mMenu; michael@0: michael@0: // now find the correct submenu michael@0: for (unsigned int i = 1; currentMenu && i < indexCount; i++) { michael@0: int targetIndex = [[indexes objectAtIndex:i] intValue]; michael@0: int visible = 0; michael@0: uint32_t 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 NS_OK; 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: return NS_OK; michael@0: }