Ah, no, _that_ fixes the malloc problem. I'd forgotten that midend.c
[sgt/puzzles] / macosx.m
index d8d941a..52a70ef 100644 (file)
--- 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 <ctype.h>
@@ -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, &params);
+
+           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];