/*
 * osxctrls.m: OS X implementation of the dialog.h interface.
 */

#import <Cocoa/Cocoa.h>
#include "putty.h"
#include "dialog.h"
#include "osxclass.h"
#include "tree234.h"

/*
 * Still to be implemented:
 * 
 *  - file selectors (NSOpenPanel / NSSavePanel)
 * 
 *  - font selectors
 *  - colour selectors
 *     * both of these have a conceptual oddity in Cocoa that
 * 	 you're only supposed to have one per application. But I
 * 	 currently expect to be able to have multiple PuTTY config
 * 	 boxes on screen at once; what happens if you trigger the
 * 	 font selector in each one at the same time?
 *     * if it comes to that, the _font_ selector can probably be
 * 	 managed by other means: nobody is forcing me to implement
 * 	 a font selector using a `Change...' button. The portable
 * 	 dialog interface gives me the flexibility to do this how I
 * 	 want.
 *     * The colour selector interface, in its present form, is
 * 	 more interesting and _if_ a radical change of plan is
 * 	 required then it may stretch across the interface into the
 * 	 portable side.
 *     * Before I do anything rash I should start by looking at the
 * 	 Mac Classic port and see how it's done there, on the basis
 * 	 that Apple seem reasonably unlikely to have invented this
 * 	 crazy restriction specifically for OS X.
 * 
 *  - focus management
 *     * I tried using makeFirstResponder to give keyboard focus,
 * 	 but it appeared not to work. Try again, and work out how
 * 	 it should be done.
 *     * also look into tab order. Currently pressing Tab suggests
 * 	 that only edit boxes and list boxes can get the keyboard
 * 	 focus, and that buttons (in all their forms) are unable to
 * 	 be driven by the keyboard. Find out for sure.
 * 
 *  - dlg_error_msg
 *     * this may run into the usual aggro with modal dialog boxes.
 */

/*
 * For Cocoa control layout, I need a two-stage process. In stage
 * one, I allocate all the controls and measure their natural
 * sizes, which allows me to compute the _minimum_ width and height
 * of a given section of dialog. Then, in stage two, I lay out the
 * dialog box as a whole, decide how much each section of the box
 * needs to receive, and assign it its final size.
 */

/*
 * As yet unsolved issues [FIXME]:
 * 
 *  - Sometimes the height returned from create_ctrls and the
 *    height returned from place_ctrls differ. Find out why. It may
 *    be harmless (e.g. results of NSTextView being odd), but I
 *    want to know.
 * 
 *  - NSTextViews are indented a bit. It'd be nice to put their
 *    left margin at the same place as everything else's.
 * 
 *  - I don't yet know whether we even _can_ support tab order or
 *    keyboard shortcuts. If we can't, then fair enough, we can't.
 *    But if we can, we should.
 * 
 *  - I would _really_ like to know of a better way to correct
 *    NSButton's stupid size estimates than by subclassing it and
 *    overriding sizeToFit with hard-wired sensible values!
 * 
 *  - Speaking of stupid size estimates, the amount by which I'm
 *    adjusting a titled NSBox (currently equal to the point size
 *    of its title font) looks as if it isn't _quite_ enough.
 *    Figure out what the real amount should be and use it.
 * 
 *  - I don't understand why there's always a scrollbar displayed
 *    in each list box. I thought I told it to autohide scrollers?
 * 
 *  - Why do I have to fudge list box heights by adding one? (Might
 *    it be to do with the missing header view?)
 */

/*
 * Subclass of NSButton which corrects the fact that the normal
 * one's sizeToFit method persistently returns 32 as its height,
 * which is simply a lie. I have yet to work out a better
 * alternative than hard-coding the real heights.
 */
@interface MyButton : NSButton
{
    int minht;
}
@end
@implementation MyButton
- (id)initWithFrame:(NSRect)r
{
    self = [super initWithFrame:r];
    minht = 25;
    return self;
}
- (void)setButtonType:(NSButtonType)t
{
    if (t == NSRadioButton || t == NSSwitchButton)
	minht = 18;
    else
	minht = 25;
    [super setButtonType:t];
}
- (void)sizeToFit
{
    NSRect r;
    [super sizeToFit];
    r = [self frame];
    r.size.height = minht;
    [self setFrame:r];
}
@end

/*
 * Class used as the data source for NSTableViews.
 */
@interface MyTableSource : NSObject
{
    tree234 *tree;
}
- (id)init;
- (void)add:(const char *)str withId:(int)id;
- (int)getid:(int)index;
- (void)swap:(int)index1 with:(int)index2;
- (void)removestr:(int)index;
- (void)clear;
@end
@implementation MyTableSource
- (id)init
{
    self = [super init];
    tree = newtree234(NULL);
    return self;
}
- (void)dealloc
{
    char *p;
    while ((p = delpos234(tree, 0)) != NULL)
	sfree(p);
    freetree234(tree);
    [super dealloc];
}
- (void)add:(const char *)str withId:(int)id
{
    addpos234(tree, dupprintf("%d\t%s", id, str), count234(tree));
}
- (int)getid:(int)index
{
    char *p = index234(tree, index);
    return atoi(p);
}
- (void)removestr:(int)index
{
    char *p = delpos234(tree, index);
    sfree(p);
}
- (void)swap:(int)index1 with:(int)index2
{
    char *p1, *p2;

    if (index1 > index2) {
	int t = index1; index1 = index2; index2 = t;
    }

    /* delete later one first so it doesn't affect index of earlier one */
    p2 = delpos234(tree, index2);
    p1 = delpos234(tree, index1);

    /* now insert earlier one before later one for the inverse reason */
    addpos234(tree, p2, index1);
    addpos234(tree, p1, index2);
}
- (void)clear
{
    char *p;
    while ((p = delpos234(tree, 0)) != NULL)
	sfree(p);
}
- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
    return count234(tree);
}
- (id)tableView:(NSTableView *)aTableView
    objectValueForTableColumn:(NSTableColumn *)aTableColumn
    row:(int)rowIndex
{
    int j = [[aTableColumn identifier] intValue];
    char *p = index234(tree, rowIndex);

    while (j >= 0) {
	p += strcspn(p, "\t");
	if (*p) p++;
	j--;
    }

    return [NSString stringWithCString:p length:strcspn(p, "\t")];
}
@end

