2015-04-28 13:17:28 -05:00
|
|
|
// 28 april 2015
|
|
|
|
#import "uipriv_darwin.h"
|
|
|
|
|
2015-04-28 19:34:57 -05:00
|
|
|
// general TODO: release Objective-C objects in dealloc since we can't use ARC
|
|
|
|
|
2015-04-30 22:24:52 -05:00
|
|
|
// TODO menu finalization
|
|
|
|
|
2015-04-28 19:34:57 -05:00
|
|
|
struct menu {
|
|
|
|
uiMenu m;
|
|
|
|
NSMenu *menu;
|
|
|
|
NSMenuItem *item;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct menuItem {
|
|
|
|
uiMenuItem mi;
|
|
|
|
NSMenuItem *item;
|
|
|
|
int type;
|
|
|
|
void (*onClicked)(uiMenuItem *, uiWindow *, void *);
|
|
|
|
void *onClickedData;
|
|
|
|
};
|
|
|
|
|
|
|
|
enum {
|
|
|
|
typeRegular,
|
|
|
|
typeCheckbox,
|
|
|
|
typeQuit,
|
|
|
|
typePreferences,
|
|
|
|
typeAbout,
|
|
|
|
typeSeparator,
|
|
|
|
};
|
|
|
|
|
2015-04-28 13:17:28 -05:00
|
|
|
@implementation menuManager
|
|
|
|
|
|
|
|
- (id)init
|
|
|
|
{
|
|
|
|
self = [super init];
|
2015-04-30 22:24:52 -05:00
|
|
|
if (self) {
|
2015-04-28 19:34:57 -05:00
|
|
|
self->items = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsOpaqueMemory
|
|
|
|
valueOptions:NSPointerFunctionsOpaqueMemory];
|
2015-04-30 22:24:52 -05:00
|
|
|
self->hasQuit = NO;
|
|
|
|
self->hasPreferences = NO;
|
|
|
|
self->hasAbout = NO;
|
|
|
|
}
|
2015-04-28 13:17:28 -05:00
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
[self->items release];
|
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction)onClicked:(id)sender
|
|
|
|
{
|
2015-04-28 19:34:57 -05:00
|
|
|
struct menuItem *item;
|
|
|
|
NSValue *v;
|
|
|
|
|
|
|
|
v = (NSValue *) [self->items objectForKey:sender];
|
|
|
|
item = (struct menuItem *) [v pointerValue];
|
|
|
|
if (item->type == typeCheckbox)
|
|
|
|
uiMenuItemSetChecked(uiMenuItem(item), !uiMenuItemChecked(uiMenuItem(item)));
|
|
|
|
// TODO get key window
|
|
|
|
(*(item->onClicked))(uiMenuItem(item), NULL, item->onClickedData);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)register:(NSMenuItem *)item to:(struct menuItem *)smi
|
|
|
|
{
|
|
|
|
NSValue *v;
|
|
|
|
|
2015-04-30 22:24:52 -05:00
|
|
|
switch (smi->type) {
|
|
|
|
case typeQuit:
|
|
|
|
if (self->hasQuit)
|
|
|
|
complain("attempt to add multiple Quit menu items");
|
|
|
|
self->hasQuit = YES;
|
|
|
|
break;
|
|
|
|
case typePreferences:
|
|
|
|
if (self->hasPreferences)
|
|
|
|
complain("attempt to add multiple Preferences menu items");
|
|
|
|
self->hasPreferences = YES;
|
|
|
|
break;
|
|
|
|
case typeAbout:
|
|
|
|
if (self->hasAbout)
|
|
|
|
complain("attempt to add multiple About menu items");
|
|
|
|
self->hasAbout = YES;
|
|
|
|
break;
|
|
|
|
}
|
2015-04-28 19:34:57 -05:00
|
|
|
v = [NSValue valueWithPointer:smi];
|
|
|
|
[self->items setObject:v forKey:item];
|
2015-04-28 13:17:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Cocoa constructs the default application menu by hand for each program; that's what MainMenu.[nx]ib does
|
|
|
|
// TODO investigate setAppleMenu:
|
|
|
|
- (void)buildApplicationMenu:(NSMenu *)menubar
|
|
|
|
{
|
|
|
|
NSString *appName;
|
|
|
|
NSMenuItem *appMenuItem;
|
|
|
|
NSMenu *appMenu;
|
|
|
|
NSMenuItem *item;
|
|
|
|
NSString *title;
|
2015-05-02 10:11:13 -05:00
|
|
|
NSMenu *servicesMenu;
|
2015-04-28 13:17:28 -05:00
|
|
|
|
|
|
|
appName = [[NSProcessInfo processInfo] processName];
|
|
|
|
appMenuItem = [[NSMenuItem alloc] initWithTitle:appName action:NULL keyEquivalent:@""];
|
|
|
|
appMenu = [[NSMenu alloc] initWithTitle:appName];
|
2015-04-30 14:08:03 -05:00
|
|
|
// TODO this is not safe for the items we don't care about
|
2015-04-28 19:34:57 -05:00
|
|
|
[appMenu setAutoenablesItems:NO];
|
2015-04-28 13:17:28 -05:00
|
|
|
[appMenuItem setSubmenu:appMenu];
|
|
|
|
[menubar addItem:appMenuItem];
|
|
|
|
|
|
|
|
// first is About
|
|
|
|
title = [@"About " stringByAppendingString:appName];
|
|
|
|
item = [[NSMenuItem alloc] initWithTitle:title action:@selector(onClicked:) keyEquivalent:@""];
|
|
|
|
[item setTarget:self];
|
|
|
|
[item setEnabled:NO];
|
|
|
|
[appMenu addItem:item];
|
|
|
|
self.aboutItem = item;
|
|
|
|
|
|
|
|
[appMenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
|
|
|
|
// next is Preferences
|
|
|
|
item = [[NSMenuItem alloc] initWithTitle:@"Preferences…" action:@selector(onClicked:) keyEquivalent:@","];
|
|
|
|
[item setTarget:self];
|
|
|
|
[item setEnabled:NO];
|
|
|
|
[appMenu addItem:item];
|
|
|
|
self.preferencesItem = item;
|
|
|
|
|
|
|
|
[appMenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
|
|
|
|
// next is Services
|
|
|
|
item = [[NSMenuItem alloc] initWithTitle:@"Services" action:NULL keyEquivalent:@""];
|
2015-05-02 10:11:13 -05:00
|
|
|
servicesMenu = [[NSMenu alloc] initWithTitle:@"Services"];
|
|
|
|
[item setSubmenu:servicesMenu];
|
|
|
|
[NSApp setServicesMenu:servicesMenu];
|
|
|
|
[appMenu addItem:item];
|
2015-04-28 13:17:28 -05:00
|
|
|
|
|
|
|
[appMenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
|
|
|
|
// next are the three hiding options
|
|
|
|
title = [@"Hide " stringByAppendingString:appName];
|
|
|
|
item = [[NSMenuItem alloc] initWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
|
2015-04-30 17:02:21 -05:00
|
|
|
// the .xib file says they go to -1 ("First Responder", which sounds wrong...)
|
|
|
|
// to do that, we simply leave the target as nil
|
2015-04-28 13:17:28 -05:00
|
|
|
[appMenu addItem:item];
|
|
|
|
item = [[NSMenuItem alloc] initWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
|
|
|
|
[item setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)];
|
|
|
|
[appMenu addItem:item];
|
|
|
|
item = [[NSMenuItem alloc] initWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
|
|
|
|
[appMenu addItem:item];
|
|
|
|
|
|
|
|
[appMenu addItem:[NSMenuItem separatorItem]];
|
|
|
|
|
|
|
|
// and finally Quit
|
2015-04-30 21:46:24 -05:00
|
|
|
// DON'T use @selector(terminate:) as the action; we handle termination ourselves
|
2015-04-28 13:17:28 -05:00
|
|
|
title = [@"Quit " stringByAppendingString:appName];
|
|
|
|
item = [[NSMenuItem alloc] initWithTitle:title action:@selector(onClicked:) keyEquivalent:@"q"];
|
|
|
|
[item setTarget:self];
|
2015-04-30 14:08:03 -05:00
|
|
|
[item setEnabled:NO];
|
2015-04-28 13:17:28 -05:00
|
|
|
[appMenu addItem:item];
|
|
|
|
self.quitItem = item;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSMenu *)makeMenubar
|
|
|
|
{
|
|
|
|
NSMenu *menubar;
|
|
|
|
|
|
|
|
menubar = [[NSMenu alloc] initWithTitle:@""];
|
|
|
|
[self buildApplicationMenu:menubar];
|
|
|
|
return menubar;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
2015-04-28 16:48:56 -05:00
|
|
|
|
2015-04-28 19:34:57 -05:00
|
|
|
static void defaultOnClicked(uiMenuItem *item, uiWindow *w, void *data)
|
|
|
|
{
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
static void menuItemEnable(uiMenuItem *ii)
|
|
|
|
{
|
|
|
|
struct menuItem *item = (struct menuItem *) ii;
|
|
|
|
|
|
|
|
[item->item setEnabled:YES];
|
|
|
|
}
|
|
|
|
|
|
|
|
static void menuItemDisable(uiMenuItem *ii)
|
|
|
|
{
|
|
|
|
struct menuItem *item = (struct menuItem *) ii;
|
|
|
|
|
|
|
|
[item->item setEnabled:NO];
|
|
|
|
}
|
|
|
|
|
|
|
|
static void menuItemOnClicked(uiMenuItem *ii, void (*f)(uiMenuItem *, uiWindow *, void *), void *data)
|
|
|
|
{
|
|
|
|
struct menuItem *item = (struct menuItem *) ii;
|
|
|
|
|
|
|
|
item->onClicked = f;
|
|
|
|
item->onClickedData = data;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int menuItemChecked(uiMenuItem *ii)
|
|
|
|
{
|
|
|
|
struct menuItem *item = (struct menuItem *) ii;
|
|
|
|
|
|
|
|
return [item->item state] != NSOffState;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void menuItemSetChecked(uiMenuItem *ii, int checked)
|
|
|
|
{
|
|
|
|
struct menuItem *item = (struct menuItem *) ii;
|
|
|
|
NSInteger state;
|
|
|
|
|
|
|
|
state = NSOffState;
|
|
|
|
if ([item->item state] == NSOffState)
|
|
|
|
state = NSOnState;
|
|
|
|
[item->item setState:state];
|
|
|
|
}
|
|
|
|
|
|
|
|
static uiMenuItem *newItem(struct menu *m, int type, const char *name)
|
|
|
|
{
|
|
|
|
struct menuItem *item;
|
|
|
|
|
|
|
|
item = uiNew(struct menuItem);
|
|
|
|
|
|
|
|
item->type = type;
|
|
|
|
switch (item->type) {
|
|
|
|
case typeQuit:
|
|
|
|
item->item = appDelegate().menuManager.quitItem;
|
|
|
|
[item->item setEnabled:YES];
|
|
|
|
break;
|
|
|
|
case typePreferences:
|
|
|
|
item->item = appDelegate().menuManager.preferencesItem;
|
|
|
|
[item->item setEnabled:YES];
|
|
|
|
break;
|
|
|
|
case typeAbout:
|
|
|
|
item->item = appDelegate().menuManager.aboutItem;
|
|
|
|
[item->item setEnabled:YES];
|
|
|
|
break;
|
|
|
|
case typeSeparator:
|
|
|
|
item->item = [NSMenuItem separatorItem];
|
|
|
|
[m->menu addItem:item->item];
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
item->item = [[NSMenuItem alloc] initWithTitle:toNSString(name) action:@selector(onClicked:) keyEquivalent:@""];
|
|
|
|
[item->item setTarget:appDelegate().menuManager];
|
|
|
|
[m->menu addItem:item->item];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
[appDelegate().menuManager register:item->item to:item];
|
|
|
|
item->onClicked = defaultOnClicked;
|
|
|
|
|
|
|
|
uiMenuItem(item)->Enable = menuItemEnable;
|
|
|
|
uiMenuItem(item)->Disable = menuItemDisable;
|
|
|
|
uiMenuItem(item)->OnClicked = menuItemOnClicked;
|
|
|
|
uiMenuItem(item)->Checked = menuItemChecked;
|
|
|
|
uiMenuItem(item)->SetChecked = menuItemSetChecked;
|
|
|
|
|
|
|
|
return uiMenuItem(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
uiMenuItem *menuAppendItem(uiMenu *mm, const char *name)
|
|
|
|
{
|
|
|
|
return newItem((struct menu *) mm, typeRegular, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
uiMenuItem *menuAppendCheckItem(uiMenu *mm, const char *name)
|
|
|
|
{
|
|
|
|
return newItem((struct menu *) mm, typeCheckbox, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
uiMenuItem *menuAppendQuitItem(uiMenu *mm)
|
|
|
|
{
|
2015-04-30 22:24:52 -05:00
|
|
|
// duplicate check is in the register:to: selector
|
2015-04-28 19:34:57 -05:00
|
|
|
return newItem((struct menu *) mm, typeQuit, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
uiMenuItem *menuAppendPreferencesItem(uiMenu *mm)
|
|
|
|
{
|
2015-04-30 22:24:52 -05:00
|
|
|
// duplicate check is in the register:to: selector
|
2015-04-28 19:34:57 -05:00
|
|
|
return newItem((struct menu *) mm, typePreferences, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
uiMenuItem *menuAppendAboutItem(uiMenu *mm)
|
|
|
|
{
|
2015-04-30 22:24:52 -05:00
|
|
|
// duplicate check is in the register:to: selector
|
2015-04-28 19:34:57 -05:00
|
|
|
return newItem((struct menu *) mm, typeAbout, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
void menuAppendSeparator(uiMenu *mm)
|
|
|
|
{
|
|
|
|
newItem((struct menu *) mm, typeSeparator, NULL);
|
|
|
|
}
|
|
|
|
|
2015-04-28 16:48:56 -05:00
|
|
|
uiMenu *uiNewMenu(const char *name)
|
|
|
|
{
|
2015-04-28 19:34:57 -05:00
|
|
|
struct menu *m;
|
|
|
|
|
|
|
|
m = uiNew(struct menu);
|
|
|
|
|
|
|
|
m->menu = [[NSMenu alloc] initWithTitle:toNSString(name)];
|
|
|
|
[m->menu setAutoenablesItems:NO];
|
|
|
|
|
|
|
|
m->item = [[NSMenuItem alloc] initWithTitle:toNSString(name) action:NULL keyEquivalent:@""];
|
|
|
|
[m->item setSubmenu:m->menu];
|
|
|
|
|
|
|
|
[[NSApp mainMenu] addItem:m->item];
|
|
|
|
|
|
|
|
uiMenu(m)->AppendItem = menuAppendItem;
|
|
|
|
uiMenu(m)->AppendCheckItem = menuAppendCheckItem;
|
|
|
|
uiMenu(m)->AppendQuitItem = menuAppendQuitItem;
|
|
|
|
uiMenu(m)->AppendPreferencesItem = menuAppendPreferencesItem;
|
|
|
|
uiMenu(m)->AppendAboutItem = menuAppendAboutItem;
|
|
|
|
uiMenu(m)->AppendSeparator = menuAppendSeparator;
|
|
|
|
|
|
|
|
return uiMenu(m);
|
2015-04-28 16:48:56 -05:00
|
|
|
}
|