aboutsummaryrefslogtreecommitdiff
path: root/src/libui_sdl/libui/darwin/menu.m
diff options
context:
space:
mode:
Diffstat (limited to 'src/libui_sdl/libui/darwin/menu.m')
-rw-r--r--src/libui_sdl/libui/darwin/menu.m368
1 files changed, 368 insertions, 0 deletions
diff --git a/src/libui_sdl/libui/darwin/menu.m b/src/libui_sdl/libui/darwin/menu.m
new file mode 100644
index 0000000..735cac5
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/menu.m
@@ -0,0 +1,368 @@
+// 28 april 2015
+#import "uipriv_darwin.h"
+
+static NSMutableArray *menus = nil;
+static BOOL menusFinalized = NO;
+
+struct uiMenu {
+ NSMenu *menu;
+ NSMenuItem *item;
+ NSMutableArray *items;
+};
+
+struct uiMenuItem {
+ NSMenuItem *item;
+ int type;
+ BOOL disabled;
+ void (*onClicked)(uiMenuItem *, uiWindow *, void *);
+ void *onClickedData;
+};
+
+enum {
+ typeRegular,
+ typeCheckbox,
+ typeQuit,
+ typePreferences,
+ typeAbout,
+ typeSeparator,
+};
+
+static void mapItemReleaser(void *key, void *value)
+{
+ uiMenuItem *item;
+
+ item = (uiMenuItem *)value;
+ [item->item release];
+}
+
+@implementation menuManager
+
+- (id)init
+{
+ self = [super init];
+ if (self) {
+ self->items = newMap();
+ self->hasQuit = NO;
+ self->hasPreferences = NO;
+ self->hasAbout = NO;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ mapWalk(self->items, mapItemReleaser);
+ mapReset(self->items);
+ mapDestroy(self->items);
+ uninitMenus();
+ [super dealloc];
+}
+
+- (IBAction)onClicked:(id)sender
+{
+ uiMenuItem *item;
+
+ item = (uiMenuItem *) mapGet(self->items, sender);
+ if (item->type == typeCheckbox)
+ uiMenuItemSetChecked(item, !uiMenuItemChecked(item));
+ // use the key window as the source of the menu event; it's the active window
+ (*(item->onClicked))(item, windowFromNSWindow([realNSApp() keyWindow]), item->onClickedData);
+}
+
+- (IBAction)onQuitClicked:(id)sender
+{
+ if (shouldQuit())
+ uiQuit();
+}
+
+- (void)register:(NSMenuItem *)item to:(uiMenuItem *)smi
+{
+ switch (smi->type) {
+ case typeQuit:
+ if (self->hasQuit)
+ userbug("You can't have multiple Quit menu items in one program.");
+ self->hasQuit = YES;
+ break;
+ case typePreferences:
+ if (self->hasPreferences)
+ userbug("You can't have multiple Preferences menu items in one program.");
+ self->hasPreferences = YES;
+ break;
+ case typeAbout:
+ if (self->hasAbout)
+ userbug("You can't have multiple About menu items in one program.");
+ self->hasAbout = YES;
+ break;
+ }
+ mapSet(self->items, item, smi);
+}
+
+// on OS X there are two ways to handle menu items being enabled or disabled: automatically and manually
+// unfortunately, the application menu requires automatic menu handling for the Hide, Hide Others, and Show All items to work correctly
+// therefore, we have to handle enabling of the other options ourselves
+- (BOOL)validateMenuItem:(NSMenuItem *)item
+{
+ uiMenuItem *smi;
+
+ // disable the special items if they aren't present
+ if (item == self.quitItem && !self->hasQuit)
+ return NO;
+ if (item == self.preferencesItem && !self->hasPreferences)
+ return NO;
+ if (item == self.aboutItem && !self->hasAbout)
+ return NO;
+ // then poll the item's enabled/disabled state
+ smi = (uiMenuItem *) mapGet(self->items, item);
+ return !smi->disabled;
+}
+
+// Cocoa constructs the default application menu by hand for each program; that's what MainMenu.[nx]ib does
+- (void)buildApplicationMenu:(NSMenu *)menubar
+{
+ NSString *appName;
+ NSMenuItem *appMenuItem;
+ NSMenu *appMenu;
+ NSMenuItem *item;
+ NSString *title;
+ NSMenu *servicesMenu;
+
+ // note: no need to call setAppleMenu: on this anymore; see https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_6Notes
+ appName = [[NSProcessInfo processInfo] processName];
+ appMenuItem = [[[NSMenuItem alloc] initWithTitle:appName action:NULL keyEquivalent:@""] autorelease];
+ appMenu = [[[NSMenu alloc] initWithTitle:appName] autorelease];
+ [appMenuItem setSubmenu:appMenu];
+ [menubar addItem:appMenuItem];
+
+ // first is About
+ title = [@"About " stringByAppendingString:appName];
+ item = [[[NSMenuItem alloc] initWithTitle:title action:@selector(onClicked:) keyEquivalent:@""] autorelease];
+ [item setTarget:self];
+ [appMenu addItem:item];
+ self.aboutItem = item;
+
+ [appMenu addItem:[NSMenuItem separatorItem]];
+
+ // next is Preferences
+ item = [[[NSMenuItem alloc] initWithTitle:@"Preferences…" action:@selector(onClicked:) keyEquivalent:@","] autorelease];
+ [item setTarget:self];
+ [appMenu addItem:item];
+ self.preferencesItem = item;
+
+ [appMenu addItem:[NSMenuItem separatorItem]];
+
+ // next is Services
+ item = [[[NSMenuItem alloc] initWithTitle:@"Services" action:NULL keyEquivalent:@""] autorelease];
+ servicesMenu = [[[NSMenu alloc] initWithTitle:@"Services"] autorelease];
+ [item setSubmenu:servicesMenu];
+ [realNSApp() setServicesMenu:servicesMenu];
+ [appMenu addItem:item];
+
+ [appMenu addItem:[NSMenuItem separatorItem]];
+
+ // next are the three hiding options
+ title = [@"Hide " stringByAppendingString:appName];
+ item = [[[NSMenuItem alloc] initWithTitle:title action:@selector(hide:) keyEquivalent:@"h"] autorelease];
+ // the .xib file says they go to -1 ("First Responder", which sounds wrong...)
+ // to do that, we simply leave the target as nil
+ [appMenu addItem:item];
+ item = [[[NSMenuItem alloc] initWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"] autorelease];
+ [item setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)];
+ [appMenu addItem:item];
+ item = [[[NSMenuItem alloc] initWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""] autorelease];
+ [appMenu addItem:item];
+
+ [appMenu addItem:[NSMenuItem separatorItem]];
+
+ // and finally Quit
+ // DON'T use @selector(terminate:) as the action; we handle termination ourselves
+ title = [@"Quit " stringByAppendingString:appName];
+ item = [[[NSMenuItem alloc] initWithTitle:title action:@selector(onQuitClicked:) keyEquivalent:@"q"] autorelease];
+ [item setTarget:self];
+ [appMenu addItem:item];
+ self.quitItem = item;
+}
+
+- (NSMenu *)makeMenubar
+{
+ NSMenu *menubar;
+
+ menubar = [[[NSMenu alloc] initWithTitle:@""] autorelease];
+ [self buildApplicationMenu:menubar];
+ return menubar;
+}
+
+@end
+
+static void defaultOnClicked(uiMenuItem *item, uiWindow *w, void *data)
+{
+ // do nothing
+}
+
+void uiMenuItemEnable(uiMenuItem *item)
+{
+ item->disabled = NO;
+ // we don't need to explicitly update the menus here; they'll be updated the next time they're opened (thanks mikeash in irc.freenode.net/#macdev)
+}
+
+void uiMenuItemDisable(uiMenuItem *item)
+{
+ item->disabled = YES;
+}
+
+void uiMenuItemOnClicked(uiMenuItem *item, void (*f)(uiMenuItem *, uiWindow *, void *), void *data)
+{
+ if (item->type == typeQuit)
+ userbug("You can't call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead.");
+ item->onClicked = f;
+ item->onClickedData = data;
+}
+
+int uiMenuItemChecked(uiMenuItem *item)
+{
+ return [item->item state] != NSOffState;
+}
+
+void uiMenuItemSetChecked(uiMenuItem *item, int checked)
+{
+ NSInteger state;
+
+ state = NSOffState;
+ if ([item->item state] == NSOffState)
+ state = NSOnState;
+ [item->item setState:state];
+}
+
+static uiMenuItem *newItem(uiMenu *m, int type, const char *name)
+{
+ @autoreleasepool {
+
+ uiMenuItem *item;
+
+ if (menusFinalized)
+ userbug("You can't create a new menu item after menus have been finalized.");
+
+ item = uiNew(uiMenuItem);
+
+ item->type = type;
+ switch (item->type) {
+ case typeQuit:
+ item->item = [appDelegate().menuManager.quitItem retain];
+ break;
+ case typePreferences:
+ item->item = [appDelegate().menuManager.preferencesItem retain];
+ break;
+ case typeAbout:
+ item->item = [appDelegate().menuManager.aboutItem retain];
+ break;
+ case typeSeparator:
+ item->item = [[NSMenuItem separatorItem] retain];
+ [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;
+
+ [m->items addObject:[NSValue valueWithPointer:item]];
+
+ return item;
+
+ } // @autoreleasepool
+}
+
+uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name)
+{
+ return newItem(m, typeRegular, name);
+}
+
+uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name)
+{
+ return newItem(m, typeCheckbox, name);
+}
+
+uiMenuItem *uiMenuAppendQuitItem(uiMenu *m)
+{
+ // duplicate check is in the register:to: selector
+ return newItem(m, typeQuit, NULL);
+}
+
+uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m)
+{
+ // duplicate check is in the register:to: selector
+ return newItem(m, typePreferences, NULL);
+}
+
+uiMenuItem *uiMenuAppendAboutItem(uiMenu *m)
+{
+ // duplicate check is in the register:to: selector
+ return newItem(m, typeAbout, NULL);
+}
+
+void uiMenuAppendSeparator(uiMenu *m)
+{
+ newItem(m, typeSeparator, NULL);
+}
+
+uiMenu *uiNewMenu(const char *name)
+{
+ @autoreleasepool {
+
+ uiMenu *m;
+
+ if (menusFinalized)
+ userbug("You can't create a new menu after menus have been finalized.");
+ if (menus == nil)
+ menus = [NSMutableArray new];
+
+ m = uiNew(uiMenu);
+
+ m->menu = [[NSMenu alloc] initWithTitle:toNSString(name)];
+ // use automatic menu item enabling for all menus for consistency's sake
+
+ m->item = [[NSMenuItem alloc] initWithTitle:toNSString(name) action:NULL keyEquivalent:@""];
+ [m->item setSubmenu:m->menu];
+
+ m->items = [NSMutableArray new];
+
+ [[realNSApp() mainMenu] addItem:m->item];
+
+ [menus addObject:[NSValue valueWithPointer:m]];
+
+ return m;
+
+ } // @autoreleasepool
+}
+
+void finalizeMenus(void)
+{
+ menusFinalized = YES;
+}
+
+void uninitMenus(void)
+{
+ if (menus == NULL)
+ return;
+ [menus enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {
+ NSValue *v;
+ uiMenu *m;
+
+ v = (NSValue *) obj;
+ m = (uiMenu *) [v pointerValue];
+ [m->items enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {
+ NSValue *v;
+ uiMenuItem *mi;
+
+ v = (NSValue *) obj;
+ mi = (uiMenuItem *) [v pointerValue];
+ uiFree(mi);
+ }];
+ [m->items release];
+ uiFree(m);
+ }];
+ [menus release];
+}