mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-19 23:07:33 +09:00
Issue #1751 -- Remove cocoa and uikit widget support code
This commit is contained in:
parent
a6ae35ba91
commit
3a4e65f1de
165 changed files with 4 additions and 42109 deletions
|
|
@ -1,979 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include <objc/objc-runtime.h>
|
||||
|
||||
#include "nsMenuBarX.h"
|
||||
#include "nsMenuX.h"
|
||||
#include "nsMenuItemX.h"
|
||||
#include "nsMenuUtilsX.h"
|
||||
#include "nsCocoaUtils.h"
|
||||
#include "nsCocoaWindow.h"
|
||||
#include "nsChildView.h"
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsString.h"
|
||||
#include "nsGkAtoms.h"
|
||||
#include "nsObjCExceptions.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
#include "nsIContent.h"
|
||||
#include "nsIWidget.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIDOMDocument.h"
|
||||
#include "nsIDOMElement.h"
|
||||
#include "nsIAppStartup.h"
|
||||
#include "nsIStringBundle.h"
|
||||
#include "nsToolkitCompsCID.h"
|
||||
|
||||
NativeMenuItemTarget* nsMenuBarX::sNativeEventTarget = nil;
|
||||
nsMenuBarX* nsMenuBarX::sLastGeckoMenuBarPainted = nullptr;
|
||||
NSMenu* sApplicationMenu = nil;
|
||||
BOOL sApplicationMenuIsFallback = NO;
|
||||
BOOL gSomeMenuBarPainted = NO;
|
||||
|
||||
// We keep references to the first quit and pref item content nodes we find, which
|
||||
// will be from the hidden window. We use these when the document for the current
|
||||
// window does not have a quit or pref item. We don't need strong refs here because
|
||||
// these items are always strong ref'd by their owning menu bar (instance variable).
|
||||
static nsIContent* sAboutItemContent = nullptr;
|
||||
static nsIContent* sPrefItemContent = nullptr;
|
||||
static nsIContent* sQuitItemContent = nullptr;
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsNativeMenuServiceX, nsINativeMenuService)
|
||||
|
||||
NS_IMETHODIMP nsNativeMenuServiceX::CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Attempting to create native menu bar on wrong thread!");
|
||||
|
||||
RefPtr<nsMenuBarX> mb = new nsMenuBarX();
|
||||
if (!mb)
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
return mb->Create(aParent, aMenuBarNode);
|
||||
}
|
||||
|
||||
nsMenuBarX::nsMenuBarX()
|
||||
: nsMenuGroupOwnerX(), mParentWindow(nullptr)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
||||
|
||||
mNativeMenu = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"];
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK;
|
||||
}
|
||||
|
||||
nsMenuBarX::~nsMenuBarX()
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
||||
|
||||
if (nsMenuBarX::sLastGeckoMenuBarPainted == this)
|
||||
nsMenuBarX::sLastGeckoMenuBarPainted = nullptr;
|
||||
|
||||
// the quit/pref items of a random window might have been used if there was no
|
||||
// hidden window, thus we need to invalidate the weak references.
|
||||
if (sAboutItemContent == mAboutItemContent)
|
||||
sAboutItemContent = nullptr;
|
||||
if (sQuitItemContent == mQuitItemContent)
|
||||
sQuitItemContent = nullptr;
|
||||
if (sPrefItemContent == mPrefItemContent)
|
||||
sPrefItemContent = nullptr;
|
||||
|
||||
// make sure we unregister ourselves as a content observer
|
||||
if (mContent) {
|
||||
UnregisterForContentChanges(mContent);
|
||||
}
|
||||
|
||||
// We have to manually clear the array here because clearing causes menu items
|
||||
// to call back into the menu bar to unregister themselves. We don't want to
|
||||
// depend on member variable ordering to ensure that the array gets cleared
|
||||
// before the registration hash table is destroyed.
|
||||
mMenuArray.Clear();
|
||||
|
||||
[mNativeMenu release];
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK;
|
||||
}
|
||||
|
||||
nsresult nsMenuBarX::Create(nsIWidget* aParent, nsIContent* aContent)
|
||||
{
|
||||
if (!aParent)
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
|
||||
mParentWindow = aParent;
|
||||
mContent = aContent;
|
||||
|
||||
if (mContent) {
|
||||
AquifyMenuBar();
|
||||
|
||||
nsresult rv = nsMenuGroupOwnerX::Create(mContent);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
RegisterForContentChanges(mContent, this);
|
||||
ConstructNativeMenus();
|
||||
} else {
|
||||
ConstructFallbackNativeMenus();
|
||||
}
|
||||
|
||||
// Give this to the parent window. The parent takes ownership.
|
||||
static_cast<nsCocoaWindow*>(mParentWindow)->SetMenuBar(this);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void nsMenuBarX::ConstructNativeMenus()
|
||||
{
|
||||
uint32_t count = mContent->GetChildCount();
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
nsIContent *menuContent = mContent->GetChildAt(i);
|
||||
if (menuContent &&
|
||||
menuContent->IsXULElement(nsGkAtoms::menu)) {
|
||||
nsMenuX* newMenu = new nsMenuX();
|
||||
if (newMenu) {
|
||||
nsresult rv = newMenu->Create(this, this, menuContent);
|
||||
if (NS_SUCCEEDED(rv))
|
||||
InsertMenuAtIndex(newMenu, GetMenuCount());
|
||||
else
|
||||
delete newMenu;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void nsMenuBarX::ConstructFallbackNativeMenus()
|
||||
{
|
||||
if (sApplicationMenu) {
|
||||
// Menu has already been built.
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIStringBundle> stringBundle;
|
||||
|
||||
nsCOMPtr<nsIStringBundleService> bundleSvc = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
|
||||
bundleSvc->CreateBundle("chrome://global/locale/fallbackMenubar.properties", getter_AddRefs(stringBundle));
|
||||
|
||||
if (!stringBundle) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsXPIDLString labelUTF16;
|
||||
nsXPIDLString keyUTF16;
|
||||
|
||||
const char16_t* labelProp = u"quitMenuitem.label";
|
||||
const char16_t* keyProp = u"quitMenuitem.key";
|
||||
|
||||
stringBundle->GetStringFromName(labelProp, getter_Copies(labelUTF16));
|
||||
stringBundle->GetStringFromName(keyProp, getter_Copies(keyUTF16));
|
||||
|
||||
NSString* labelStr = [NSString stringWithUTF8String:
|
||||
NS_ConvertUTF16toUTF8(labelUTF16).get()];
|
||||
NSString* keyStr= [NSString stringWithUTF8String:
|
||||
NS_ConvertUTF16toUTF8(keyUTF16).get()];
|
||||
|
||||
if (!nsMenuBarX::sNativeEventTarget) {
|
||||
nsMenuBarX::sNativeEventTarget = [[NativeMenuItemTarget alloc] init];
|
||||
}
|
||||
|
||||
sApplicationMenu = [[[[NSApp mainMenu] itemAtIndex:0] submenu] retain];
|
||||
NSMenuItem* quitMenuItem = [[[NSMenuItem alloc] initWithTitle:labelStr
|
||||
action:@selector(menuItemHit:)
|
||||
keyEquivalent:keyStr] autorelease];
|
||||
[quitMenuItem setTarget:nsMenuBarX::sNativeEventTarget];
|
||||
[quitMenuItem setTag:eCommand_ID_Quit];
|
||||
[sApplicationMenu addItem:quitMenuItem];
|
||||
sApplicationMenuIsFallback = YES;
|
||||
}
|
||||
|
||||
uint32_t nsMenuBarX::GetMenuCount()
|
||||
{
|
||||
return mMenuArray.Length();
|
||||
}
|
||||
|
||||
bool nsMenuBarX::MenuContainsAppMenu()
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
||||
|
||||
return ([mNativeMenu numberOfItems] > 0 &&
|
||||
[[mNativeMenu itemAtIndex:0] submenu] == sApplicationMenu);
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
|
||||
}
|
||||
|
||||
nsresult nsMenuBarX::InsertMenuAtIndex(nsMenuX* aMenu, uint32_t aIndex)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||
|
||||
// If we've only yet created a fallback global Application menu (using
|
||||
// ContructFallbackNativeMenus()), destroy it before recreating it properly.
|
||||
if (sApplicationMenu && sApplicationMenuIsFallback) {
|
||||
ResetNativeApplicationMenu();
|
||||
}
|
||||
// If we haven't created a global Application menu yet, do it.
|
||||
if (!sApplicationMenu) {
|
||||
nsresult rv = NS_OK; // avoid warning about rv being unused
|
||||
rv = CreateApplicationMenu(aMenu);
|
||||
NS_ASSERTION(NS_SUCCEEDED(rv), "Can't create Application menu");
|
||||
|
||||
// Hook the new Application menu up to the menu bar.
|
||||
NSMenu* mainMenu = [NSApp mainMenu];
|
||||
NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
|
||||
[[mainMenu itemAtIndex:0] setSubmenu:sApplicationMenu];
|
||||
}
|
||||
|
||||
// add menu to array that owns our menus
|
||||
mMenuArray.InsertElementAt(aIndex, aMenu);
|
||||
|
||||
// hook up submenus
|
||||
nsIContent* menuContent = aMenu->Content();
|
||||
if (menuContent->GetChildCount() > 0 &&
|
||||
!nsMenuUtilsX::NodeIsHiddenOrCollapsed(menuContent)) {
|
||||
int insertionIndex = nsMenuUtilsX::CalculateNativeInsertionPoint(this, aMenu);
|
||||
if (MenuContainsAppMenu())
|
||||
insertionIndex++;
|
||||
[mNativeMenu insertItem:aMenu->NativeMenuItem() atIndex:insertionIndex];
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||||
}
|
||||
|
||||
void nsMenuBarX::RemoveMenuAtIndex(uint32_t aIndex)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
||||
|
||||
if (mMenuArray.Length() <= aIndex) {
|
||||
NS_ERROR("Attempting submenu removal with bad index!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Our native menu and our internal menu object array might be out of sync.
|
||||
// This happens, for example, when a submenu is hidden. Because of this we
|
||||
// should not assume that a native submenu is hooked up.
|
||||
NSMenuItem* nativeMenuItem = mMenuArray[aIndex]->NativeMenuItem();
|
||||
int nativeMenuItemIndex = [mNativeMenu indexOfItem:nativeMenuItem];
|
||||
if (nativeMenuItemIndex != -1)
|
||||
[mNativeMenu removeItemAtIndex:nativeMenuItemIndex];
|
||||
|
||||
mMenuArray.RemoveElementAt(aIndex);
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK;
|
||||
}
|
||||
|
||||
void nsMenuBarX::ObserveAttributeChanged(nsIDocument* aDocument,
|
||||
nsIContent* aContent,
|
||||
nsIAtom* aAttribute)
|
||||
{
|
||||
}
|
||||
|
||||
void nsMenuBarX::ObserveContentRemoved(nsIDocument* aDocument,
|
||||
nsIContent* aChild,
|
||||
int32_t aIndexInContainer)
|
||||
{
|
||||
RemoveMenuAtIndex(aIndexInContainer);
|
||||
}
|
||||
|
||||
void nsMenuBarX::ObserveContentInserted(nsIDocument* aDocument,
|
||||
nsIContent* aContainer,
|
||||
nsIContent* aChild)
|
||||
{
|
||||
nsMenuX* newMenu = new nsMenuX();
|
||||
if (newMenu) {
|
||||
nsresult rv = newMenu->Create(this, this, aChild);
|
||||
if (NS_SUCCEEDED(rv))
|
||||
InsertMenuAtIndex(newMenu, aContainer->IndexOf(aChild));
|
||||
else
|
||||
delete newMenu;
|
||||
}
|
||||
}
|
||||
|
||||
void nsMenuBarX::ForceUpdateNativeMenuAt(const nsAString& indexString)
|
||||
{
|
||||
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;
|
||||
|
||||
nsMenuX* currentMenu = NULL;
|
||||
int targetIndex = [[indexes objectAtIndex:0] intValue];
|
||||
int visible = 0;
|
||||
uint32_t length = mMenuArray.Length();
|
||||
// first find a menu in the menu bar
|
||||
for (unsigned int i = 0; i < length; i++) {
|
||||
nsMenuX* menu = mMenuArray[i].get();
|
||||
if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(menu->Content())) {
|
||||
visible++;
|
||||
if (visible == (targetIndex + 1)) {
|
||||
currentMenu = menu;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentMenu)
|
||||
return;
|
||||
|
||||
// fake open/close to cause lazy update to happen so submenus populate
|
||||
currentMenu->MenuOpened();
|
||||
currentMenu->MenuClosed();
|
||||
|
||||
// now find the correct submenu
|
||||
for (unsigned int i = 1; currentMenu && i < indexCount; i++) {
|
||||
targetIndex = [[indexes objectAtIndex:i] intValue];
|
||||
visible = 0;
|
||||
length = currentMenu->GetItemCount();
|
||||
for (unsigned int j = 0; j < length; j++) {
|
||||
nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j);
|
||||
if (!targetMenu)
|
||||
return;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calling this forces a full reload of the menu system, reloading all native
|
||||
// menus and their items.
|
||||
// Without this testing is hard because changes to the DOM affect the native
|
||||
// menu system lazily.
|
||||
void nsMenuBarX::ForceNativeMenuReload()
|
||||
{
|
||||
// tear down everything
|
||||
while (GetMenuCount() > 0)
|
||||
RemoveMenuAtIndex(0);
|
||||
|
||||
// construct everything
|
||||
ConstructNativeMenus();
|
||||
}
|
||||
|
||||
nsMenuX* nsMenuBarX::GetMenuAt(uint32_t aIndex)
|
||||
{
|
||||
if (mMenuArray.Length() <= aIndex) {
|
||||
NS_ERROR("Requesting menu at invalid index!");
|
||||
return NULL;
|
||||
}
|
||||
return mMenuArray[aIndex].get();
|
||||
}
|
||||
|
||||
nsMenuX* nsMenuBarX::GetXULHelpMenu()
|
||||
{
|
||||
// The Help menu is usually (always?) the last one, so we start there and
|
||||
// count back.
|
||||
for (int32_t i = GetMenuCount() - 1; i >= 0; --i) {
|
||||
nsMenuX* aMenu = GetMenuAt(i);
|
||||
if (aMenu && nsMenuX::IsXULHelpMenu(aMenu->Content()))
|
||||
return aMenu;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
// On SnowLeopard and later we must tell the OS which is our Help menu.
|
||||
// Otherwise it will only add Spotlight for Help (the Search item) to our
|
||||
// Help menu if its label/title is "Help" -- i.e. if the menu is in English.
|
||||
// This resolves bugs 489196 and 539317.
|
||||
void nsMenuBarX::SetSystemHelpMenu()
|
||||
{
|
||||
nsMenuX* xulHelpMenu = GetXULHelpMenu();
|
||||
if (xulHelpMenu) {
|
||||
NSMenu* helpMenu = (NSMenu*)xulHelpMenu->NativeData();
|
||||
if (helpMenu)
|
||||
[NSApp setHelpMenu:helpMenu];
|
||||
}
|
||||
}
|
||||
|
||||
nsresult nsMenuBarX::Paint()
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||
|
||||
// Don't try to optimize anything in this painting by checking
|
||||
// sLastGeckoMenuBarPainted because the menubar can be manipulated by
|
||||
// native dialogs and sheet code and other things besides this paint method.
|
||||
|
||||
// We have to keep the same menu item for the Application menu so we keep
|
||||
// passing it along.
|
||||
NSMenu* outgoingMenu = [NSApp mainMenu];
|
||||
NS_ASSERTION([outgoingMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
|
||||
|
||||
NSMenuItem* appMenuItem = [[outgoingMenu itemAtIndex:0] retain];
|
||||
[outgoingMenu removeItemAtIndex:0];
|
||||
[mNativeMenu insertItem:appMenuItem atIndex:0];
|
||||
[appMenuItem release];
|
||||
|
||||
// Set menu bar and event target.
|
||||
[NSApp setMainMenu:mNativeMenu];
|
||||
SetSystemHelpMenu();
|
||||
nsMenuBarX::sLastGeckoMenuBarPainted = this;
|
||||
|
||||
gSomeMenuBarPainted = YES;
|
||||
|
||||
return NS_OK;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||||
}
|
||||
|
||||
// Returns the 'key' attribute of the 'shortcutID' object (if any) in the
|
||||
// currently active menubar's DOM document. 'shortcutID' should be the id
|
||||
// (i.e. the name) of a component that defines a commonly used (and
|
||||
// localized) cmd+key shortcut, and belongs to a keyset containing similar
|
||||
// objects. For example "key_selectAll". Returns a value that can be
|
||||
// compared to the first character of [NSEvent charactersIgnoringModifiers]
|
||||
// when [NSEvent modifierFlags] == NSCommandKeyMask.
|
||||
char nsMenuBarX::GetLocalizedAccelKey(const char *shortcutID)
|
||||
{
|
||||
if (!sLastGeckoMenuBarPainted)
|
||||
return 0;
|
||||
|
||||
nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(sLastGeckoMenuBarPainted->mContent->OwnerDoc()));
|
||||
if (!domDoc)
|
||||
return 0;
|
||||
|
||||
NS_ConvertASCIItoUTF16 shortcutIDStr((const char *)shortcutID);
|
||||
nsCOMPtr<nsIDOMElement> shortcutElement;
|
||||
domDoc->GetElementById(shortcutIDStr, getter_AddRefs(shortcutElement));
|
||||
nsCOMPtr<nsIContent> shortcutContent = do_QueryInterface(shortcutElement);
|
||||
if (!shortcutContent)
|
||||
return 0;
|
||||
|
||||
nsAutoString key;
|
||||
shortcutContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key);
|
||||
NS_LossyConvertUTF16toASCII keyASC(key.get());
|
||||
const char *keyASCPtr = keyASC.get();
|
||||
if (!keyASCPtr)
|
||||
return 0;
|
||||
// If keyID's 'key' attribute isn't exactly one character long, it's not
|
||||
// what we're looking for.
|
||||
if (strlen(keyASCPtr) != sizeof(char))
|
||||
return 0;
|
||||
// Make sure retval is lower case.
|
||||
char retval = tolower(keyASCPtr[0]);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* static */
|
||||
void nsMenuBarX::ResetNativeApplicationMenu()
|
||||
{
|
||||
[sApplicationMenu removeAllItems];
|
||||
[sApplicationMenu release];
|
||||
sApplicationMenu = nil;
|
||||
sApplicationMenuIsFallback = NO;
|
||||
}
|
||||
|
||||
// Hide the item in the menu by setting the 'hidden' attribute. Returns it in |outHiddenNode| so
|
||||
// the caller can hang onto it if they so choose. It is acceptable to pass nsull
|
||||
// for |outHiddenNode| if the caller doesn't care about the hidden node.
|
||||
void nsMenuBarX::HideItem(nsIDOMDocument* inDoc, const nsAString & inID, nsIContent** outHiddenNode)
|
||||
{
|
||||
nsCOMPtr<nsIDOMElement> menuItem;
|
||||
inDoc->GetElementById(inID, getter_AddRefs(menuItem));
|
||||
nsCOMPtr<nsIContent> menuContent(do_QueryInterface(menuItem));
|
||||
if (menuContent) {
|
||||
menuContent->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, NS_LITERAL_STRING("true"), false);
|
||||
if (outHiddenNode) {
|
||||
*outHiddenNode = menuContent.get();
|
||||
NS_IF_ADDREF(*outHiddenNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do what is necessary to conform to the Aqua guidelines for menus.
|
||||
void nsMenuBarX::AquifyMenuBar()
|
||||
{
|
||||
nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(mContent->GetComposedDoc()));
|
||||
if (domDoc) {
|
||||
// remove the "About..." item and its separator
|
||||
HideItem(domDoc, NS_LITERAL_STRING("aboutSeparator"), nullptr);
|
||||
HideItem(domDoc, NS_LITERAL_STRING("aboutName"), getter_AddRefs(mAboutItemContent));
|
||||
if (!sAboutItemContent)
|
||||
sAboutItemContent = mAboutItemContent;
|
||||
|
||||
// remove quit item and its separator
|
||||
HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitSeparator"), nullptr);
|
||||
HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitItem"), getter_AddRefs(mQuitItemContent));
|
||||
if (!sQuitItemContent)
|
||||
sQuitItemContent = mQuitItemContent;
|
||||
|
||||
// remove prefs item and its separator, but save off the pref content node
|
||||
// so we can invoke its command later.
|
||||
HideItem(domDoc, NS_LITERAL_STRING("menu_PrefsSeparator"), nullptr);
|
||||
HideItem(domDoc, NS_LITERAL_STRING("menu_preferences"), getter_AddRefs(mPrefItemContent));
|
||||
if (!sPrefItemContent)
|
||||
sPrefItemContent = mPrefItemContent;
|
||||
|
||||
// hide items that we use for the Application menu
|
||||
HideItem(domDoc, NS_LITERAL_STRING("menu_mac_services"), nullptr);
|
||||
HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_app"), nullptr);
|
||||
HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_others"), nullptr);
|
||||
HideItem(domDoc, NS_LITERAL_STRING("menu_mac_show_all"), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// for creating menu items destined for the Application menu
|
||||
NSMenuItem* nsMenuBarX::CreateNativeAppMenuItem(nsMenuX* inMenu, const nsAString& nodeID, SEL action,
|
||||
int tag, NativeMenuItemTarget* target)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
||||
|
||||
nsCOMPtr<nsIDocument> doc = inMenu->Content()->GetUncomposedDoc();
|
||||
if (!doc) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(doc));
|
||||
if (!domdoc) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Get information from the gecko menu item
|
||||
nsAutoString label;
|
||||
nsAutoString modifiers;
|
||||
nsAutoString key;
|
||||
nsCOMPtr<nsIDOMElement> menuItem;
|
||||
domdoc->GetElementById(nodeID, getter_AddRefs(menuItem));
|
||||
if (menuItem) {
|
||||
menuItem->GetAttribute(NS_LITERAL_STRING("label"), label);
|
||||
menuItem->GetAttribute(NS_LITERAL_STRING("modifiers"), modifiers);
|
||||
menuItem->GetAttribute(NS_LITERAL_STRING("key"), key);
|
||||
}
|
||||
else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Get more information about the key equivalent. Start by
|
||||
// finding the key node we need.
|
||||
NSString* keyEquiv = nil;
|
||||
unsigned int macKeyModifiers = 0;
|
||||
if (!key.IsEmpty()) {
|
||||
nsCOMPtr<nsIDOMElement> keyElement;
|
||||
domdoc->GetElementById(key, getter_AddRefs(keyElement));
|
||||
if (keyElement) {
|
||||
nsCOMPtr<nsIContent> keyContent (do_QueryInterface(keyElement));
|
||||
// first grab the key equivalent character
|
||||
nsAutoString keyChar(NS_LITERAL_STRING(" "));
|
||||
keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyChar);
|
||||
if (!keyChar.EqualsLiteral(" ")) {
|
||||
keyEquiv = [[NSString stringWithCharacters:reinterpret_cast<const unichar*>(keyChar.get())
|
||||
length:keyChar.Length()] lowercaseString];
|
||||
}
|
||||
// now grab the key equivalent modifiers
|
||||
nsAutoString modifiersStr;
|
||||
keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr);
|
||||
uint8_t geckoModifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr);
|
||||
macKeyModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(geckoModifiers);
|
||||
}
|
||||
}
|
||||
// get the label into NSString-form
|
||||
NSString* labelString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(label.get())
|
||||
length:label.Length()];
|
||||
|
||||
if (!labelString)
|
||||
labelString = @"";
|
||||
if (!keyEquiv)
|
||||
keyEquiv = @"";
|
||||
|
||||
// put together the actual NSMenuItem
|
||||
NSMenuItem* newMenuItem = [[NSMenuItem alloc] initWithTitle:labelString action:action keyEquivalent:keyEquiv];
|
||||
|
||||
[newMenuItem setTag:tag];
|
||||
[newMenuItem setTarget:target];
|
||||
[newMenuItem setKeyEquivalentModifierMask:macKeyModifiers];
|
||||
|
||||
MenuItemInfo * info = [[MenuItemInfo alloc] initWithMenuGroupOwner:this];
|
||||
[newMenuItem setRepresentedObject:info];
|
||||
[info release];
|
||||
|
||||
return newMenuItem;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
||||
}
|
||||
|
||||
// build the Application menu shared by all menu bars
|
||||
nsresult nsMenuBarX::CreateApplicationMenu(nsMenuX* inMenu)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||
|
||||
// At this point, the application menu is the application menu from
|
||||
// the nib in cocoa widgets. We do not have a way to create an application
|
||||
// menu manually, so we grab the one from the nib and use that.
|
||||
sApplicationMenu = [[[[NSApp mainMenu] itemAtIndex:0] submenu] retain];
|
||||
|
||||
/*
|
||||
We support the following menu items here:
|
||||
|
||||
Menu Item DOM Node ID Notes
|
||||
|
||||
========================
|
||||
= About This App = <- aboutName
|
||||
========================
|
||||
= Preferences... = <- menu_preferences
|
||||
========================
|
||||
= Services > = <- menu_mac_services <- (do not define key equivalent)
|
||||
========================
|
||||
= Hide App = <- menu_mac_hide_app
|
||||
= Hide Others = <- menu_mac_hide_others
|
||||
= Show All = <- menu_mac_show_all
|
||||
========================
|
||||
= Quit = <- menu_FileQuitItem
|
||||
========================
|
||||
|
||||
If any of them are ommitted from the application's DOM, we just don't add
|
||||
them. We always add a "Quit" item, but if an app developer does not provide a
|
||||
DOM node with the right ID for the Quit item, we add it in English. App
|
||||
developers need only add each node with a label and a key equivalent (if they
|
||||
want one). Other attributes are optional. Like so:
|
||||
|
||||
<menuitem id="menu_preferences"
|
||||
label="&preferencesCmdMac.label;"
|
||||
key="open_prefs_key"/>
|
||||
|
||||
We need to use this system for localization purposes, until we have a better way
|
||||
to define the Application menu to be used on Mac OS X.
|
||||
*/
|
||||
|
||||
if (sApplicationMenu) {
|
||||
// This code reads attributes we are going to care about from the DOM elements
|
||||
|
||||
NSMenuItem *itemBeingAdded = nil;
|
||||
BOOL addAboutSeparator = FALSE;
|
||||
|
||||
// Add the About menu item
|
||||
itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("aboutName"), @selector(menuItemHit:),
|
||||
eCommand_ID_About, nsMenuBarX::sNativeEventTarget);
|
||||
if (itemBeingAdded) {
|
||||
[sApplicationMenu addItem:itemBeingAdded];
|
||||
[itemBeingAdded release];
|
||||
itemBeingAdded = nil;
|
||||
|
||||
addAboutSeparator = TRUE;
|
||||
}
|
||||
|
||||
// Add separator if either the About item or software update item exists
|
||||
if (addAboutSeparator)
|
||||
[sApplicationMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// Add the Preferences menu item
|
||||
itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_preferences"), @selector(menuItemHit:),
|
||||
eCommand_ID_Prefs, nsMenuBarX::sNativeEventTarget);
|
||||
if (itemBeingAdded) {
|
||||
[sApplicationMenu addItem:itemBeingAdded];
|
||||
[itemBeingAdded release];
|
||||
itemBeingAdded = nil;
|
||||
|
||||
// Add separator after Preferences menu
|
||||
[sApplicationMenu addItem:[NSMenuItem separatorItem]];
|
||||
}
|
||||
|
||||
// Add Services menu item
|
||||
itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_services"), nil,
|
||||
0, nil);
|
||||
if (itemBeingAdded) {
|
||||
[sApplicationMenu addItem:itemBeingAdded];
|
||||
|
||||
// set this menu item up as the Mac OS X Services menu
|
||||
NSMenu* servicesMenu = [[GeckoServicesNSMenu alloc] initWithTitle:@""];
|
||||
[itemBeingAdded setSubmenu:servicesMenu];
|
||||
[NSApp setServicesMenu:servicesMenu];
|
||||
|
||||
[itemBeingAdded release];
|
||||
itemBeingAdded = nil;
|
||||
|
||||
// Add separator after Services menu
|
||||
[sApplicationMenu addItem:[NSMenuItem separatorItem]];
|
||||
}
|
||||
|
||||
BOOL addHideShowSeparator = FALSE;
|
||||
|
||||
// Add menu item to hide this application
|
||||
itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_app"), @selector(menuItemHit:),
|
||||
eCommand_ID_HideApp, nsMenuBarX::sNativeEventTarget);
|
||||
if (itemBeingAdded) {
|
||||
[sApplicationMenu addItem:itemBeingAdded];
|
||||
[itemBeingAdded release];
|
||||
itemBeingAdded = nil;
|
||||
|
||||
addHideShowSeparator = TRUE;
|
||||
}
|
||||
|
||||
// Add menu item to hide other applications
|
||||
itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_others"), @selector(menuItemHit:),
|
||||
eCommand_ID_HideOthers, nsMenuBarX::sNativeEventTarget);
|
||||
if (itemBeingAdded) {
|
||||
[sApplicationMenu addItem:itemBeingAdded];
|
||||
[itemBeingAdded release];
|
||||
itemBeingAdded = nil;
|
||||
|
||||
addHideShowSeparator = TRUE;
|
||||
}
|
||||
|
||||
// Add menu item to show all applications
|
||||
itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_show_all"), @selector(menuItemHit:),
|
||||
eCommand_ID_ShowAll, nsMenuBarX::sNativeEventTarget);
|
||||
if (itemBeingAdded) {
|
||||
[sApplicationMenu addItem:itemBeingAdded];
|
||||
[itemBeingAdded release];
|
||||
itemBeingAdded = nil;
|
||||
|
||||
addHideShowSeparator = TRUE;
|
||||
}
|
||||
|
||||
// Add a separator after the hide/show menus if at least one exists
|
||||
if (addHideShowSeparator)
|
||||
[sApplicationMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// Add quit menu item
|
||||
itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_FileQuitItem"), @selector(menuItemHit:),
|
||||
eCommand_ID_Quit, nsMenuBarX::sNativeEventTarget);
|
||||
if (itemBeingAdded) {
|
||||
[sApplicationMenu addItem:itemBeingAdded];
|
||||
[itemBeingAdded release];
|
||||
itemBeingAdded = nil;
|
||||
}
|
||||
else {
|
||||
// the current application does not have a DOM node for "Quit". Add one
|
||||
// anyway, in English.
|
||||
NSMenuItem* defaultQuitItem = [[[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(menuItemHit:)
|
||||
keyEquivalent:@"q"] autorelease];
|
||||
[defaultQuitItem setTarget:nsMenuBarX::sNativeEventTarget];
|
||||
[defaultQuitItem setTag:eCommand_ID_Quit];
|
||||
[sApplicationMenu addItem:defaultQuitItem];
|
||||
}
|
||||
}
|
||||
|
||||
return (sApplicationMenu) ? NS_OK : NS_ERROR_FAILURE;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||||
}
|
||||
|
||||
void nsMenuBarX::SetParent(nsIWidget* aParent)
|
||||
{
|
||||
mParentWindow = aParent;
|
||||
}
|
||||
|
||||
//
|
||||
// Objective-C class used to allow us to have keyboard commands
|
||||
// look like they are doing something but actually do nothing.
|
||||
// We allow mouse actions to work normally.
|
||||
//
|
||||
|
||||
// Controls whether or not native menu items should invoke their commands.
|
||||
static BOOL gMenuItemsExecuteCommands = YES;
|
||||
|
||||
@implementation GeckoNSMenu
|
||||
|
||||
// Keyboard commands should not cause menu items to invoke their
|
||||
// commands when there is a key window because we'd rather send
|
||||
// the keyboard command to the window. We still have the menus
|
||||
// go through the mechanics so they'll give the proper visual
|
||||
// feedback.
|
||||
- (BOOL)performKeyEquivalent:(NSEvent *)theEvent
|
||||
{
|
||||
// We've noticed that Mac OS X expects this check in subclasses before
|
||||
// calling NSMenu's "performKeyEquivalent:".
|
||||
//
|
||||
// There is no case in which we'd need to do anything or return YES
|
||||
// when we have no items so we can just do this check first.
|
||||
if ([self numberOfItems] <= 0) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSWindow *keyWindow = [NSApp keyWindow];
|
||||
|
||||
// If there is no key window then just behave normally. This
|
||||
// probably means that this menu is associated with Gecko's
|
||||
// hidden window.
|
||||
if (!keyWindow) {
|
||||
return [super performKeyEquivalent:theEvent];
|
||||
}
|
||||
|
||||
NSResponder *firstResponder = [keyWindow firstResponder];
|
||||
|
||||
gMenuItemsExecuteCommands = NO;
|
||||
[super performKeyEquivalent:theEvent];
|
||||
gMenuItemsExecuteCommands = YES; // return to default
|
||||
|
||||
// Return YES if we invoked a command and there is now no key window or we changed
|
||||
// the first responder. In this case we do not want to propagate the event because
|
||||
// we don't want it handled again.
|
||||
if (![NSApp keyWindow] || [[NSApp keyWindow] firstResponder] != firstResponder) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Return NO so that we can handle the event via NSView's "keyDown:".
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
// Objective-C class used as action target for menu items
|
||||
//
|
||||
|
||||
@implementation NativeMenuItemTarget
|
||||
|
||||
// called when some menu item in this menu gets hit
|
||||
-(IBAction)menuItemHit:(id)sender
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
||||
|
||||
if (!gMenuItemsExecuteCommands) {
|
||||
return;
|
||||
}
|
||||
|
||||
int tag = [sender tag];
|
||||
|
||||
nsMenuGroupOwnerX* menuGroupOwner = nullptr;
|
||||
nsMenuBarX* menuBar = nullptr;
|
||||
MenuItemInfo* info = [sender representedObject];
|
||||
|
||||
if (info) {
|
||||
menuGroupOwner = [info menuGroupOwner];
|
||||
if (!menuGroupOwner) {
|
||||
return;
|
||||
}
|
||||
if (menuGroupOwner->MenuObjectType() == eMenuBarObjectType) {
|
||||
menuBar = static_cast<nsMenuBarX*>(menuGroupOwner);
|
||||
}
|
||||
}
|
||||
|
||||
// Do special processing if this is for an app-global command.
|
||||
if (tag == eCommand_ID_About) {
|
||||
nsIContent* mostSpecificContent = sAboutItemContent;
|
||||
if (menuBar && menuBar->mAboutItemContent)
|
||||
mostSpecificContent = menuBar->mAboutItemContent;
|
||||
nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
|
||||
return;
|
||||
}
|
||||
else if (tag == eCommand_ID_Prefs) {
|
||||
nsIContent* mostSpecificContent = sPrefItemContent;
|
||||
if (menuBar && menuBar->mPrefItemContent)
|
||||
mostSpecificContent = menuBar->mPrefItemContent;
|
||||
nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
|
||||
return;
|
||||
}
|
||||
else if (tag == eCommand_ID_HideApp) {
|
||||
[NSApp hide:sender];
|
||||
return;
|
||||
}
|
||||
else if (tag == eCommand_ID_HideOthers) {
|
||||
[NSApp hideOtherApplications:sender];
|
||||
return;
|
||||
}
|
||||
else if (tag == eCommand_ID_ShowAll) {
|
||||
[NSApp unhideAllApplications:sender];
|
||||
return;
|
||||
}
|
||||
else if (tag == eCommand_ID_Quit) {
|
||||
nsIContent* mostSpecificContent = sQuitItemContent;
|
||||
if (menuBar && menuBar->mQuitItemContent)
|
||||
mostSpecificContent = menuBar->mQuitItemContent;
|
||||
// If we have some content for quit we execute it. Otherwise we send a native app terminate
|
||||
// message. If you want to stop a quit from happening, provide quit content and return
|
||||
// the event as unhandled.
|
||||
if (mostSpecificContent) {
|
||||
nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
|
||||
}
|
||||
else {
|
||||
nsCOMPtr<nsIAppStartup> appStartup = do_GetService(NS_APPSTARTUP_CONTRACTID);
|
||||
if (appStartup) {
|
||||
appStartup->Quit(nsIAppStartup::eAttemptQuit);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// given the commandID, look it up in our hashtable and dispatch to
|
||||
// that menu item.
|
||||
if (menuGroupOwner) {
|
||||
nsMenuItemX* menuItem = menuGroupOwner->GetMenuItemForCommandID(static_cast<uint32_t>(tag));
|
||||
if (menuItem)
|
||||
menuItem->DoCommand();
|
||||
}
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Objective-C class used for menu items on the Services menu to allow Gecko
|
||||
// to override their standard behavior in order to stop key equivalents from
|
||||
// firing in certain instances. When gMenuItemsExecuteCommands is NO, we return
|
||||
// a dummy target and action instead of the actual target and action.
|
||||
|
||||
@implementation GeckoServicesNSMenuItem
|
||||
|
||||
- (id) target
|
||||
{
|
||||
id realTarget = [super target];
|
||||
if (gMenuItemsExecuteCommands)
|
||||
return realTarget;
|
||||
else
|
||||
return realTarget ? self : nil;
|
||||
}
|
||||
|
||||
- (SEL) action
|
||||
{
|
||||
SEL realAction = [super action];
|
||||
if (gMenuItemsExecuteCommands)
|
||||
return realAction;
|
||||
else
|
||||
return realAction ? @selector(_doNothing:) : NULL;
|
||||
}
|
||||
|
||||
- (void) _doNothing:(id)sender
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Objective-C class used as the Services menu so that Gecko can override the
|
||||
// standard behavior of the Services menu in order to stop key equivalents
|
||||
// from firing in certain instances.
|
||||
|
||||
@implementation GeckoServicesNSMenu
|
||||
|
||||
- (void)addItem:(NSMenuItem *)newItem
|
||||
{
|
||||
[self _overrideClassOfMenuItem:newItem];
|
||||
[super addItem:newItem];
|
||||
}
|
||||
|
||||
- (NSMenuItem *)addItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv
|
||||
{
|
||||
NSMenuItem * newItem = [super addItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv];
|
||||
[self _overrideClassOfMenuItem:newItem];
|
||||
return newItem;
|
||||
}
|
||||
|
||||
- (void)insertItem:(NSMenuItem *)newItem atIndex:(NSInteger)index
|
||||
{
|
||||
[self _overrideClassOfMenuItem:newItem];
|
||||
[super insertItem:newItem atIndex:index];
|
||||
}
|
||||
|
||||
- (NSMenuItem *)insertItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv atIndex:(NSInteger)index
|
||||
{
|
||||
NSMenuItem * newItem = [super insertItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv atIndex:index];
|
||||
[self _overrideClassOfMenuItem:newItem];
|
||||
return newItem;
|
||||
}
|
||||
|
||||
- (void) _overrideClassOfMenuItem:(NSMenuItem *)menuItem
|
||||
{
|
||||
if ([menuItem class] == [NSMenuItem class])
|
||||
object_setClass(menuItem, [GeckoServicesNSMenuItem class]);
|
||||
}
|
||||
|
||||
@end
|
||||
Loading…
Add table
Add a link
Reference in a new issue