gecko-dev/widget/cocoa/nsStandaloneNativeMenu.mm

217 lines
5.9 KiB
Plaintext

/* -*- 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 "nsIMutationObserver.h"
#include "nsGkAtoms.h"
#include "nsObjCExceptions.h"
#include "mozilla/dom/Element.h"
using mozilla::dom::Element;
NS_IMPL_ISUPPORTS_INHERITED(nsStandaloneNativeMenu, nsMenuGroupOwnerX,
nsIMutationObserver, nsIStandaloneNativeMenu)
nsStandaloneNativeMenu::nsStandaloneNativeMenu()
: mMenu(nullptr)
, mContainerStatusBarItem(nil)
{
}
nsStandaloneNativeMenu::~nsStandaloneNativeMenu()
{
if (mMenu) delete mMenu;
}
NS_IMETHODIMP
nsStandaloneNativeMenu::Init(Element* aElement)
{
NS_ASSERTION(mMenu == nullptr, "nsNativeMenu::Init - mMenu not null!");
NS_ENSURE_ARG(aElement);
if (!aElement->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menupopup))
return NS_ERROR_FAILURE;
nsresult rv = nsMenuGroupOwnerX::Create(aElement);
if (NS_FAILED(rv))
return rv;
mMenu = new nsMenuX();
rv = mMenu->Create(this, this, aElement);
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) {
NSImage* menuImage = [mMenu->NativeMenuItem() image];
if (menuImage) {
[menuImage setTemplate:true];
}
[mContainerStatusBarItem setImage:menuImage];
}
}
void
nsStandaloneNativeMenu::SetContainerStatusBarItem(NSStatusItem* aItem)
{
mContainerStatusBarItem = aItem;
IconUpdated();
}