/*
 * Object to receive messages from various control classes.
 */
@class Receiver;

struct fe_dlg {
    NSWindow *window;
    NSObject *target;
    SEL action;
    tree234 *byctrl;
    tree234 *bywidget;
    tree234 *boxes;
    void *data;			       /* passed to portable side */
    Receiver *rec;
};

@interface Receiver : NSObject
{
    struct fe_dlg *d;
}
- (id)initWithStruct:(struct fe_dlg *)aStruct;
@end

struct fe_ctrl {
    union control *ctrl;
    NSButton *button, *button2;
    NSTextField *label, *editbox;
    NSComboBox *combobox;
    NSButton **radiobuttons;
    NSTextView *textview;
    NSPopUpButton *popupbutton;
    NSTableView *tableview;
    NSScrollView *scrollview;
    int nradiobuttons;
    void *privdata;
    int privdata_needs_free;
};

static int fe_ctrl_cmp_by_ctrl(void *av, void *bv)
{
    struct fe_ctrl *a = (struct fe_ctrl *)av;
    struct fe_ctrl *b = (struct fe_ctrl *)bv;

    if (a->ctrl < b->ctrl)
	return -1;
    if (a->ctrl > b->ctrl)
	return +1;
    return 0;
}

static int fe_ctrl_find_by_ctrl(void *av, void *bv)
{
    union control *a = (union control *)av;
    struct fe_ctrl *b = (struct fe_ctrl *)bv;

    if (a < b->ctrl)
	return -1;
    if (a > b->ctrl)
	return +1;
    return 0;
}

struct fe_box {
    struct controlset *s;
    id box;
};

static int fe_boxcmp(void *av, void *bv)
{
    struct fe_box *a = (struct fe_box *)av;
    struct fe_box *b = (struct fe_box *)bv;

    if (a->s < b->s)
	return -1;
    if (a->s > b->s)
	return +1;
    return 0;
}

static int fe_boxfind(void *av, void *bv)
{
    struct controlset *a = (struct controlset *)av;
    struct fe_box *b = (struct fe_box *)bv;

    if (a < b->s)
	return -1;
    if (a > b->s)
	return +1;
    return 0;
}

struct fe_backwards {		       /* map Cocoa widgets back to fe_ctrls */
    id widget;
    struct fe_ctrl *c;
};

static int fe_backwards_cmp_by_widget(void *av, void *bv)
{
    struct fe_backwards *a = (struct fe_backwards *)av;
    struct fe_backwards *b = (struct fe_backwards *)bv;

    if (a->widget < b->widget)
	return -1;
    if (a->widget > b->widget)
	return +1;
    return 0;
}

static int fe_backwards_find_by_widget(void *av, void *bv)
{
    id a = (id)av;
    struct fe_backwards *b = (struct fe_backwards *)bv;

    if (a < b->widget)
	return -1;
    if (a > b->widget)
	return +1;
    return 0;
}

static struct fe_ctrl *fe_ctrl_new(union control *ctrl)
{
    struct fe_ctrl *c;

    c = snew(struct fe_ctrl);
    c->ctrl = ctrl;

    c->button = c->button2 = nil;
    c->label = nil;
    c->editbox = nil;
    c->combobox = nil;
    c->textview = nil;
    c->popupbutton = nil;
    c->tableview = nil;
    c->scrollview = nil;
    c->radiobuttons = NULL;
    c->nradiobuttons = 0;
    c->privdata = NULL;
    c->privdata_needs_free = FALSE;

    return c;
}

static void fe_ctrl_free(struct fe_ctrl *c)
{
    if (c->privdata_needs_free)
	sfree(c->privdata);
    sfree(c->radiobuttons);
    sfree(c);
}

static struct fe_ctrl *fe_ctrl_byctrl(struct fe_dlg *d, union control *ctrl)
{
    return find234(d->byctrl, ctrl, fe_ctrl_find_by_ctrl);
}

static void add_box(struct fe_dlg *d, struct controlset *s, id box)
{
    struct fe_box *b = snew(struct fe_box);
    b->box = box;
    b->s = s;
    add234(d->boxes, b);
}

static id find_box(struct fe_dlg *d, struct controlset *s)
{
    struct fe_box *b = find234(d->boxes, s, fe_boxfind);
    return b ? b->box : NULL;
}

static void add_widget(struct fe_dlg *d, struct fe_ctrl *c, id widget)
{
    struct fe_backwards *b = snew(struct fe_backwards);
    b->widget = widget;
    b->c = c;
    add234(d->bywidget, b);
}

static struct fe_ctrl *find_widget(struct fe_dlg *d, id widget)
{
    struct fe_backwards *b = find234(d->bywidget, widget,
				     fe_backwards_find_by_widget);
    return b ? b->c : NULL;
}

void *fe_dlg_init(void *data, NSWindow *window, NSObject *target, SEL action)
{
    struct fe_dlg *d;

    d = snew(struct fe_dlg);
    d->window = window;
    d->target = target;
    d->action = action;
    d->byctrl = newtree234(fe_ctrl_cmp_by_ctrl);
    d->bywidget = newtree234(fe_backwards_cmp_by_widget);
    d->boxes = newtree234(fe_boxcmp);
    d->data = data;
    d->rec = [[Receiver alloc] initWithStruct:d];

    return d;
}

void fe_dlg_free(void *dv)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c;
    struct fe_box *b;

    while ( (c = delpos234(d->byctrl, 0)) != NULL )
	fe_ctrl_free(c);
    freetree234(d->byctrl);

    while ( (c = delpos234(d->bywidget, 0)) != NULL )
	sfree(c);
    freetree234(d->bywidget);

    while ( (b = delpos234(d->boxes, 0)) != NULL )
	sfree(b);
    freetree234(d->boxes);

    [d->rec release];

    sfree(d);
}

