libui-ffi 0.4.0

Easy to build low-level bindings to 'libui-ng'
Documentation
// 28 april 2015
#import "uipriv_darwin.h"

struct uiMenu {
	NSMenu *menu;
	NSMenuItem *item;
};

struct uiMenuItem {
	uiprivMenuItem *item;
	int type;
	BOOL disabled;
	void (*onClicked)(uiMenuItem *, uiWindow *, void *);
	void *onClickedData;
};

enum uiprivMenuItemType {
	typeRegular,
	typeCheckbox,
	typeQuit,
	typePreferences,
	typeAbout,
};

@interface uiprivMenu : NSMenu {
@public
	uiMenu *menu;
}
@end

@implementation uiprivMenu
- (id)initWithTitle:(NSString *)title uiMenu:(uiMenu *)m
{
	self = [super initWithTitle:title];
	if (self) {
		self->menu = m;
	}
	return self;
}
@end

@implementation uiprivMenuItem
- (id)initWithTitle:(NSString *)title uiMenuItem:(uiMenuItem *)i
{
	self = [super initWithTitle:title action:@selector(onClicked:) keyEquivalent:@""];
	if (self) {
		self->item = i;

		[self setTarget:self];
	}
	return self;
}

- (IBAction)onClicked:(id)sender
{
	// System menu item (Quit/Preferences/About) that has not been user created (yet)
	if (self->item == NULL) {
		uiprivImplBug("Clicked nonexistent uiMenuItem which should be impossible");
		return;
	}

	switch (self->item->type) {
	case typeQuit:
		if (uiprivShouldQuit())
			uiQuit();
		return;
	case typeCheckbox:
		uiMenuItemSetChecked(self->item, !uiMenuItemChecked(self->item));
		// fall through
	default:
		// use the key window as the source of the menu event; it's the active window
		(*(self->item->onClicked))(self->item, uiprivWindowFromNSWindow([uiprivNSApp() keyWindow]),
			self->item->onClickedData);
		break;
	}
}

- (void)setUiMenuItem:(uiMenuItem *)i
{
	self->item = i;
}

// Manually enable/disable menu items
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
	uiprivMenuItem *i = (uiprivMenuItem *)menuItem;

	// System menu item (Quit/Preferences/About) that has not been user created (yet)
	if (i->item == NULL)
		return NO;

	return !i->item->disabled;
}

@end

@implementation uiprivMenuManager

- (id)init
{
	self = [super init];
	if (self) {
		self->hasQuit = NO;
		self->hasPreferences = NO;
		self->hasAbout = NO;
		self->finalized = NO;
	}
	return self;
}

- (BOOL)finalized
{
	return self->finalized;
}

- (void)finalize
{
	self->finalized = YES;
}

- (void)dealloc
{
	uiprivUninitMenus();
	[super dealloc];
}

- (void)register:(enum uiprivMenuItemType)type
{
	switch (type) {
	case typeQuit:
		if (self->hasQuit)
			uiprivUserBug("You can't have multiple Quit menu items in one program.");
		self->hasQuit = YES;
		break;
	case typePreferences:
		if (self->hasPreferences)
			uiprivUserBug("You can't have multiple Preferences menu items in one program.");
		self->hasPreferences = YES;
		break;
	case typeAbout:
		if (self->hasAbout)
			uiprivUserBug("You can't have multiple About menu items in one program.");
		self->hasAbout = YES;
		break;
	}
}

// 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;
	uiprivMenuItem *pitem;
	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];
	pitem = [[[uiprivMenuItem alloc] initWithTitle:title uiMenuItem:NULL] autorelease];
	[appMenu addItem:pitem];
	self.aboutItem = pitem;

	[appMenu addItem:[NSMenuItem separatorItem]];

	// next is Preferences
	pitem = [[[uiprivMenuItem alloc] initWithTitle:@"Preferences\u2026" uiMenuItem:NULL] autorelease];
	[appMenu addItem:pitem];
	self.preferencesItem = pitem;

	[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];
	[uiprivNSApp() 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];
	pitem = [[[uiprivMenuItem alloc] initWithTitle:title uiMenuItem:NULL] autorelease];
	[appMenu addItem:pitem];
	self.quitItem = pitem;
}

- (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)
		uiprivUserBug("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 ([uiprivAppDelegate().menuManager finalized])
		uiprivUserBug("You can't create a new menu item after menus have been finalized.");

	item = uiprivNew(uiMenuItem);

	item->type = type;
	switch (item->type) {
	case typeQuit:
		item->item = uiprivAppDelegate().menuManager.quitItem;
		[item->item setUiMenuItem:item];
		break;
	case typePreferences:
		item->item = uiprivAppDelegate().menuManager.preferencesItem;
		[item->item setUiMenuItem:item];
		break;
	case typeAbout:
		item->item = uiprivAppDelegate().menuManager.aboutItem;
		[item->item setUiMenuItem:item];
		break;
	default:
		item->item = [[uiprivMenuItem alloc] initWithTitle:uiprivToNSString(name) uiMenuItem:item];
		[m->menu addItem:item->item];
		break;
	}

	[uiprivAppDelegate().menuManager register:item->type];
	// typeQuit is handled via uiprivShouldQuit()
	if (item->type != typeQuit)
		uiMenuItemOnClicked(item, defaultOnClicked, NULL);

	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)
{
	return newItem(m, typeQuit, NULL);
}

uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m)
{
	return newItem(m, typePreferences, NULL);
}

uiMenuItem *uiMenuAppendAboutItem(uiMenu *m)
{
	return newItem(m, typeAbout, NULL);
}

void uiMenuAppendSeparator(uiMenu *m)
{
	[m->menu addItem:[NSMenuItem separatorItem]];
}

uiMenu *uiNewMenu(const char *name)
{
	@autoreleasepool {

	uiMenu *m;

	if ([uiprivAppDelegate().menuManager finalized])
		uiprivUserBug("You can't create a new menu after menus have been finalized.");

	m = uiprivNew(uiMenu);

	m->menu = [[uiprivMenu alloc] initWithTitle:uiprivToNSString(name) uiMenu:m];
	// use automatic menu item enabling for all menus for consistency's sake

	m->item = [[NSMenuItem alloc] initWithTitle:uiprivToNSString(name) action:NULL keyEquivalent:@""];
	[m->item setSubmenu:m->menu];

	[[uiprivNSApp() mainMenu] addItem:m->item];

	return m;

	} // @autoreleasepool
}

void uiprivFinalizeMenus(void)
{
	[uiprivAppDelegate().menuManager finalize];
}

void uiprivUninitMenus(void)
{
	NSMenuItem *mi;
	NSMenu *sm;
	NSMenuItem *smi;

	for (mi in [[uiprivNSApp() mainMenu] itemArray]) {
		if ([mi hasSubmenu]) {
			sm = [mi submenu];
			for (smi in [sm itemArray]) {
				if ([smi isKindOfClass:[uiprivMenuItem class]]) {
					uiprivMenuItem *x = (uiprivMenuItem *)smi;
					if (x->item != NULL)
						uiprivFree(x->item);
				}
			}
			if ([sm isKindOfClass:[uiprivMenu class]]) {
				uiprivMenu *x = (uiprivMenu *)sm;
				uiprivFree(x->menu);
			}
		}
	}
}