+- (void)startConfigureSheet:(int)which
+{
+ NSButton *ok, *cancel;
+ int actw, acth, leftw, rightw, totalw, h, thish, y;
+ int k;
+ NSRect rect, tmprect;
+ const int SPACING = 16;
+ char *title;
+ config_item *i;
+ int cfg_controlsize;
+ NSTextField *tf;
+ NSButton *b;
+ NSPopUpButton *pb;
+
+ assert(sheet == NULL);
+
+ /*
+ * Every control we create here is going to have this size
+ * until we tell it to calculate a better one.
+ */
+ tmprect = NSMakeRect(0, 0, 100, 50);
+
+ /*
+ * Set up OK and Cancel buttons. (Actually, MacOS doesn't seem
+ * to be fond of generic OK and Cancel wording, so I'm going to
+ * rename them to something nicer.)
+ */
+ actw = acth = 0;
+
+ cancel = [[NSButton alloc] initWithFrame:tmprect];
+ [cancel setBezelStyle:NSRoundedBezelStyle];
+ [cancel setTitle:@"Abandon"];
+ [cancel setTarget:self];
+ [cancel setKeyEquivalent:@"\033"];
+ [cancel setAction:@selector(sheetCancelButton:)];
+ [cancel sizeToFit];
+ rect = [cancel frame];
+ if (actw < rect.size.width) actw = rect.size.width;
+ if (acth < rect.size.height) acth = rect.size.height;
+
+ ok = [[NSButton alloc] initWithFrame:tmprect];
+ [ok setBezelStyle:NSRoundedBezelStyle];
+ [ok setTitle:@"Accept"];
+ [ok setTarget:self];
+ [ok setKeyEquivalent:@"\r"];
+ [ok setAction:@selector(sheetOKButton:)];
+ [ok sizeToFit];
+ rect = [ok frame];
+ if (actw < rect.size.width) actw = rect.size.width;
+ if (acth < rect.size.height) acth = rect.size.height;
+
+ totalw = SPACING + 2 * actw;
+ h = 2 * SPACING + acth;
+
+ /*
+ * Now fetch the midend config data and go through it creating
+ * controls.
+ */
+ cfg = midend_get_config(me, which, &title);
+ sfree(title); /* FIXME: should we use this somehow? */
+ cfg_which = which;
+
+ cfg_ncontrols = cfg_controlsize = 0;
+ cfg_controls = NULL;
+ leftw = rightw = 0;
+ for (i = cfg; i->type != C_END; i++) {
+ if (cfg_controlsize < cfg_ncontrols + 5) {
+ cfg_controlsize = cfg_ncontrols + 32;
+ cfg_controls = sresize(cfg_controls, cfg_controlsize, NSView *);
+ }
+
+ thish = 0;
+
+ switch (i->type) {
+ case C_STRING:
+ /*
+ * Two NSTextFields, one being a label and the other
+ * being an edit box.
+ */
+
+ tf = [[NSTextField alloc] initWithFrame:tmprect];
+ [tf setEditable:NO];
+ [tf setSelectable:NO];
+ [tf setBordered:NO];
+ [tf setDrawsBackground:NO];
+ [[tf cell] setTitle:[NSString stringWithCString:i->name]];
+ [tf sizeToFit];
+ rect = [tf frame];
+ if (thish < rect.size.height + 1) thish = rect.size.height + 1;
+ if (leftw < rect.size.width + 1) leftw = rect.size.width + 1;
+ cfg_controls[cfg_ncontrols++] = tf;
+
+ tf = [[NSTextField alloc] initWithFrame:tmprect];
+ [tf setEditable:YES];
+ [tf setSelectable:YES];
+ [tf setBordered:YES];
+ [[tf cell] setTitle:[NSString stringWithCString:i->sval]];
+ [tf sizeToFit];
+ rect = [tf frame];
+ /*
+ * We impose a minimum and maximum width on editable
+ * NSTextFields. If we allow them to size themselves to
+ * the contents of the text within them, then they will
+ * look very silly if that text is only one or two
+ * characters, and equally silly if it's an absolutely
+ * enormous Rectangles or Pattern game ID!
+ */
+ if (rect.size.width < 75) rect.size.width = 75;
+ if (rect.size.width > 400) rect.size.width = 400;
+
+ if (thish < rect.size.height + 1) thish = rect.size.height + 1;
+ if (rightw < rect.size.width + 1) rightw = rect.size.width + 1;
+ cfg_controls[cfg_ncontrols++] = tf;
+ break;
+
+ case C_BOOLEAN:
+ /*
+ * A checkbox is an NSButton with a type of
+ * NSSwitchButton.
+ */
+ b = [[NSButton alloc] initWithFrame:tmprect];
+ [b setBezelStyle:NSRoundedBezelStyle];
+ [b setButtonType:NSSwitchButton];
+ [b setTitle:[NSString stringWithCString:i->name]];
+ [b sizeToFit];
+ [b setState:(i->ival ? NSOnState : NSOffState)];
+ rect = [b frame];
+ if (totalw < rect.size.width + 1) totalw = rect.size.width + 1;
+ if (thish < rect.size.height + 1) thish = rect.size.height + 1;
+ cfg_controls[cfg_ncontrols++] = b;
+ break;
+
+ case C_CHOICES:
+ /*
+ * A pop-up menu control is an NSPopUpButton, which
+ * takes an embedded NSMenu. We also need an
+ * NSTextField to act as a label.
+ */
+
+ tf = [[NSTextField alloc] initWithFrame:tmprect];
+ [tf setEditable:NO];
+ [tf setSelectable:NO];
+ [tf setBordered:NO];
+ [tf setDrawsBackground:NO];
+ [[tf cell] setTitle:[NSString stringWithCString:i->name]];
+ [tf sizeToFit];
+ rect = [tf frame];
+ if (thish < rect.size.height + 1) thish = rect.size.height + 1;
+ if (leftw < rect.size.width + 1) leftw = rect.size.width + 1;
+ cfg_controls[cfg_ncontrols++] = tf;
+
+ pb = [[NSPopUpButton alloc] initWithFrame:tmprect pullsDown:NO];
+ [pb setBezelStyle:NSRoundedBezelStyle];
+ {
+ char c, *p;
+
+ p = i->sval;
+ c = *p++;
+ while (*p) {
+ char *q;
+
+ q = p;
+ while (*p && *p != c) p++;
+
+ [pb addItemWithTitle:[NSString stringWithCString:q
+ length:p-q]];
+
+ if (*p) p++;
+ }
+ }
+ [pb selectItemAtIndex:i->ival];
+ [pb sizeToFit];
+
+ rect = [pb frame];
+ if (rightw < rect.size.width + 1) rightw = rect.size.width + 1;
+ if (thish < rect.size.height + 1) thish = rect.size.height + 1;
+ cfg_controls[cfg_ncontrols++] = pb;
+ break;
+ }
+
+ h += SPACING + thish;
+ }
+
+ if (totalw < leftw + SPACING + rightw)
+ totalw = leftw + SPACING + rightw;
+ if (totalw > leftw + SPACING + rightw) {
+ int excess = totalw - (leftw + SPACING + rightw);
+ int leftexcess = leftw * excess / (leftw + rightw);
+ int rightexcess = excess - leftexcess;
+ leftw += leftexcess;
+ rightw += rightexcess;
+ }
+
+ /*
+ * Now go through the list again, setting the final position
+ * for each control.
+ */
+ k = 0;
+ y = h;
+ for (i = cfg; i->type != C_END; i++) {
+ y -= SPACING;
+ thish = 0;
+ switch (i->type) {
+ case C_STRING:
+ case C_CHOICES:
+ /*
+ * These two are treated identically, since both expect
+ * a control on the left and another on the right.
+ */
+ rect = [cfg_controls[k] frame];
+ if (thish < rect.size.height + 1)
+ thish = rect.size.height + 1;
+ rect = [cfg_controls[k+1] frame];
+ if (thish < rect.size.height + 1)
+ thish = rect.size.height + 1;
+ rect = [cfg_controls[k] frame];
+ rect.origin.y = y - thish/2 - rect.size.height/2;
+ rect.origin.x = SPACING;
+ rect.size.width = leftw;
+ [cfg_controls[k] setFrame:rect];
+ rect = [cfg_controls[k+1] frame];
+ rect.origin.y = y - thish/2 - rect.size.height/2;
+ rect.origin.x = 2 * SPACING + leftw;
+ rect.size.width = rightw;
+ [cfg_controls[k+1] setFrame:rect];
+ k += 2;
+ break;
+
+ case C_BOOLEAN:
+ rect = [cfg_controls[k] frame];
+ if (thish < rect.size.height + 1)
+ thish = rect.size.height + 1;
+ rect.origin.y = y - thish/2 - rect.size.height/2;
+ rect.origin.x = SPACING;
+ rect.size.width = totalw;
+ [cfg_controls[k] setFrame:rect];
+ k++;
+ break;
+ }
+ y -= thish;
+ }
+
+ assert(k == cfg_ncontrols);
+
+ [cancel setFrame:NSMakeRect(SPACING+totalw/4-actw/2, SPACING, actw, acth)];
+ [ok setFrame:NSMakeRect(SPACING+3*totalw/4-actw/2, SPACING, actw, acth)];
+
+ sheet = [[NSWindow alloc]
+ initWithContentRect:NSMakeRect(0,0,totalw + 2*SPACING,h)
+ styleMask:NSTitledWindowMask | NSClosableWindowMask
+ backing:NSBackingStoreBuffered
+ defer:YES];
+
+ [[sheet contentView] addSubview:cancel];
+ [[sheet contentView] addSubview:ok];
+
+ for (k = 0; k < cfg_ncontrols; k++)
+ [[sheet contentView] addSubview:cfg_controls[k]];
+
+ [NSApp beginSheet:sheet modalForWindow:self
+ modalDelegate:nil didEndSelector:nil contextInfo:nil];
+}
+
+- (void)specificGame:(id)sender
+{
+ [self startConfigureSheet:CFG_DESC];
+}
+
+- (void)specificRandomGame:(id)sender
+{
+ [self startConfigureSheet:CFG_SEED];
+}
+
+- (void)customGameType:(id)sender
+{
+ [self startConfigureSheet:CFG_SETTINGS];
+}
+
+- (void)sheetEndWithStatus:(BOOL)update
+{
+ assert(sheet != NULL);
+ [NSApp endSheet:sheet];
+ [sheet orderOut:self];
+ sheet = NULL;
+ if (update) {
+ int k;
+ config_item *i;
+ char *error;
+
+ k = 0;
+ for (i = cfg; i->type != C_END; i++) {
+ switch (i->type) {
+ case C_STRING:
+ sfree(i->sval);
+ i->sval = dupstr([[[(id)cfg_controls[k+1] cell]
+ title] cString]);
+ k += 2;
+ break;
+ case C_BOOLEAN:
+ i->ival = [(id)cfg_controls[k] state] == NSOnState;
+ k++;
+ break;
+ case C_CHOICES:
+ i->ival = [(id)cfg_controls[k+1] indexOfSelectedItem];
+ k += 2;
+ break;
+ }
+ }
+
+ error = midend_set_config(me, cfg_which, cfg);
+ if (error) {
+ NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+ [alert addButtonWithTitle:@"Bah"];
+ [alert setInformativeText:[NSString stringWithCString:error]];
+ [alert beginSheetModalForWindow:self modalDelegate:nil
+ didEndSelector:nil contextInfo:nil];
+ } else {
+ midend_new_game(me);
+ [self resizeForNewGameParams];
+ }
+ }
+ sfree(cfg_controls);
+ cfg_controls = NULL;
+}
+- (void)sheetOKButton:(id)sender
+{
+ [self sheetEndWithStatus:YES];
+}
+- (void)sheetCancelButton:(id)sender
+{
+ [self sheetEndWithStatus:NO];
+}
+
+- (void)setStatusLine:(char *)text
+{
+ char *rewritten = midend_rewrite_statusbar(me, text);
+ [[status cell] setTitle:[NSString stringWithCString:rewritten]];
+ sfree(rewritten);
+}
+