@implementation Receiver
- (id)initWithStruct:(struct fe_dlg *)aStruct
{
    self = [super init];
    d = aStruct;
    return self;
}
- (void)buttonPushed:(id)sender
{
    struct fe_ctrl *c = find_widget(d, sender);

    assert(c && c->ctrl->generic.type == CTRL_BUTTON);
    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
}
- (void)checkboxChanged:(id)sender
{
    struct fe_ctrl *c = find_widget(d, sender);

    assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
}
- (void)radioChanged:(id)sender
{
    struct fe_ctrl *c = find_widget(d, sender);
    int j;

    assert(c && c->radiobuttons);
    for (j = 0; j < c->nradiobuttons; j++)
	if (sender != c->radiobuttons[j])
	    [c->radiobuttons[j] setState:NSOffState];
    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
}
- (void)popupMenuSelected:(id)sender
{
    struct fe_ctrl *c = find_widget(d, sender);
    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
}
- (void)controlTextDidChange:(NSNotification *)notification
{
    id widget = [notification object];
    struct fe_ctrl *c = find_widget(d, widget);
    assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
}
- (void)controlTextDidEndEditing:(NSNotification *)notification
{
    id widget = [notification object];
    struct fe_ctrl *c = find_widget(d, widget);
    assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_REFRESH);
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
    id widget = [notification object];
    struct fe_ctrl *c = find_widget(d, widget);
    assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_SELCHANGE);
}
- (BOOL)tableView:(NSTableView *)aTableView
    shouldEditTableColumn:(NSTableColumn *)aTableColumn
    row:(int)rowIndex
{
    return NO;			       /* no editing permitted */
}
- (void)listDoubleClicked:(id)sender
{
    struct fe_ctrl *c = find_widget(d, sender);
    assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
}
- (void)dragListButton:(id)sender
{
    struct fe_ctrl *c = find_widget(d, sender);
    int direction, row, nrows;
    assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
	   c->ctrl->listbox.draglist);

    if (sender == c->button)
	direction = -1;		       /* up */
    else
	direction = +1;		       /* down */

    row = [c->tableview selectedRow];
    nrows = [c->tableview numberOfRows];

    if (row + direction < 0 || row + direction >= nrows) {
	NSBeep();
	return;
    }

    [[c->tableview dataSource] swap:row with:row+direction];
    [c->tableview reloadData];
    [c->tableview selectRow:row+direction byExtendingSelection:NO];

    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
}
@end

