X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/6d634196bf3d10fd692a72969b392eb2b2a1e510..5eae38d148446beb4fdc1aaed5fbd4a876f30e99:/macosx.m?ds=sidebyside diff --git a/macosx.m b/macosx.m index d8d941a..52a70ef 100644 --- a/macosx.m +++ b/macosx.m @@ -1,55 +1,67 @@ /* * Mac OS X / Cocoa front end to puzzles. - * + * * TODO: - * + * * - status bar support. - * - * - preset selection. Should be reasonably simple: just a matter - * of dynamically frobbing the menu bar. - * + * * - configurability. Will no doubt involve learning all about the * dialog control side of Cocoa. - * + * * - needs an icon. - * + * * - not sure what I should be doing about default window * placement. Centring new windows is a bit feeble, but what's * better? Is there a standard way to tell the OS "here's the * _size_ of window I want, now use your best judgment about the * initial position"? - * + * * - a brief frob of the Mac numeric keypad suggests that it * generates numbers no matter what you do. I wonder if I should * try to figure out a way of detecting keypad codes so I can * implement UP_LEFT and friends. Alternatively, perhaps I * should simply assign the number keys to UP_LEFT et al? * They're not in use for anything else right now. - * + * * - proper fatal errors. - * + * * - is there a better approach to frontend_default_colour? + * + * - do we need any more options in the Window menu? + * + * - see if we can do anything to one-button-ise the multi-button + * dependent puzzle UIs: + * - Pattern is a _little_ unwieldy but not too bad (since + * generally you never need the middle button unless you've + * made a mistake, so it's just click versus command-click). + * - Net is utterly vile; having normal click be one rotate and + * command-click be the other introduces a horrid asymmetry, + * and yet requiring a shift key for _each_ click would be + * even worse because rotation feels as if it ought to be the + * default action. I fear this is why the Flash Net had the + * UI it did... + * + * - Find out how to do help, and do some. We have a help file; at + * _worst_ this should involve a new Halibut back end, but I + * think help is HTML round here anyway so perhaps we can work + * with what we already have. * - * - some options in the Window menu! Close and Minimise, I think, - * at least. + * - Can we arrange for a pop-up menu from the Dock icon which + * launches specific games, perhaps? * - * - more Mac-ish key bindings. I suspect, for example, that Undo - * should be Command-Z as well as (or even instead of?) Ctrl-Z. + * - Why are the right and bottom edges of the Pattern grid one + * pixel thinner than they should be? * - * - Which reminds me, commands like Undo and Redo also ought to - * be available from the menus. Actually, the sensible thing is - * probably to go and look at the menus on Unix and make sure - * everything is there. + * Grotty implementation details that could probably be improved: * - * - see if we can do anything to one-button-ise the puzzle UIs. - * Some are fine as is (Sixteen, Fifteen, Rectangles, Netslide, - * Cube), some are a little unwieldy with Command-clicking - * (Pattern), and some are utterly horrid (Net). + * - I am _utterly_ unconvinced that NSImageView was the right way + * to go about having a window with a reliable backing store! It + * just doesn't feel right; NSImageView is a _control_. Is there + * a simpler way? * - * - Find out how to do help, and do some. We have a help file; at - * _worst_ this should involve a new Halibut back end, but I - * think help is HTML round here anyway so perhaps we can work - * with what we already have. + * - Resizing is currently very bad; rather than bother to work + * out how to resize the NSImageView, I just splatter and + * recreate it. */ #include @@ -91,6 +103,39 @@ void get_random_seed(void **randseed, int *randseedsize) } /* ---------------------------------------------------------------------- + * Global variables. + */ + +/* + * The `Type' menu. We frob this dynamically to allow the user to + * choose a preset set of settings from the current game. + */ +NSMenu *typemenu; + +/* ---------------------------------------------------------------------- + * Tiny extension to NSMenuItem which carries a payload of a `void + * *', allowing several menu items to invoke the same message but + * pass different data through it. + */ +@interface DataMenuItem : NSMenuItem +{ + void *payload; +} +- (void)setPayload:(void *)d; +- (void *)getPayload; +@end +@implementation DataMenuItem +- (void)setPayload:(void *)d +{ + payload = d; +} +- (void *)getPayload +{ + return payload; +} +@end + +/* ---------------------------------------------------------------------- * The front end presented to midend.c. * * This is mostly a subclass of NSWindow. The actual `frontend' @@ -217,6 +262,26 @@ struct frontend { @end @implementation GameWindow +- (void)setupContentView +{ + NSSize size = {0,0}; + int w, h; + + midend_size(me, &w, &h); + size.width = w; + size.height = h; + + fe.image = [[NSImage alloc] initWithSize:size]; + [fe.image setFlipped:YES]; + fe.view = [[MyImageView alloc] + initWithFrame:[self contentRectForFrameRect:[self frame]]]; + [fe.view setImage:fe.image]; + [fe.view setWindow:self]; + + midend_redraw(me); + + [self setContentView:fe.view]; +} - (id)initWithGame:(const game *)g { NSRect rect = { {0,0}, {0,0} }; @@ -260,17 +325,9 @@ struct frontend { } } - fe.image = [[NSImage alloc] initWithSize:rect.size]; - [fe.image setFlipped:YES]; - fe.view = [[MyImageView alloc] - initWithFrame:[self contentRectForFrameRect:[self frame]]]; - [fe.view setImage:fe.image]; - [fe.view setWindow:self]; - [self setContentView:fe.view]; + [self setupContentView]; [self setIgnoresMouseEvents:NO]; - midend_redraw(me); - [self center]; /* :-) */ return self; @@ -360,6 +417,100 @@ struct frontend { last_time = now; } +- (void)newGame:(id)sender +{ + [self processButton:'n' x:-1 y:-1]; +} +- (void)restartGame:(id)sender +{ + [self processButton:'r' x:-1 y:-1]; +} +- (void)undoMove:(id)sender +{ + [self processButton:'u' x:-1 y:-1]; +} +- (void)redoMove:(id)sender +{ + [self processButton:'r'&0x1F x:-1 y:-1]; +} + +- (void)clearTypeMenu +{ + while ([typemenu numberOfItems] > 1) + [typemenu removeItemAtIndex:0]; +} + +- (void)becomeKeyWindow +{ + int n; + + [self clearTypeMenu]; + + [super becomeKeyWindow]; + + n = midend_num_presets(me); + + if (n > 0) { + [typemenu insertItem:[NSMenuItem separatorItem] atIndex:0]; + while (n--) { + char *name; + game_params *params; + DataMenuItem *item; + + midend_fetch_preset(me, n, &name, ¶ms); + + item = [[[DataMenuItem alloc] + initWithTitle:[NSString stringWithCString:name] + action:NULL keyEquivalent:@""] + autorelease]; + + [item setEnabled:YES]; + [item setTarget:self]; + [item setAction:@selector(presetGame:)]; + [item setPayload:params]; + + [typemenu insertItem:item atIndex:0]; + } + } +} + +- (void)resignKeyWindow +{ + [self clearTypeMenu]; + [super resignKeyWindow]; +} + +- (void)close +{ + [self clearTypeMenu]; + [super close]; +} + +- (void)resizeForNewGameParams +{ + NSSize size = {0,0}; + int w, h; + + midend_size(me, &w, &h); + size.width = w; + size.height = h; + + NSDisableScreenUpdates(); + [self setContentSize:size]; + [self setupContentView]; + NSEnableScreenUpdates(); +} + +- (void)presetGame:(id)sender +{ + game_params *params = [sender getPayload]; + + midend_set_params(me, params); + midend_new_game(me); + + [self resizeForNewGameParams]; +} + @end /* @@ -492,7 +643,7 @@ void activate_timer(frontend *fe) /* ---------------------------------------------------------------------- * Utility routines for constructing OS X menus. -~|~ */ + */ NSMenu *newmenu(const char *title) { @@ -562,29 +713,6 @@ NSMenuItem *newitem(NSMenu *parent, char *title, char *key, } /* ---------------------------------------------------------------------- - * Tiny extension to NSMenuItem which carries a payload of a `const - * game *', allowing our AppController to work out _which_ game - * needs to be launched when it receives a newGame message. - */ -@interface GameMenuItem : NSMenuItem -{ - const game *ourgame; -} -- (void)setGame:(const game *)g; -- (const game *)getGame; -@end -@implementation GameMenuItem -- (void)setGame:(const game *)g -{ - ourgame = g; -} -- (const game *)getGame -{ - return ourgame; -} -@end - -/* ---------------------------------------------------------------------- * AppController: the object which receives the messages from all * menu selections that aren't standard OS X functions. */ @@ -598,7 +726,7 @@ NSMenuItem *newitem(NSMenu *parent, char *title, char *key, - (IBAction)newGame:(id)sender { - const game *g = [sender getGame]; + const game *g = [sender getPayload]; id win; win = [[GameWindow alloc] initWithGame:g]; @@ -634,21 +762,36 @@ int main(int argc, char **argv) item = newitem(menu, "Quit", "q", NSApp, @selector(terminate:)); [NSApp setAppleMenu: menu]; - menu = newsubmenu([NSApp mainMenu], "Game"); + menu = newsubmenu([NSApp mainMenu], "Open"); { int i; for (i = 0; i < gamecount; i++) { id item = - initnewitem([GameMenuItem allocWithZone:[NSMenu menuZone]], + initnewitem([DataMenuItem allocWithZone:[NSMenu menuZone]], menu, gamelist[i]->name, "", controller, @selector(newGame:)); - [item setGame:gamelist[i]]; + [item setPayload:(void *)gamelist[i]]; } } - menu = newsubmenu([NSApp mainMenu], "Windows"); + menu = newsubmenu([NSApp mainMenu], "Game"); + item = newitem(menu, "New", "n", NULL, @selector(newGame:)); + item = newitem(menu, "Restart", "r", NULL, @selector(restartGame:)); + item = newitem(menu, "Specific", "", NULL, @selector(specificGame:)); + [menu addItem:[NSMenuItem separatorItem]]; + item = newitem(menu, "Undo", "z", NULL, @selector(undoMove:)); + item = newitem(menu, "Redo", "S-z", NULL, @selector(redoMove:)); + [menu addItem:[NSMenuItem separatorItem]]; + item = newitem(menu, "Close", "w", NULL, @selector(performClose:)); + + menu = newsubmenu([NSApp mainMenu], "Type"); + typemenu = menu; + item = newitem(menu, "Custom", "", NULL, @selector(customGameType:)); + + menu = newsubmenu([NSApp mainMenu], "Window"); [NSApp setWindowsMenu: menu]; + item = newitem(menu, "Minimise Window", "m", NULL, @selector(performMiniaturize:)); [NSApp run]; [pool release];