diff options
Diffstat (limited to 'widget/cocoa/nsStandaloneNativeMenu.mm')
-rw-r--r-- | widget/cocoa/nsStandaloneNativeMenu.mm | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/widget/cocoa/nsStandaloneNativeMenu.mm b/widget/cocoa/nsStandaloneNativeMenu.mm new file mode 100644 index 000000000..98a5fd8f6 --- /dev/null +++ b/widget/cocoa/nsStandaloneNativeMenu.mm @@ -0,0 +1,213 @@ +/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#import <Cocoa/Cocoa.h> + +#include "nsStandaloneNativeMenu.h" +#include "nsMenuUtilsX.h" +#include "nsIDOMElement.h" +#include "nsIMutationObserver.h" +#include "nsGkAtoms.h" +#include "nsObjCExceptions.h" + + +NS_IMPL_ISUPPORTS_INHERITED(nsStandaloneNativeMenu, nsMenuGroupOwnerX, + nsIMutationObserver, nsIStandaloneNativeMenu) + +nsStandaloneNativeMenu::nsStandaloneNativeMenu() +: mMenu(nullptr) +, mContainerStatusBarItem(nil) +{ +} + +nsStandaloneNativeMenu::~nsStandaloneNativeMenu() +{ + if (mMenu) delete mMenu; +} + +NS_IMETHODIMP +nsStandaloneNativeMenu::Init(nsIDOMElement * aDOMElement) +{ + NS_ASSERTION(mMenu == nullptr, "nsNativeMenu::Init - mMenu not null!"); + + nsresult rv; + + nsCOMPtr<nsIContent> content = do_QueryInterface(aDOMElement, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (!content->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menupopup)) + return NS_ERROR_FAILURE; + + rv = nsMenuGroupOwnerX::Create(content); + if (NS_FAILED(rv)) + return rv; + + mMenu = new nsMenuX(); + rv = mMenu->Create(this, this, content); + if (NS_FAILED(rv)) { + delete mMenu; + mMenu = nullptr; + return rv; + } + + mMenu->SetupIcon(); + + return NS_OK; +} + +static void +UpdateMenu(nsMenuX * aMenu) +{ + aMenu->MenuOpened(); + aMenu->MenuClosed(); + + uint32_t itemCount = aMenu->GetItemCount(); + for (uint32_t i = 0; i < itemCount; i++) { + nsMenuObjectX * menuObject = aMenu->GetItemAt(i); + if (menuObject->MenuObjectType() == eSubmenuObjectType) { + UpdateMenu(static_cast<nsMenuX*>(menuObject)); + } + } +} + +NS_IMETHODIMP +nsStandaloneNativeMenu::MenuWillOpen(bool * aResult) +{ + NS_ASSERTION(mMenu != nullptr, "nsStandaloneNativeMenu::OnOpen - mMenu is null!"); + + // Force an update on the mMenu by faking an open/close on all of + // its submenus. + UpdateMenu(mMenu); + + *aResult = true; + return NS_OK; +} + +NS_IMETHODIMP +nsStandaloneNativeMenu::GetNativeMenu(void ** aVoidPointer) +{ + if (mMenu) { + *aVoidPointer = mMenu->NativeData(); + [[(NSObject *)(*aVoidPointer) retain] autorelease]; + return NS_OK; + } else { + *aVoidPointer = nullptr; + return NS_ERROR_NOT_INITIALIZED; + } +} + +static NSMenuItem * +NativeMenuItemWithLocation(NSMenu * currentSubmenu, NSString * locationString) +{ + NSArray * indexes = [locationString componentsSeparatedByString:@"|"]; + NSUInteger indexCount = [indexes count]; + if (indexCount == 0) + return nil; + + for (NSUInteger i = 0; i < indexCount; i++) { + NSInteger targetIndex = [[indexes objectAtIndex:i] integerValue]; + NSInteger itemCount = [currentSubmenu numberOfItems]; + if (targetIndex < itemCount) { + NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex]; + + // If this is the last index, just return the menu item. + if (i == (indexCount - 1)) + return menuItem; + + // If this is not the last index, find the submenu and keep going. + if ([menuItem hasSubmenu]) + currentSubmenu = [menuItem submenu]; + else + return nil; + } + } + + return nil; +} + +NS_IMETHODIMP +nsStandaloneNativeMenu::ActivateNativeMenuItemAt(const nsAString& indexString) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (!mMenu) + return NS_ERROR_NOT_INITIALIZED; + + NSString * locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading()) + length:indexString.Length()]; + NSMenu * menu = static_cast<NSMenu *> (mMenu->NativeData()); + NSMenuItem * item = NativeMenuItemWithLocation(menu, locationString); + + // We can't perform an action on an item with a submenu, that will raise + // an obj-c exception. + if (item && ![item hasSubmenu]) { + NSMenu * parent = [item menu]; + if (parent) { + // NSLog(@"Performing action for native menu item titled: %@\n", + // [[currentSubmenu itemAtIndex:targetIndex] title]); + [parent performActionForItemAtIndex:[parent indexOfItem:item]]; + return NS_OK; + } + } + + return NS_ERROR_FAILURE; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +nsStandaloneNativeMenu::ForceUpdateNativeMenuAt(const nsAString& indexString) +{ + if (!mMenu) + return NS_ERROR_NOT_INITIALIZED; + + NSString* locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading()) + length:indexString.Length()]; + NSArray* indexes = [locationString componentsSeparatedByString:@"|"]; + unsigned int indexCount = [indexes count]; + if (indexCount == 0) + return NS_OK; + + nsMenuX* currentMenu = mMenu; + + // now find the correct submenu + for (unsigned int i = 1; currentMenu && i < indexCount; i++) { + int targetIndex = [[indexes objectAtIndex:i] intValue]; + int visible = 0; + uint32_t length = currentMenu->GetItemCount(); + for (unsigned int j = 0; j < length; j++) { + nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j); + if (!targetMenu) + return NS_OK; + if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) { + visible++; + if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) { + currentMenu = static_cast<nsMenuX*>(targetMenu); + // fake open/close to cause lazy update to happen + currentMenu->MenuOpened(); + currentMenu->MenuClosed(); + break; + } + } + } + } + + return NS_OK; +} + +void +nsStandaloneNativeMenu::IconUpdated() +{ + if (mContainerStatusBarItem) { + [mContainerStatusBarItem setImage:[mMenu->NativeMenuItem() image]]; + } +} + +void +nsStandaloneNativeMenu::SetContainerStatusBarItem(NSStatusItem* aItem) +{ + mContainerStatusBarItem = aItem; + IconUpdated(); +} |