void create_ctrls(void *dv, NSView *parent, struct controlset *s,
		  int *minw, int *minh)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    int ccw[100];		       /* cumulative column widths */
    int cypos[100];
    int ncols;
    int wmin = 0, hmin = 0;
    int i, j, cw, ch;
    NSRect rect;
    NSFont *textviewfont = nil;
    int boxh = 0, boxw = 0;

    if (!s->boxname && s->boxtitle) {
        /* This controlset is a panel title. */

	NSTextField *tf;

	tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
	[tf setEditable:NO];
	[tf setSelectable:NO];
	[tf setBordered:NO];
	[tf setDrawsBackground:NO];
	[tf setStringValue:[NSString stringWithCString:s->boxtitle]];
	[tf sizeToFit];
	rect = [tf frame];
	[parent addSubview:tf];

	/*
	 * I'm going to store this NSTextField in the boxes tree,
	 * because I really can't face having a special tree234
	 * mapping controlsets to panel titles.
	 */
	add_box(d, s, tf);

	*minw = rect.size.width;
	*minh = rect.size.height;

	return;
    }

    if (*s->boxname) {
	/*
	 * Create an NSBox to contain this subset of controls.
	 */
	NSBox *box;
	NSRect tmprect;

	box = [[NSBox alloc] initWithFrame:NSMakeRect(0,0,1,1)];
	if (s->boxtitle)
	    [box setTitle:[NSString stringWithCString:s->boxtitle]];
	else
	    [box setTitlePosition:NSNoTitle];
	add_box(d, s, box);
	tmprect = [box frame];
	[box setContentViewMargins:NSMakeSize(20,20)];
	[box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
	rect = [box frame];
	[box setFrame:tmprect];
	boxh = (int)(rect.size.height - 100);
	boxw = (int)(rect.size.width - 100);
	[parent addSubview:box];

	if (s->boxtitle)
	    boxh += [[box titleFont] pointSize];

	/*
	 * All subsequent controls will be placed within this box.
	 */
	parent = box;
    }

    ncols = 1;
    ccw[0] = 0;
    ccw[1] = 100;
    cypos[0] = 0;

    /*
     * Now iterate through the controls themselves, create them,
     * and add their width and height to the overall width/height
     * calculation.
     */
    for (i = 0; i < s->ncontrols; i++) {
	union control *ctrl = s->ctrls[i];
	struct fe_ctrl *c;
	int colstart = COLUMN_START(ctrl->generic.column);
	int colspan = COLUMN_SPAN(ctrl->generic.column);
	int colend = colstart + colspan;
	int ytop, wthis;

        switch (ctrl->generic.type) {
          case CTRL_COLUMNS:
	    for (j = 1; j < ncols; j++)
		if (cypos[0] < cypos[j])
		    cypos[0] = cypos[j];

	    assert(ctrl->columns.ncols < lenof(ccw));

	    ccw[0] = 0;
	    for (j = 0; j < ctrl->columns.ncols; j++) {
		ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
				     ctrl->columns.percentages[j] : 100);
		cypos[j] = cypos[0];
	    }

	    ncols = ctrl->columns.ncols;

            continue;                  /* no actual control created */
          case CTRL_TABDELAY:
	    /*
	     * I'm currently uncertain that we can implement tab
	     * order in OS X.
	     */
            continue;                  /* no actual control created */
	}

	c = fe_ctrl_new(ctrl);
	add234(d->byctrl, c);

	cw = ch = 0;

        switch (ctrl->generic.type) {
          case CTRL_BUTTON:
          case CTRL_CHECKBOX:
	    {
		NSButton *b;

		b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
		[b setBezelStyle:NSRoundedBezelStyle];
		if (ctrl->generic.type == CTRL_CHECKBOX)
		    [b setButtonType:NSSwitchButton];
		[b setTitle:[NSString stringWithCString:ctrl->generic.label]];
		if (ctrl->button.isdefault)
		    [b setKeyEquivalent:@"\r"];
		else if (ctrl->button.iscancel)
		    [b setKeyEquivalent:@"\033"];
		[b sizeToFit];
		rect = [b frame];

		[parent addSubview:b];

		[b setTarget:d->rec];
		if (ctrl->generic.type == CTRL_CHECKBOX)
		    [b setAction:@selector(checkboxChanged:)];
		else
		    [b setAction:@selector(buttonPushed:)];
		add_widget(d, c, b);

		c->button = b;

		cw = rect.size.width;
		ch = rect.size.height;
	    }
	    break;
	  case CTRL_EDITBOX:
	    {
		int editp = ctrl->editbox.percentwidth;
		int labelp = editp == 100 ? 100 : 100 - editp;
		NSTextField *tf;
		NSComboBox *cb;

		tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
		[tf setEditable:NO];
		[tf setSelectable:NO];
		[tf setBordered:NO];
		[tf setDrawsBackground:NO];
		[tf setStringValue:[NSString
				    stringWithCString:ctrl->generic.label]];
		[tf sizeToFit];
		rect = [tf frame];
		[parent addSubview:tf];
		c->label = tf;

		cw = rect.size.width * 100 / labelp;
		ch = rect.size.height;

		if (ctrl->editbox.has_list) {
		    cb = [[NSComboBox alloc]
			  initWithFrame:NSMakeRect(0,0,1,1)];
		    [cb setStringValue:@"x"];
		    [cb sizeToFit];
		    rect = [cb frame];
		    [parent addSubview:cb];
		    c->combobox = cb;
		} else {
		    if (ctrl->editbox.password)
			tf = [NSSecureTextField alloc];
		    else
			tf = [NSTextField alloc];

		    tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
		    [tf setEditable:YES];
		    [tf setSelectable:YES];
		    [tf setBordered:YES];
		    [tf setStringValue:@"x"];
		    [tf sizeToFit];
		    rect = [tf frame];
		    [parent addSubview:tf];
		    c->editbox = tf;

		    [tf setDelegate:d->rec];
		    add_widget(d, c, tf);
		}

		if (editp == 100) {
		    /* the edit box and its label are vertically separated */
		    ch += VSPACING + rect.size.height;
		} else {
		    /* the edit box and its label are horizontally separated */
		    if (ch < rect.size.height)
			ch = rect.size.height;
		}

		if (cw < rect.size.width * 100 / editp)
		    cw = rect.size.width * 100 / editp;
	    }
	    break;
	  case CTRL_TEXT:
	    {
		NSTextView *tv;
		int testwid;

		if (!textviewfont) {
		    NSTextField *tf;
		    tf = [[NSTextField alloc] init];
		    textviewfont = [tf font];
		    [tf release];
		}

		testwid = (ccw[colend] - ccw[colstart]) * 3;

		tv = [[NSTextView alloc]
		      initWithFrame:NSMakeRect(0,0,testwid,1)];
		[tv setEditable:NO];
		[tv setSelectable:NO];
		//[tv setBordered:NO];
		[tv setDrawsBackground:NO];
		[tv setFont:textviewfont];
		[tv setString:
		 [NSString stringWithCString:ctrl->generic.label]];
		rect = [tv frame];
		[tv sizeToFit];
		[parent addSubview:tv];
		c->textview = tv;

		cw = rect.size.width;
		ch = rect.size.height;
	    }
	    break;
	  case CTRL_RADIO:
	    {
		NSTextField *tf;
		int j;

		if (ctrl->generic.label) {
		    tf = [[NSTextField alloc]
			  initWithFrame:NSMakeRect(0,0,1,1)];
		    [tf setEditable:NO];
		    [tf setSelectable:NO];
		    [tf setBordered:NO];
		    [tf setDrawsBackground:NO];
		    [tf setStringValue:
		     [NSString stringWithCString:ctrl->generic.label]];
		    [tf sizeToFit];
		    rect = [tf frame];
		    [parent addSubview:tf];
		    c->label = tf;

		    cw = rect.size.width;
		    ch = rect.size.height;
		} else {
		    cw = 0;
		    ch = -VSPACING;    /* compensate for next advance */
		}

		c->nradiobuttons = ctrl->radio.nbuttons;
		c->radiobuttons = snewn(ctrl->radio.nbuttons, NSButton *);

		for (j = 0; j < ctrl->radio.nbuttons; j++) {
		    NSButton *b;
		    int ncols;

		    b = [[MyButton alloc] initWithFrame:NSMakeRect(0,0,1,1)];
		    [b setBezelStyle:NSRoundedBezelStyle];
		    [b setButtonType:NSRadioButton];
		    [b setTitle:[NSString
				 stringWithCString:ctrl->radio.buttons[j]]];
		    [b sizeToFit];
		    rect = [b frame];
		    [parent addSubview:b];

		    c->radiobuttons[j] = b;

		    [b setTarget:d->rec];
		    [b setAction:@selector(radioChanged:)];
		    add_widget(d, c, b);

		    /*
		     * Add to the height every time we place a
		     * button in column 0.
		     */
		    if (j % ctrl->radio.ncolumns == 0) {
			ch += rect.size.height + VSPACING;
		    }

		    /*
		     * Add to the width by working out how many
		     * columns this button spans.
		     */
		    if (j == ctrl->radio.nbuttons - 1)
			ncols = (ctrl->radio.ncolumns -
				 (j % ctrl->radio.ncolumns));
		    else
			ncols = 1;

		    if (cw < rect.size.width * ctrl->radio.ncolumns / ncols)
			cw = rect.size.width * ctrl->radio.ncolumns / ncols;
		}
	    }
	    break;
	  case CTRL_FILESELECT:
	  case CTRL_FONTSELECT:
	    {
		NSTextField *tf;
		NSButton *b;
		int kh;

		tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
		[tf setEditable:NO];
		[tf setSelectable:NO];
		[tf setBordered:NO];
		[tf setDrawsBackground:NO];
		[tf setStringValue:[NSString
				    stringWithCString:ctrl->generic.label]];
		[tf sizeToFit];
		rect = [tf frame];
		[parent addSubview:tf];
		c->label = tf;

		cw = rect.size.width;
		ch = rect.size.height;

		tf = [NSTextField alloc];
		tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
		if (ctrl->generic.type == CTRL_FILESELECT) {
		    [tf setEditable:YES];
		    [tf setSelectable:YES];
		    [tf setBordered:YES];
		} else {
		    [tf setEditable:NO];
		    [tf setSelectable:NO];
		    [tf setBordered:NO];
		    [tf setDrawsBackground:NO];
		}
		[tf setStringValue:@"x"];
		[tf sizeToFit];
		rect = [tf frame];
		[parent addSubview:tf];
		c->editbox = tf;

		kh = rect.size.height;
		if (cw < rect.size.width * 4 / 3)
		    cw = rect.size.width * 4 / 3;

		b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
		[b setBezelStyle:NSRoundedBezelStyle];
		if (ctrl->generic.type == CTRL_FILESELECT)
		    [b setTitle:@"Browse..."];
		else
		    [b setTitle:@"Change..."];
		// [b setKeyEquivalent:somethingorother];
		// [b setTarget:somethingorother];
		// [b setAction:somethingorother];
		[b sizeToFit];
		rect = [b frame];
		[parent addSubview:b];

		c->button = b;

		if (kh < rect.size.height)
		    kh = rect.size.height;
		ch += VSPACING + kh;
		if (cw < rect.size.width * 4)
		    cw = rect.size.width * 4;
	    }
	    break;
	  case CTRL_LISTBOX:
	    {
		int listp = ctrl->listbox.percentwidth;
		int labelp = listp == 100 ? 100 : 100 - listp;
		NSTextField *tf;
		NSPopUpButton *pb;
		NSTableView *tv;
		NSScrollView *sv;

		if (ctrl->generic.label) {
		    tf = [[NSTextField alloc]
			  initWithFrame:NSMakeRect(0,0,1,1)];
		    [tf setEditable:NO];
		    [tf setSelectable:NO];
		    [tf setBordered:NO];
		    [tf setDrawsBackground:NO];
		    [tf setStringValue:
		     [NSString stringWithCString:ctrl->generic.label]];
		    [tf sizeToFit];
		    rect = [tf frame];
		    [parent addSubview:tf];
		    c->label = tf;

		    cw = rect.size.width;
		    ch = rect.size.height;
		} else {
		    cw = 0;
		    ch = -VSPACING;    /* compensate for next advance */
		}

		if (ctrl->listbox.height == 0) {
		    pb = [[NSPopUpButton alloc]
			  initWithFrame:NSMakeRect(0,0,1,1)];
		    [pb sizeToFit];
		    rect = [pb frame];
		    [parent addSubview:pb];
		    c->popupbutton = pb;

		    [pb setTarget:d->rec];
		    [pb setAction:@selector(popupMenuSelected:)];
		    add_widget(d, c, pb);
		} else {
		    assert(listp == 100);
		    if (ctrl->listbox.draglist) {
			int bi;

			listp = 75;

			for (bi = 0; bi < 2; bi++) {
			    NSButton *b;
			    b = [[MyButton alloc]
				 initWithFrame:NSMakeRect(0, 0, 1, 1)];
			    [b setBezelStyle:NSRoundedBezelStyle];
			    if (bi == 0)
				[b setTitle:@"Up"];
			    else
				[b setTitle:@"Down"];
			    [b sizeToFit];
			    rect = [b frame];
			    [parent addSubview:b];

			    if (bi == 0)
				c->button = b;
			    else
				c->button2 = b;

			    [b setTarget:d->rec];
			    [b setAction:@selector(dragListButton:)];
			    add_widget(d, c, b);

			    if (cw < rect.size.width * 4)
				cw = rect.size.width * 4;
			}
		    }

		    sv = [[NSScrollView alloc] initWithFrame:
			  NSMakeRect(20,20,10,10)];
		    [sv setBorderType:NSLineBorder];
		    tv = [[NSTableView alloc] initWithFrame:[sv frame]];
		    [[tv headerView] setFrame:NSMakeRect(0,0,0,0)];
		    [sv setDocumentView:tv];
		    [parent addSubview:sv];
		    [sv setHasVerticalScroller:YES];
		    [sv setAutohidesScrollers:YES];
		    [tv setAllowsColumnReordering:NO];
		    [tv setAllowsColumnResizing:NO];
		    [tv setAllowsMultipleSelection:ctrl->listbox.multisel];
		    [tv setAllowsEmptySelection:YES];
		    [tv setAllowsColumnSelection:YES];
		    [tv setDataSource:[[MyTableSource alloc] init]];
		    rect = [tv frame];
		    /*
		     * For some reason this consistently comes out
		     * one short. Add one.
		     */
		    rect.size.height = (ctrl->listbox.height+1)*[tv rowHeight];
		    [sv setFrame:rect];
		    c->tableview = tv;
		    c->scrollview = sv;

		    [tv setDelegate:d->rec];
		    [tv setTarget:d->rec];
		    [tv setDoubleAction:@selector(listDoubleClicked:)];
		    add_widget(d, c, tv);
		}

		if (c->tableview) {
		    int ncols, *percentages;
		    int hundred = 100;

		    if (ctrl->listbox.ncols) {
			ncols = ctrl->listbox.ncols;
			percentages = ctrl->listbox.percentages;
		    } else {
			ncols = 1;
			percentages = &hundred;
		    }

		    for (j = 0; j < ncols; j++) {
			NSTableColumn *col;

			col = [[NSTableColumn alloc] initWithIdentifier:
			       [NSNumber numberWithInt:j]];
			[c->tableview addTableColumn:col];
		    }
		}

		if (labelp == 100) {
		    /* the list and its label are vertically separated */
		    ch += VSPACING + rect.size.height;
		} else {
		    /* the list and its label are horizontally separated */
		    if (ch < rect.size.height)
			ch = rect.size.height;
		}

		if (cw < rect.size.width * 100 / listp)
		    cw = rect.size.width * 100 / listp;
	    }
	    break;
	}

	/*
	 * Update the width and height data for the control we've
	 * just created.
	 */
	ytop = 0;

	for (j = colstart; j < colend; j++) {
	    if (ytop < cypos[j])
		ytop = cypos[j];
	}

	for (j = colstart; j < colend; j++)
	    cypos[j] = ytop + ch + VSPACING;

	if (hmin < ytop + ch)
	    hmin = ytop + ch;

	wthis = (cw + HSPACING) * 100 / (ccw[colend] - ccw[colstart]);
	wthis -= HSPACING;

	if (wmin < wthis)
	    wmin = wthis;
    }

    if (*s->boxname) {
	/*
	 * Add a bit to the width and height for the box.
	 */
	wmin += boxw;
	hmin += boxh;
    }

    //printf("For controlset %s/%s, returning w=%d h=%d\n",
    //       s->pathname, s->boxname, wmin, hmin);
    *minw = wmin;
    *minh = hmin;
}

