/*
* 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 <ctype.h>
}
/* ----------------------------------------------------------------------
+ * 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'
@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} };
}
}
- 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;
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
/*
/* ----------------------------------------------------------------------
* Utility routines for constructing OS X menus.
-~|~ */
+ */
NSMenu *newmenu(const char *title)
{
}
/* ----------------------------------------------------------------------
- * 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.
*/
- (IBAction)newGame:(id)sender
{
- const game *g = [sender getGame];
+ const game *g = [sender getPayload];
id win;
win = [[GameWindow alloc] initWithGame:g];
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];