Cocoa menu work. This code is only built when Cocoa widgets are built (only Camino at the moment, and Camino doesn't actually use the code). This is most of the Application menu implementation. It lacks support for key equivs in the Application menu, that will be in the next patch. b=316076 sr=sparky

This commit is contained in:
joshmoz%gmail.com 2006-01-20 05:01:20 +00:00
parent f15416364c
commit abb4fbbd9a
5 changed files with 272 additions and 122 deletions

View File

@ -57,6 +57,8 @@ class nsIWidget;
class nsIDocument;
class nsIDOMNode;
extern "C" MenuRef _NSGetCarbonMenu(NSMenu* aMenu);
namespace MenuHelpersX
{
// utility routine for getting a PresContext out of a docShell
@ -64,9 +66,18 @@ namespace MenuHelpersX
nsEventStatus DispatchCommandTo(nsIWeakReference* aDocShellWeakRef,
nsIContent* aTargetContent);
NSString* CreateTruncatedCocoaLabel(nsString itemLabel);
PRUint8 GeckoModifiersForNodeAttribute(char* modifiersAttribute);
unsigned int MacModifiersForGeckoModifiers(PRUint8 geckoModifiers);
}
// Objective-C class used as action target for menu items
@interface NativeMenuItemTarget : NSObject
{
}
-(IBAction)menuItemHit:(id)sender;
@end
//
// Native Mac menu bar wrapper
//
@ -84,6 +95,9 @@ public:
enum {kApplicationMenuID = 1};
// |NSMenuItem|s target Objective-C objects
static NativeMenuItemTarget* sNativeEventTarget;
NS_DECL_ISUPPORTS
NS_DECL_NSICHANGEMANAGER
NS_DECL_NSIMENUCOMMANDDISPATCHER
@ -132,6 +146,8 @@ protected:
nsEventStatus ExecuteCommand(nsIContent* inDispatchTo);
// build the Application menu shared by all menu bars.
NSMenuItem* nsMenuBarX::CreateNativeAppMenuItem(nsIMenu* inMenu, const nsAString& nodeID, SEL action,
int tag, NativeMenuItemTarget* target);
nsresult CreateApplicationMenu(nsIMenu* inMenu);
nsHashtable mObserverTable; // stores observers for content change notification

View File

@ -73,6 +73,7 @@ NS_IMPL_ISUPPORTS6(nsMenuBarX, nsIMenuBar, nsIMenuListener, nsIDocumentObserver,
NSMenu* nsMenuBarX::sApplicationMenu = nsnull;
EventHandlerUPP nsMenuBarX::sCommandEventHandler = nsnull;
NativeMenuItemTarget* nsMenuBarX::sNativeEventTarget = nil;
nsMenuBarX::nsMenuBarX()
@ -530,6 +531,52 @@ NS_IMETHODIMP nsMenuBarX::AddMenu(nsIMenu * aMenu)
}
// for creating menu items destined for the Application menu
NSMenuItem* nsMenuBarX::CreateNativeAppMenuItem(nsIMenu* inMenu, const nsAString& nodeID, SEL action,
int tag, NativeMenuItemTarget* target)
{
nsCOMPtr<nsIContent> menu;
inMenu->GetMenuContent(getter_AddRefs(menu));
if (!menu)
return nil;
nsCOMPtr<nsIDocument> doc = menu->GetDocument();
if (!doc)
return nil;
nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(doc));
if (!domdoc)
return nil;
// Get menu item label and access key
nsAutoString label;
nsAutoString modifiers;
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);
}
else {
return nil;
}
NSString* labelString = (NSString*)::CFStringCreateWithCharacters(kCFAllocatorDefault, (UniChar*)label.get(),
label.Length());
if (!labelString)
labelString = [@"" retain];
NSMenuItem* newMenuItem = [[NSMenuItem alloc] initWithTitle:labelString action:action keyEquivalent:@""];
[labelString release];
[newMenuItem setTag:tag];
[newMenuItem setTarget:target];
return newMenuItem;
}
//
// CreateApplicationMenu
//
@ -543,35 +590,126 @@ nsMenuBarX::CreateApplicationMenu(nsIMenu* inMenu)
// 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 access key, has submenu)
==================
= 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 an accesskey (if they want
one). Other attributes are optional. Like so:
<menuitem id="menu_preferences"
label="&preferencesCmdMac.label;"
accesskey="&preferencesCmdMac.accesskey;"/>
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 the "label" attribute from the <menuitem/> with
// id="aboutName" and puts its label in the Application Menu
nsAutoString label;
nsCOMPtr<nsIContent> menu;
inMenu->GetMenuContent(getter_AddRefs(menu));
if (menu) {
nsCOMPtr<nsIDocument> doc = menu->GetDocument();
if (doc) {
nsCOMPtr<nsIDOMDocument> domdoc (do_QueryInterface(doc));
if (domdoc) {
nsCOMPtr<nsIDOMElement> aboutMenuItem;
domdoc->GetElementById(NS_LITERAL_STRING("aboutName"), getter_AddRefs(aboutMenuItem));
if (aboutMenuItem)
aboutMenuItem->GetAttribute(NS_LITERAL_STRING("label"), label);
}
}
}
CFStringRef labelRef = ::CFStringCreateWithCharacters(kCFAllocatorDefault, (UniChar*)label.get(), label.Length());
if (labelRef) {
[sApplicationMenu insertItemWithTitle:(NSString*)labelRef action:nil keyEquivalent:@"" atIndex:0];
::CFRelease(labelRef);
// This code reads attributes we are going to care about from the DOM elements
NSMenuItem *itemBeingAdded = nil;
// Add the About menu item
itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("aboutName"), @selector(menuItemHit:),
kHICommandAbout, nsMenuBarX::sNativeEventTarget);
if (itemBeingAdded) {
[sApplicationMenu addItem:itemBeingAdded];
[itemBeingAdded release];
// Add separator after About menu
[sApplicationMenu addItem:[NSMenuItem separatorItem]];
}
//XXXJOSH we need to get this event, maybe convert to a MenuRef, do later
//::SetMenuItemCommandID(sApplicationMenu, 1, kHICommandAbout);
// Add the Preferences menu item
itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_preferences"), @selector(menuItemHit:),
kHICommandPreferences, nsMenuBarX::sNativeEventTarget);
if (itemBeingAdded) {
[sApplicationMenu addItem:itemBeingAdded];
[itemBeingAdded release];
// 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 = [[NSMenu alloc] initWithTitle:@""];
[itemBeingAdded setSubmenu:servicesMenu];
[NSApp setServicesMenu:servicesMenu];
[itemBeingAdded release];
// Add separator after Services menu
[sApplicationMenu addItem:[NSMenuItem separatorItem]];
}
// add separator after About 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(hide:),
0, NSApp);
if (itemBeingAdded) {
[sApplicationMenu addItem:itemBeingAdded];
[itemBeingAdded release];
addHideShowSeparator = TRUE;
}
// Add menu item to hide other applications
itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_others"), @selector(hideOtherApplications:),
0, NSApp);
if (itemBeingAdded) {
[sApplicationMenu addItem:itemBeingAdded];
[itemBeingAdded release];
addHideShowSeparator = TRUE;
}
// Add menu item to show all applications
itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_show_all"), @selector(unhideAllApplications:),
0, NSApp);
if (itemBeingAdded) {
[sApplicationMenu addItem:itemBeingAdded];
[itemBeingAdded release];
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:),
kHICommandQuit, nsMenuBarX::sNativeEventTarget);
if (itemBeingAdded) {
[sApplicationMenu addItem:itemBeingAdded];
[itemBeingAdded release];
}
else {
// the current application does not have a DOM node for "Quit". Add one
// anyway, in English.
;
}
}
return (sApplicationMenu) ? NS_OK : NS_ERROR_FAILURE;
@ -762,7 +900,6 @@ nsMenuBarX::ContentInserted(nsIDocument * aDocument, nsIContent * aContainer,
}
}
#pragma mark -
//
// nsIChangeManager
@ -931,3 +1068,80 @@ NSString* MenuHelpersX::CreateTruncatedCocoaLabel(nsString itemLabel)
::TruncateThemeText(labelRef, kThemeMenuItemFont, kThemeStateActive, kMaxItemPixelWidth, truncMiddle, NULL);
return (NSString*)labelRef; // caller releases
}
PRUint8 MenuHelpersX::GeckoModifiersForNodeAttribute(char* modifiersAttribute)
{
PRUint8 modifiers = knsMenuItemNoModifier;
char* newStr;
char* token = nsCRT::strtok(modifiersAttribute, ", \t", &newStr);
while (token != NULL) {
if (PL_strcmp(token, "shift") == 0)
modifiers |= knsMenuItemShiftModifier;
else if (PL_strcmp(token, "alt") == 0)
modifiers |= knsMenuItemAltModifier;
else if (PL_strcmp(token, "control") == 0)
modifiers |= knsMenuItemControlModifier;
else if ((PL_strcmp(token, "accel") == 0) ||
(PL_strcmp(token, "meta") == 0)) {
modifiers |= knsMenuItemCommandModifier;
}
token = nsCRT::strtok(newStr, ", \t", &newStr);
}
return modifiers;
}
unsigned int MenuHelpersX::MacModifiersForGeckoModifiers(PRUint8 geckoModifiers)
{
unsigned int macModifiers = 0;
if (geckoModifiers & knsMenuItemShiftModifier)
macModifiers |= NSShiftKeyMask;
if (geckoModifiers & knsMenuItemAltModifier)
macModifiers |= NSAlternateKeyMask;
if (geckoModifiers & knsMenuItemControlModifier)
macModifiers |= NSControlKeyMask;
if (geckoModifiers & knsMenuItemCommandModifier)
macModifiers |= NSCommandKeyMask;
return macModifiers;
}
// 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
{
MenuRef senderMenuRef = _NSGetCarbonMenu([sender menu]);
int senderCarbonMenuItemIndex = [[sender menu] indexOfItem:sender] + 1;
// If this carbon menu item has never had a command ID assigned to it, give it
// one from the sender's tag
MenuCommand menuCommand;
::GetMenuItemCommandID(senderMenuRef, senderCarbonMenuItemIndex, &menuCommand);
if (menuCommand == 0) {
menuCommand = (MenuCommand)[sender tag];
::SetMenuItemCommandID(senderMenuRef, senderCarbonMenuItemIndex, menuCommand);
}
// set up an HICommand to send
HICommand menuHICommand;
menuHICommand.commandID = menuCommand;
menuHICommand.menu.menuRef = senderMenuRef;
menuHICommand.menu.menuItemIndex = senderCarbonMenuItemIndex;
// send Carbon Event
EventRef newEvent;
OSErr err = ::CreateEvent(NULL, kEventClassCommand, kEventCommandProcess, 0, kEventAttributeUserEvent, &newEvent);
if (err == noErr) {
err = ::SetEventParameter(newEvent, kEventParamDirectObject, typeHICommand, sizeof(HICommand), &menuHICommand);
if (err == noErr) {
err = ::SendEventToEventTarget(newEvent, GetWindowEventTarget((WindowRef)[[NSApp keyWindow] windowRef]));
NS_ASSERTION(err == noErr, "Carbon event for menu hit not sent!");
}
}
}
@end