int place_ctrls(void *dv, struct controlset *s, int leftx, int topy,
		int width)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    int ccw[100];		       /* cumulative column widths */
    int cypos[100];
    int ncols;
    int i, j, ret;
    int boxh = 0, boxw = 0;

    if (!s->boxname && s->boxtitle) {
        /* Size and place the panel title. */

	NSTextField *tf = find_box(d, s);
	NSRect rect;

	rect = [tf frame];
	[tf setFrame:NSMakeRect(leftx, topy-rect.size.height,
				width, rect.size.height)];
	return rect.size.height;
    }

    if (*s->boxname) {
	NSRect rect, tmprect;
	NSBox *box = find_box(d, s);

	assert(box != NULL);
	tmprect = [box frame];
	[box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
	rect = [box frame];
	[box setFrame:tmprect];
	boxw = rect.size.width - 100;
	boxh = rect.size.height - 100;
	if (s->boxtitle)
	    boxh += [[box titleFont] pointSize];
	topy -= boxh;
	width -= boxw;
    }

    ncols = 1;
    ccw[0] = 0;
    ccw[1] = 100;
    cypos[0] = topy;
    ret = 0;

    /*
     * Now iterate through the controls themselves, placing them
     * appropriately.
     */
    for (i = 0; i < s->ncontrols; i++) {
	union control *ctrl = s->ctrls[i];
	struct fe_ctrl *c;
	int colstart = COLUMN_START(ctrl->generic.column);
	int colspan = COLUMN_SPAN(ctrl->generic.column);
	int colend = colstart + colspan;
	int xthis, ythis, wthis, ch;
	NSRect rect;

        switch (ctrl->generic.type) {
          case CTRL_COLUMNS:
	    for (j = 1; j < ncols; j++)
		if (cypos[0] > cypos[j])
		    cypos[0] = cypos[j];

	    assert(ctrl->columns.ncols < lenof(ccw));

	    ccw[0] = 0;
	    for (j = 0; j < ctrl->columns.ncols; j++) {
		ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
				     ctrl->columns.percentages[j] : 100);
		cypos[j] = cypos[0];
	    }

	    ncols = ctrl->columns.ncols;

            continue;                  /* no actual control created */
          case CTRL_TABDELAY:
            continue;                  /* nothing to do here, move along */
	}

	c = fe_ctrl_byctrl(d, ctrl);

	ch = 0;
	ythis = topy;

	for (j = colstart; j < colend; j++) {
	    if (ythis > cypos[j])
		ythis = cypos[j];
	}

	xthis = (width + HSPACING) * ccw[colstart] / 100;
	wthis = (width + HSPACING) * ccw[colend] / 100 - HSPACING - xthis;
	xthis += leftx;

        switch (ctrl->generic.type) {
          case CTRL_BUTTON:
	  case CTRL_CHECKBOX:
	    rect = [c->button frame];
	    [c->button setFrame:NSMakeRect(xthis,ythis-rect.size.height,wthis,
					   rect.size.height)];
	    ch = rect.size.height;
	    break;
	  case CTRL_EDITBOX:
	    {
		int editp = ctrl->editbox.percentwidth;
		int labelp = editp == 100 ? 100 : 100 - editp;
		int lheight, theight, rheight, ynext, editw;
		NSControl *edit = (c->editbox ? c->editbox : c->combobox);

	 	rect = [c->label frame];
		lheight = rect.size.height;
		rect = [edit frame];
		theight = rect.size.height;

		if (editp == 100)
		    rheight = lheight;
		else
		    rheight = (lheight < theight ? theight : lheight);

		[c->label setFrame:
		 NSMakeRect(xthis, ythis-(rheight+lheight)/2,
			    (wthis + HSPACING) * labelp / 100 - HSPACING,
			    lheight)];
		if (editp == 100) {
		    ynext = ythis - rheight - VSPACING;
		    rheight = theight;
		} else {
		    ynext = ythis;
		}

		editw = (wthis + HSPACING) * editp / 100 - HSPACING;

		[edit setFrame:
		 NSMakeRect(xthis+wthis-editw, ynext-(rheight+theight)/2,
			    editw, theight)];

		ch = (ythis - ynext) + theight;
	    }
	    break;
          case CTRL_TEXT:
	    [c->textview setFrame:NSMakeRect(xthis, 0, wthis, 1)];
	    [c->textview sizeToFit];
	    rect = [c->textview frame];
	    [c->textview setFrame:NSMakeRect(xthis, ythis-rect.size.height,
					     wthis, rect.size.height)];
	    ch = rect.size.height;
	    break;
	  case CTRL_RADIO:
	    {
		int j, ynext;

		if (c->label) {
		    rect = [c->label frame];
		    [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
						  wthis,rect.size.height)];
		    ynext = ythis - rect.size.height - VSPACING;
		} else
		    ynext = ythis;

		for (j = 0; j < ctrl->radio.nbuttons; j++) {
		    int col = j % ctrl->radio.ncolumns;
		    int ncols;
		    int lx,rx;

		    if (j == ctrl->radio.nbuttons - 1)
			ncols = ctrl->radio.ncolumns - col;
		    else
			ncols = 1;

		    lx = (wthis + HSPACING) * col / ctrl->radio.ncolumns;
		    rx = ((wthis + HSPACING) *
			  (col+ncols) / ctrl->radio.ncolumns) - HSPACING;

		    /*
		     * Set the frame size.
		     */
		    rect = [c->radiobuttons[j] frame];
		    [c->radiobuttons[j] setFrame:
		     NSMakeRect(lx+xthis, ynext-rect.size.height,
				rx-lx, rect.size.height)];

		    /*
		     * Advance to next line if we're in the last
		     * column.
		     */
		    if (col + ncols == ctrl->radio.ncolumns)
			ynext -= rect.size.height + VSPACING;
		}
		ch = (ythis - ynext) - VSPACING;
	    }
	    break;
	  case CTRL_FILESELECT:
	  case CTRL_FONTSELECT:
	    {
		int ynext, eh, bh, th, mx;

		rect = [c->label frame];
		[c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
					      wthis,rect.size.height)];
		ynext = ythis - rect.size.height - VSPACING;

		rect = [c->editbox frame];
		eh = rect.size.height;
		rect = [c->button frame];
		bh = rect.size.height;
		th = (eh > bh ? eh : bh);

		mx = (wthis + HSPACING) * 3 / 4 - HSPACING;

		[c->editbox setFrame:
		 NSMakeRect(xthis, ynext-(th+eh)/2, mx, eh)];
		[c->button setFrame:
		 NSMakeRect(xthis+mx+HSPACING, ynext-(th+bh)/2,
			    wthis-mx-HSPACING, bh)];

		ch = (ythis - ynext) + th + VSPACING;
	    }
	    break;
	  case CTRL_LISTBOX:
	    {
		int listp = ctrl->listbox.percentwidth;
		int labelp = listp == 100 ? 100 : 100 - listp;
		int lheight, theight, rheight, ynext, listw, xlist;
		NSControl *list = (c->scrollview ? (id)c->scrollview :
				   (id)c->popupbutton);

		if (ctrl->listbox.draglist) {
		    assert(listp == 100);
		    listp = 75;
		}

		rect = [list frame];
		theight = rect.size.height;

		if (c->label) {
		    rect = [c->label frame];
		    lheight = rect.size.height;

		    if (labelp == 100)
			rheight = lheight;
		    else
			rheight = (lheight < theight ? theight : lheight);

		    [c->label setFrame:
		     NSMakeRect(xthis, ythis-(rheight+lheight)/2,
				(wthis + HSPACING) * labelp / 100 - HSPACING,
				lheight)];
		    if (labelp == 100) {
			ynext = ythis - rheight - VSPACING;
			rheight = theight;
		    } else {
			ynext = ythis;
		    }
		} else {
		    ynext = ythis;
		    rheight = theight;
		}

		listw = (wthis + HSPACING) * listp / 100 - HSPACING;

		if (labelp == 100)
		    xlist = xthis;
		else
		    xlist = xthis+wthis-listw;

		[list setFrame: NSMakeRect(xlist, ynext-(rheight+theight)/2,
					   listw, theight)];

		/*
		 * Size the columns for the table view.
		 */
		if (c->tableview) {
		    int ncols, *percentages;
		    int hundred = 100;
		    int cpercent = 0, cpixels = 0;
		    NSArray *cols;

		    if (ctrl->listbox.ncols) {
			ncols = ctrl->listbox.ncols;
			percentages = ctrl->listbox.percentages;
		    } else {
			ncols = 1;
			percentages = &hundred;
		    }

		    cols = [c->tableview tableColumns];

		    for (j = 0; j < ncols; j++) {
			NSTableColumn *col = [cols objectAtIndex:j];
			int newcpixels;

			cpercent += percentages[j];
			newcpixels = listw * cpercent / 100;
			[col setWidth:newcpixels-cpixels];
			cpixels = newcpixels;
		    }
		}

		ch = (ythis - ynext) + theight;

		if (c->button) {
		    int b2height, centre;
		    int bx, bw;

		    /*
		     * Place the Up and Down buttons for a drag list.
		     */
		    assert(c->button2);

		    rect = [c->button frame];
		    b2height = VSPACING + 2 * rect.size.height;

		    centre = ynext - rheight/2;

		    bx = (wthis + HSPACING) * 3 / 4;
		    bw = wthis - bx;
		    bx += leftx;

		    [c->button setFrame:
		     NSMakeRect(bx, centre+b2height/2-rect.size.height,
				bw, rect.size.height)];
		    [c->button2 setFrame:
		     NSMakeRect(bx, centre-b2height/2,
				bw, rect.size.height)];
		}
	    }
	    break;
	}

	for (j = colstart; j < colend; j++)
	    cypos[j] = ythis - ch - VSPACING;
	if (ret < topy - (ythis - ch))
	    ret = topy - (ythis - ch);
    }

    if (*s->boxname) {
	NSBox *box = find_box(d, s);
	assert(box != NULL);
	[box sizeToFit];

	if (s->boxtitle) {
	    NSRect rect = [box frame];
	    rect.size.height += [[box titleFont] pointSize];
	    [box setFrame:rect];
	}

	ret += boxh;
    }

    //printf("For controlset %s/%s, returning ret=%d\n",
    //       s->pathname, s->boxname, ret);
    return ret;
}

void select_panel(void *dv, struct controlbox *b, const char *name)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    int i, j, hidden;
    struct controlset *s;
    union control *ctrl;
    struct fe_ctrl *c;
    NSBox *box;

    for (i = 0; i < b->nctrlsets; i++) {
	s = b->ctrlsets[i];

	if (*s->pathname) {
	    hidden = !strcmp(s->pathname, name) ? NO : YES;

	    if ((box = find_box(d, s)) != NULL) {
		[box setHidden:hidden];
	    } else {
		for (j = 0; j < s->ncontrols; j++) {
		    ctrl = s->ctrls[j];
		    c = fe_ctrl_byctrl(d, ctrl);

		    if (!c)
			continue;

		    if (c->label)
			[c->label setHidden:hidden];
		    if (c->button)
			[c->button setHidden:hidden];
		    if (c->button2)
			[c->button2 setHidden:hidden];
		    if (c->editbox)
			[c->editbox setHidden:hidden];
		    if (c->combobox)
			[c->combobox setHidden:hidden];
		    if (c->textview)
			[c->textview setHidden:hidden];
		    if (c->tableview)
			[c->tableview setHidden:hidden];
		    if (c->scrollview)
			[c->scrollview setHidden:hidden];
		    if (c->popupbutton)
			[c->popupbutton setHidden:hidden];
		    if (c->radiobuttons) {
			int j;
			for (j = 0; j < c->nradiobuttons; j++)
			    [c->radiobuttons[j] setHidden:hidden];
		    }
		    break;
		}
	    }
	}
    }
}