View File

@ -297,15 +297,7 @@ NS_METHOD nsMenuItemX::SetModifiers(PRUint8 aModifiers)
mModifiers = aModifiers;
// set up shortcut key modifiers on native menu item
unsigned int macModifiers = 0;
if (mModifiers & knsMenuItemShiftModifier)
macModifiers |= NSShiftKeyMask;
if (mModifiers & knsMenuItemAltModifier)
macModifiers |= NSAlternateKeyMask;
if (mModifiers & knsMenuItemControlModifier)
macModifiers |= NSControlKeyMask;
if (mModifiers & knsMenuItemCommandModifier)
macModifiers |= NSCommandKeyMask;
unsigned int macModifiers = MenuHelpersX::MacModifiersForGeckoModifiers(mModifiers);
[mNativeMenuItem setKeyEquivalentModifierMask:macModifiers];
return NS_OK;

View File

@ -45,6 +45,7 @@
#include "nsIMenuListener.h"
#include "nsIChangeManager.h"
#include "nsWeakReference.h"
#include "nsMenuBarX.h"
#import <Carbon/Carbon.h>
#import <Cocoa/Cocoa.h>
@ -55,7 +56,8 @@ class nsIMenuListener;
class nsMenuX;
// This class simply receives events from menus as a proxy for a gecko menu
// MenuDelegate is used to receive Cocoa notifications for
// setting up carbon events
@interface MenuDelegate : NSObject
{
nsMenuX* mGeckoMenu; // weak ref

View File

@ -47,7 +47,6 @@
#include "prinrval.h"
#include "nsMenuX.h"
#include "nsMenuBarX.h"
#include "nsIMenu.h"
#include "nsIMenuBar.h"
#include "nsIMenuItem.h"
@ -71,8 +70,6 @@
static PRBool gConstructingMenu = PR_FALSE;
static MenuRef GetCarbonMenuRef(NSMenu* aMenu);
// CIDs
#include "nsWidgetsCID.h"
static NS_DEFINE_CID(kMenuCID, NS_MENU_CID);
@ -103,7 +100,10 @@ nsMenuX::nsMenuX()
mDestroyHandlerCalled(PR_FALSE), mNeedsRebuild(PR_TRUE),
mConstructed(PR_FALSE), mVisible(PR_TRUE), mHandler(nsnull)
{
mMenuDelegate = [[MenuDelegate alloc] initWithGeckoMenu:this];
mMenuDelegate = [[MenuDelegate alloc] initWithGeckoMenu:this];
if (!nsMenuBarX::sNativeEventTarget)
nsMenuBarX::sNativeEventTarget = [[NativeMenuItemTarget alloc] init];
}
@ -229,7 +229,7 @@ NS_IMETHODIMP nsMenuX::AddMenuItem(nsIMenuItem * aMenuItem)
[mMacMenu addItem:newNativeMenuItem];
// set up target/action
[newNativeMenuItem setTarget:mMenuDelegate];
[newNativeMenuItem setTarget:nsMenuBarX::sNativeEventTarget];
[newNativeMenuItem setAction:@selector(menuItemHit:)];
// set its command. we get the unique command id from the menubar
@ -386,7 +386,7 @@ nsEventStatus nsMenuX::MenuSelected(const nsMenuEvent & aMenuEvent)
// at this point, the carbon event handler was installed so there
// must be a carbon MenuRef to be had
if (GetCarbonMenuRef(mMacMenu) == selectedMenuHandle) {
if (_NSGetCarbonMenu(mMacMenu) == selectedMenuHandle) {
if (mIsHelpMenu && mConstructed) {
RemoveAll();
mConstructed = false;
@ -637,16 +637,14 @@ void nsMenuX::LoadMenuItem(nsIMenu* inParentMenu, nsIContent* inMenuItemContent)
nsAutoString checked;
nsAutoString type;
nsAutoString menuitemName;
nsAutoString menuitemCmd;
inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, disabled);
inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::checked, checked);
inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::type, type);
inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::label, menuitemName);
inMenuItemContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::command, menuitemCmd);
// printf("menuitem %s \n", NS_LossyConvertUCS2toASCII(menuitemName).get());
PRBool enabled = !(disabled.EqualsLiteral("true"));
nsIMenuItem::EMenuItemType itemType = nsIMenuItem::eRegular;
@ -683,29 +681,13 @@ void nsMenuX::LoadMenuItem(nsIMenu* inParentMenu, nsIContent* inMenuItemContent)
keyContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::key, keyChar);
if (!keyChar.EqualsLiteral(" "))
pnsMenuItem->SetShortcutChar(keyChar);
PRUint8 modifiers = knsMenuItemNoModifier;
nsAutoString modifiersStr;
keyContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::modifiers, modifiersStr);
char* str = ToNewCString(modifiersStr);
char* newStr;
char* token = nsCRT::strtok(str, ", \t", &newStr);
while (token != NULL) {
if (PL_strcmp(token, "shift") == 0)
modifiers |= knsMenuItemShiftModifier;
else if (PL_strcmp(token, "alt") == 0)
modifiers |= knsMenuItemAltModifier;
else if (PL_strcmp(token, "control") == 0)
modifiers |= knsMenuItemControlModifier;
else if ((PL_strcmp(token, "accel") == 0) ||
(PL_strcmp(token, "meta") == 0)) {
modifiers |= knsMenuItemCommandModifier;
}
token = nsCRT::strtok(newStr, ", \t", &newStr);
}
PRUint8 modifiers = MenuHelpersX::GeckoModifiersForNodeAttribute(str);
nsMemory::Free(str);
pnsMenuItem->SetModifiers(modifiers);
}
@ -1230,31 +1212,8 @@ static OSStatus InstallMyMenuEventHandler(MenuRef menuRef, void* userData, Event
return status;
}
// #include <mach-o/dyld.h>
extern "C" MenuRef _NSGetCarbonMenu(NSMenu* aMenu);
static MenuRef GetCarbonMenuRef(NSMenu* aMenu)
{
// we use a private API, so check for its existence and cache it (no static linking)
//XXXJOSH I don't feel like debugging this now, so I'll leave it for later
/*
typedef MenuRef (*NSGetCarbonMenuPtr)(NSMenu* menu);
static NSGetCarbonMenuPtr NSGetCarbonMenu = NULL;
if (!NSGetCarbonMenu && NSIsSymbolNameDefined("_NSGetCarbonMenu"))
NSGetCarbonMenu = (NSGetCarbonMenuPtr)NSAddressOfSymbol(NSLookupAndBindSymbol("_NSGetCarbonMenu"));
if (NSGetCarbonMenu)
return NSGetCarbonMenu(aMenu);
else
return NULL;
*/
return _NSGetCarbonMenu(aMenu);
}
//
// MenuDelegate Cocoa class, receives events for the gecko menus
//
// MenuDelegate Objective-C class, used to set up Carbon events
@implementation MenuDelegate
@ -1278,7 +1237,7 @@ static MenuRef GetCarbonMenuRef(NSMenu* aMenu)
- (void)menuNeedsUpdate:(NSMenu*)aMenu
{
if (!mHaveInstalledCarbonEvents) {
MenuRef myMenuRef = GetCarbonMenuRef(aMenu);
MenuRef myMenuRef = _NSGetCarbonMenu(aMenu);
if (myMenuRef) {
InstallMyMenuEventHandler(myMenuRef, mGeckoMenu, nil); // can't be nil because we need to clean up
mHaveInstalledCarbonEvents = TRUE;
@ -1286,37 +1245,4 @@ static MenuRef GetCarbonMenuRef(NSMenu* aMenu)
}
}
// called when some menu item in this menu gets hit
- (IBAction)menuItemHit:(id)sender {
MenuRef senderMenuRef = GetCarbonMenuRef([sender menu]);
int senderCarbonMenuItemIndex = [[sender menu] indexOfItem:sender] + 1;
// If this carbon menu item has never had a command ID assigned to it, give it
// one from the sender's tag
MenuCommand menuCommand;
::GetMenuItemCommandID(senderMenuRef, senderCarbonMenuItemIndex, &menuCommand);
if (menuCommand == 0) {
menuCommand = (MenuCommand)[sender tag];
::SetMenuItemCommandID(senderMenuRef, senderCarbonMenuItemIndex, menuCommand);
}
// set up an HICommand to send
HICommand menuHICommand;
menuHICommand.commandID = menuCommand;
menuHICommand.menu.menuRef = senderMenuRef;
menuHICommand.menu.menuItemIndex = senderCarbonMenuItemIndex;
// send Carbon Event
EventRef newEvent;
OSErr err = ::CreateEvent(NULL, kEventClassCommand, kEventCommandProcess, 0, kEventAttributeUserEvent, &newEvent);
if (err == noErr) {
err = ::SetEventParameter(newEvent, kEventParamDirectObject, typeHICommand, sizeof(HICommand), &menuHICommand);
if (err == noErr) {
err = ::SendEventToEventTarget(newEvent, GetWindowEventTarget((WindowRef)[[NSApp keyWindow] windowRef]));
NS_ASSERTION(err == noErr, "Carbon event for menu hit not sent!");
}
}
}
@end