void dlg_radiobutton_set(union control *ctrl, void *dv, int whichbutton)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
    int j;

    assert(c->radiobuttons);
    for (j = 0; j < c->nradiobuttons; j++)
	[c->radiobuttons[j] setState:
	 (j == whichbutton ? NSOnState : NSOffState)];
}

int dlg_radiobutton_get(union control *ctrl, void *dv)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
    int j;

    assert(c->radiobuttons);
    for (j = 0; j < c->nradiobuttons; j++)
	if ([c->radiobuttons[j] state] == NSOnState)
	    return j;

    return 0;			       /* should never reach here */
}

void dlg_checkbox_set(union control *ctrl, void *dv, int checked)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);

    assert(c->button);
    [c->button setState:(checked ? NSOnState : NSOffState)];
}

int dlg_checkbox_get(union control *ctrl, void *dv)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);

    assert(c->button);
    return ([c->button state] == NSOnState);
}

void dlg_editbox_set(union control *ctrl, void *dv, char const *text)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);

    if (c->editbox) {
	[c->editbox setStringValue:[NSString stringWithCString:text]];
    } else {
	assert(c->combobox);
	[c->combobox setStringValue:[NSString stringWithCString:text]];
    }
}

void dlg_editbox_get(union control *ctrl, void *dv, char *buffer, int length)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
    NSString *str;

    if (c->editbox) {
	str = [c->editbox stringValue];
    } else {
	assert(c->combobox);
	str = [c->combobox stringValue];
    }
    if (!str)
	str = @"";

    /* The length parameter to this method doesn't include a trailing NUL */
    [str getCString:buffer maxLength:length-1];
}

void dlg_listbox_clear(union control *ctrl, void *dv)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);

    if (c->tableview) {
	[[c->tableview dataSource] clear];
	[c->tableview reloadData];
    } else {
	[c->popupbutton removeAllItems];
    }
}

void dlg_listbox_del(union control *ctrl, void *dv, int index)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);

    if (c->tableview) {
	[[c->tableview dataSource] removestr:index];
	[c->tableview reloadData];
    } else {
	[c->popupbutton removeItemAtIndex:index];
    }
}

void dlg_listbox_addwithid(union control *ctrl, void *dv,
			   char const *text, int id)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);

    if (c->tableview) {
	[[c->tableview dataSource] add:text withId:id];
	[c->tableview reloadData];
    } else {
	[c->popupbutton addItemWithTitle:[NSString stringWithCString:text]];
	[[c->popupbutton lastItem] setTag:id];
    }
}

void dlg_listbox_add(union control *ctrl, void *dv, char const *text)
{
    dlg_listbox_addwithid(ctrl, dv, text, -1);
}

int dlg_listbox_getid(union control *ctrl, void *dv, int index)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);

    if (c->tableview) {
	return [[c->tableview dataSource] getid:index];
    } else {
	return [[c->popupbutton itemAtIndex:index] tag];
    }
}

int dlg_listbox_index(union control *ctrl, void *dv)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);

    if (c->tableview) {
	return [c->tableview selectedRow];
    } else {
	return [c->popupbutton indexOfSelectedItem];
    }
}

int dlg_listbox_issel(union control *ctrl, void *dv, int index)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);

    if (c->tableview) {
	return [c->tableview isRowSelected:index];
    } else {
	return [c->popupbutton indexOfSelectedItem] == index;
    }
}

void dlg_listbox_select(union control *ctrl, void *dv, int index)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);

    if (c->tableview) {
	[c->tableview selectRow:index byExtendingSelection:NO];
    } else {
	[c->popupbutton selectItemAtIndex:index];
    }
}

void dlg_text_set(union control *ctrl, void *dv, char const *text)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);

    assert(c->textview);
    [c->textview setString:[NSString stringWithCString:text]];
}

void dlg_label_change(union control *ctrl, void *dlg, char const *text)
{
    /*
     * This function is currently only used by the config box to
     * switch the labels on the host and port boxes between serial
     * and network modes. Since OS X does not (yet?) have a serial
     * back end, this function can safely do nothing for the
     * moment.
     */
}

void dlg_filesel_set(union control *ctrl, void *dv, Filename fn)
{
    /* FIXME */
}

void dlg_filesel_get(union control *ctrl, void *dv, Filename *fn)
{
    /* FIXME */
}

void dlg_fontsel_set(union control *ctrl, void *dv, FontSpec fn)
{
    /* FIXME */
}

void dlg_fontsel_get(union control *ctrl, void *dv, FontSpec *fn)
{
    /* FIXME */
}

void dlg_update_start(union control *ctrl, void *dv)
{
    /* FIXME */
}

void dlg_update_done(union control *ctrl, void *dv)
{
    /* FIXME */
}

void dlg_set_focus(union control *ctrl, void *dv)
{
    /* FIXME */
}

union control *dlg_last_focused(union control *ctrl, void *dv)
{
    return NULL; /* FIXME */
}

void dlg_beep(void *dv)
{
    NSBeep();
}

void dlg_error_msg(void *dv, char *msg)
{
    /* FIXME */
}

void dlg_end(void *dv, int value)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    [d->target performSelector:d->action
     withObject:[NSNumber numberWithInt:value]];
}

void dlg_coloursel_start(union control *ctrl, void *dv,
			 int r, int g, int b)
{
    /* FIXME */
}

int dlg_coloursel_results(union control *ctrl, void *dv,
			  int *r, int *g, int *b)
{
    return 0; /* FIXME */
}

void dlg_refresh(union control *ctrl, void *dv)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c;

    if (ctrl) {
	if (ctrl->generic.handler != NULL)
	    ctrl->generic.handler(ctrl, d, d->data, EVENT_REFRESH);
    } else {
	int i;

	for (i = 0; (c = index234(d->byctrl, i)) != NULL; i++) {
	    assert(c->ctrl != NULL);
	    if (c->ctrl->generic.handler != NULL)
		c->ctrl->generic.handler(c->ctrl, d,
					 d->data, EVENT_REFRESH);
	}
    }
}

void *dlg_get_privdata(union control *ctrl, void *dv)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
    return c->privdata;
}

void dlg_set_privdata(union control *ctrl, void *dv, void *ptr)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
    c->privdata = ptr;
    c->privdata_needs_free = FALSE;
}

void *dlg_alloc_privdata(union control *ctrl, void *dv, size_t size)
{
    struct fe_dlg *d = (struct fe_dlg *)dv;
    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
    /*
     * This is an internal allocation routine, so it's allowed to
     * use smalloc directly.
     */
    c->privdata = smalloc(size);
    c->privdata_needs_free = TRUE;
    return c->privdata;
}
