OSDN Git Service

Add PuTTY 0.61 to contrib directory.
[ffftp/ffftp.git] / contrib / putty / MACOSX / OSXCTRLS.M
diff --git a/contrib/putty/MACOSX/OSXCTRLS.M b/contrib/putty/MACOSX/OSXCTRLS.M
new file mode 100644 (file)
index 0000000..5e0699e
--- /dev/null
@@ -0,0 +1,1815 @@
+/*\r
+ * osxctrls.m: OS X implementation of the dialog.h interface.\r
+ */\r
+\r
+#import <Cocoa/Cocoa.h>\r
+#include "putty.h"\r
+#include "dialog.h"\r
+#include "osxclass.h"\r
+#include "tree234.h"\r
+\r
+/*\r
+ * Still to be implemented:\r
+ * \r
+ *  - file selectors (NSOpenPanel / NSSavePanel)\r
+ * \r
+ *  - font selectors\r
+ *  - colour selectors\r
+ *     * both of these have a conceptual oddity in Cocoa that\r
+ *      you're only supposed to have one per application. But I\r
+ *      currently expect to be able to have multiple PuTTY config\r
+ *      boxes on screen at once; what happens if you trigger the\r
+ *      font selector in each one at the same time?\r
+ *     * if it comes to that, the _font_ selector can probably be\r
+ *      managed by other means: nobody is forcing me to implement\r
+ *      a font selector using a `Change...' button. The portable\r
+ *      dialog interface gives me the flexibility to do this how I\r
+ *      want.\r
+ *     * The colour selector interface, in its present form, is\r
+ *      more interesting and _if_ a radical change of plan is\r
+ *      required then it may stretch across the interface into the\r
+ *      portable side.\r
+ *     * Before I do anything rash I should start by looking at the\r
+ *      Mac Classic port and see how it's done there, on the basis\r
+ *      that Apple seem reasonably unlikely to have invented this\r
+ *      crazy restriction specifically for OS X.\r
+ * \r
+ *  - focus management\r
+ *     * I tried using makeFirstResponder to give keyboard focus,\r
+ *      but it appeared not to work. Try again, and work out how\r
+ *      it should be done.\r
+ *     * also look into tab order. Currently pressing Tab suggests\r
+ *      that only edit boxes and list boxes can get the keyboard\r
+ *      focus, and that buttons (in all their forms) are unable to\r
+ *      be driven by the keyboard. Find out for sure.\r
+ * \r
+ *  - dlg_error_msg\r
+ *     * this may run into the usual aggro with modal dialog boxes.\r
+ */\r
+\r
+/*\r
+ * For Cocoa control layout, I need a two-stage process. In stage\r
+ * one, I allocate all the controls and measure their natural\r
+ * sizes, which allows me to compute the _minimum_ width and height\r
+ * of a given section of dialog. Then, in stage two, I lay out the\r
+ * dialog box as a whole, decide how much each section of the box\r
+ * needs to receive, and assign it its final size.\r
+ */\r
+\r
+/*\r
+ * As yet unsolved issues [FIXME]:\r
+ * \r
+ *  - Sometimes the height returned from create_ctrls and the\r
+ *    height returned from place_ctrls differ. Find out why. It may\r
+ *    be harmless (e.g. results of NSTextView being odd), but I\r
+ *    want to know.\r
+ * \r
+ *  - NSTextViews are indented a bit. It'd be nice to put their\r
+ *    left margin at the same place as everything else's.\r
+ * \r
+ *  - I don't yet know whether we even _can_ support tab order or\r
+ *    keyboard shortcuts. If we can't, then fair enough, we can't.\r
+ *    But if we can, we should.\r
+ * \r
+ *  - I would _really_ like to know of a better way to correct\r
+ *    NSButton's stupid size estimates than by subclassing it and\r
+ *    overriding sizeToFit with hard-wired sensible values!\r
+ * \r
+ *  - Speaking of stupid size estimates, the amount by which I'm\r
+ *    adjusting a titled NSBox (currently equal to the point size\r
+ *    of its title font) looks as if it isn't _quite_ enough.\r
+ *    Figure out what the real amount should be and use it.\r
+ * \r
+ *  - I don't understand why there's always a scrollbar displayed\r
+ *    in each list box. I thought I told it to autohide scrollers?\r
+ * \r
+ *  - Why do I have to fudge list box heights by adding one? (Might\r
+ *    it be to do with the missing header view?)\r
+ */\r
+\r
+/*\r
+ * Subclass of NSButton which corrects the fact that the normal\r
+ * one's sizeToFit method persistently returns 32 as its height,\r
+ * which is simply a lie. I have yet to work out a better\r
+ * alternative than hard-coding the real heights.\r
+ */\r
+@interface MyButton : NSButton\r
+{\r
+    int minht;\r
+}\r
+@end\r
+@implementation MyButton\r
+- (id)initWithFrame:(NSRect)r\r
+{\r
+    self = [super initWithFrame:r];\r
+    minht = 25;\r
+    return self;\r
+}\r
+- (void)setButtonType:(NSButtonType)t\r
+{\r
+    if (t == NSRadioButton || t == NSSwitchButton)\r
+       minht = 18;\r
+    else\r
+       minht = 25;\r
+    [super setButtonType:t];\r
+}\r
+- (void)sizeToFit\r
+{\r
+    NSRect r;\r
+    [super sizeToFit];\r
+    r = [self frame];\r
+    r.size.height = minht;\r
+    [self setFrame:r];\r
+}\r
+@end\r
+\r
+/*\r
+ * Class used as the data source for NSTableViews.\r
+ */\r
+@interface MyTableSource : NSObject\r
+{\r
+    tree234 *tree;\r
+}\r
+- (id)init;\r
+- (void)add:(const char *)str withId:(int)id;\r
+- (int)getid:(int)index;\r
+- (void)swap:(int)index1 with:(int)index2;\r
+- (void)removestr:(int)index;\r
+- (void)clear;\r
+@end\r
+@implementation MyTableSource\r
+- (id)init\r
+{\r
+    self = [super init];\r
+    tree = newtree234(NULL);\r
+    return self;\r
+}\r
+- (void)dealloc\r
+{\r
+    char *p;\r
+    while ((p = delpos234(tree, 0)) != NULL)\r
+       sfree(p);\r
+    freetree234(tree);\r
+    [super dealloc];\r
+}\r
+- (void)add:(const char *)str withId:(int)id\r
+{\r
+    addpos234(tree, dupprintf("%d\t%s", id, str), count234(tree));\r
+}\r
+- (int)getid:(int)index\r
+{\r
+    char *p = index234(tree, index);\r
+    return atoi(p);\r
+}\r
+- (void)removestr:(int)index\r
+{\r
+    char *p = delpos234(tree, index);\r
+    sfree(p);\r
+}\r
+- (void)swap:(int)index1 with:(int)index2\r
+{\r
+    char *p1, *p2;\r
+\r
+    if (index1 > index2) {\r
+       int t = index1; index1 = index2; index2 = t;\r
+    }\r
+\r
+    /* delete later one first so it doesn't affect index of earlier one */\r
+    p2 = delpos234(tree, index2);\r
+    p1 = delpos234(tree, index1);\r
+\r
+    /* now insert earlier one before later one for the inverse reason */\r
+    addpos234(tree, p2, index1);\r
+    addpos234(tree, p1, index2);\r
+}\r
+- (void)clear\r
+{\r
+    char *p;\r
+    while ((p = delpos234(tree, 0)) != NULL)\r
+       sfree(p);\r
+}\r
+- (int)numberOfRowsInTableView:(NSTableView *)aTableView\r
+{\r
+    return count234(tree);\r
+}\r
+- (id)tableView:(NSTableView *)aTableView\r
+    objectValueForTableColumn:(NSTableColumn *)aTableColumn\r
+    row:(int)rowIndex\r
+{\r
+    int j = [[aTableColumn identifier] intValue];\r
+    char *p = index234(tree, rowIndex);\r
+\r
+    while (j >= 0) {\r
+       p += strcspn(p, "\t");\r
+       if (*p) p++;\r
+       j--;\r
+    }\r
+\r
+    return [NSString stringWithCString:p length:strcspn(p, "\t")];\r
+}\r
+@end\r
+\r
+/*\r
+ * Object to receive messages from various control classes.\r
+ */\r
+@class Receiver;\r
+\r
+struct fe_dlg {\r
+    NSWindow *window;\r
+    NSObject *target;\r
+    SEL action;\r
+    tree234 *byctrl;\r
+    tree234 *bywidget;\r
+    tree234 *boxes;\r
+    void *data;                               /* passed to portable side */\r
+    Receiver *rec;\r
+};\r
+\r
+@interface Receiver : NSObject\r
+{\r
+    struct fe_dlg *d;\r
+}\r
+- (id)initWithStruct:(struct fe_dlg *)aStruct;\r
+@end\r
+\r
+struct fe_ctrl {\r
+    union control *ctrl;\r
+    NSButton *button, *button2;\r
+    NSTextField *label, *editbox;\r
+    NSComboBox *combobox;\r
+    NSButton **radiobuttons;\r
+    NSTextView *textview;\r
+    NSPopUpButton *popupbutton;\r
+    NSTableView *tableview;\r
+    NSScrollView *scrollview;\r
+    int nradiobuttons;\r
+    void *privdata;\r
+    int privdata_needs_free;\r
+};\r
+\r
+static int fe_ctrl_cmp_by_ctrl(void *av, void *bv)\r
+{\r
+    struct fe_ctrl *a = (struct fe_ctrl *)av;\r
+    struct fe_ctrl *b = (struct fe_ctrl *)bv;\r
+\r
+    if (a->ctrl < b->ctrl)\r
+       return -1;\r
+    if (a->ctrl > b->ctrl)\r
+       return +1;\r
+    return 0;\r
+}\r
+\r
+static int fe_ctrl_find_by_ctrl(void *av, void *bv)\r
+{\r
+    union control *a = (union control *)av;\r
+    struct fe_ctrl *b = (struct fe_ctrl *)bv;\r
+\r
+    if (a < b->ctrl)\r
+       return -1;\r
+    if (a > b->ctrl)\r
+       return +1;\r
+    return 0;\r
+}\r
+\r
+struct fe_box {\r
+    struct controlset *s;\r
+    id box;\r
+};\r
+\r
+static int fe_boxcmp(void *av, void *bv)\r
+{\r
+    struct fe_box *a = (struct fe_box *)av;\r
+    struct fe_box *b = (struct fe_box *)bv;\r
+\r
+    if (a->s < b->s)\r
+       return -1;\r
+    if (a->s > b->s)\r
+       return +1;\r
+    return 0;\r
+}\r
+\r
+static int fe_boxfind(void *av, void *bv)\r
+{\r
+    struct controlset *a = (struct controlset *)av;\r
+    struct fe_box *b = (struct fe_box *)bv;\r
+\r
+    if (a < b->s)\r
+       return -1;\r
+    if (a > b->s)\r
+       return +1;\r
+    return 0;\r
+}\r
+\r
+struct fe_backwards {                 /* map Cocoa widgets back to fe_ctrls */\r
+    id widget;\r
+    struct fe_ctrl *c;\r
+};\r
+\r
+static int fe_backwards_cmp_by_widget(void *av, void *bv)\r
+{\r
+    struct fe_backwards *a = (struct fe_backwards *)av;\r
+    struct fe_backwards *b = (struct fe_backwards *)bv;\r
+\r
+    if (a->widget < b->widget)\r
+       return -1;\r
+    if (a->widget > b->widget)\r
+       return +1;\r
+    return 0;\r
+}\r
+\r
+static int fe_backwards_find_by_widget(void *av, void *bv)\r
+{\r
+    id a = (id)av;\r
+    struct fe_backwards *b = (struct fe_backwards *)bv;\r
+\r
+    if (a < b->widget)\r
+       return -1;\r
+    if (a > b->widget)\r
+       return +1;\r
+    return 0;\r
+}\r
+\r
+static struct fe_ctrl *fe_ctrl_new(union control *ctrl)\r
+{\r
+    struct fe_ctrl *c;\r
+\r
+    c = snew(struct fe_ctrl);\r
+    c->ctrl = ctrl;\r
+\r
+    c->button = c->button2 = nil;\r
+    c->label = nil;\r
+    c->editbox = nil;\r
+    c->combobox = nil;\r
+    c->textview = nil;\r
+    c->popupbutton = nil;\r
+    c->tableview = nil;\r
+    c->scrollview = nil;\r
+    c->radiobuttons = NULL;\r
+    c->nradiobuttons = 0;\r
+    c->privdata = NULL;\r
+    c->privdata_needs_free = FALSE;\r
+\r
+    return c;\r
+}\r
+\r
+static void fe_ctrl_free(struct fe_ctrl *c)\r
+{\r
+    if (c->privdata_needs_free)\r
+       sfree(c->privdata);\r
+    sfree(c->radiobuttons);\r
+    sfree(c);\r
+}\r
+\r
+static struct fe_ctrl *fe_ctrl_byctrl(struct fe_dlg *d, union control *ctrl)\r
+{\r
+    return find234(d->byctrl, ctrl, fe_ctrl_find_by_ctrl);\r
+}\r
+\r
+static void add_box(struct fe_dlg *d, struct controlset *s, id box)\r
+{\r
+    struct fe_box *b = snew(struct fe_box);\r
+    b->box = box;\r
+    b->s = s;\r
+    add234(d->boxes, b);\r
+}\r
+\r
+static id find_box(struct fe_dlg *d, struct controlset *s)\r
+{\r
+    struct fe_box *b = find234(d->boxes, s, fe_boxfind);\r
+    return b ? b->box : NULL;\r
+}\r
+\r
+static void add_widget(struct fe_dlg *d, struct fe_ctrl *c, id widget)\r
+{\r
+    struct fe_backwards *b = snew(struct fe_backwards);\r
+    b->widget = widget;\r
+    b->c = c;\r
+    add234(d->bywidget, b);\r
+}\r
+\r
+static struct fe_ctrl *find_widget(struct fe_dlg *d, id widget)\r
+{\r
+    struct fe_backwards *b = find234(d->bywidget, widget,\r
+                                    fe_backwards_find_by_widget);\r
+    return b ? b->c : NULL;\r
+}\r
+\r
+void *fe_dlg_init(void *data, NSWindow *window, NSObject *target, SEL action)\r
+{\r
+    struct fe_dlg *d;\r
+\r
+    d = snew(struct fe_dlg);\r
+    d->window = window;\r
+    d->target = target;\r
+    d->action = action;\r
+    d->byctrl = newtree234(fe_ctrl_cmp_by_ctrl);\r
+    d->bywidget = newtree234(fe_backwards_cmp_by_widget);\r
+    d->boxes = newtree234(fe_boxcmp);\r
+    d->data = data;\r
+    d->rec = [[Receiver alloc] initWithStruct:d];\r
+\r
+    return d;\r
+}\r
+\r
+void fe_dlg_free(void *dv)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c;\r
+    struct fe_box *b;\r
+\r
+    while ( (c = delpos234(d->byctrl, 0)) != NULL )\r
+       fe_ctrl_free(c);\r
+    freetree234(d->byctrl);\r
+\r
+    while ( (c = delpos234(d->bywidget, 0)) != NULL )\r
+       sfree(c);\r
+    freetree234(d->bywidget);\r
+\r
+    while ( (b = delpos234(d->boxes, 0)) != NULL )\r
+       sfree(b);\r
+    freetree234(d->boxes);\r
+\r
+    [d->rec release];\r
+\r
+    sfree(d);\r
+}\r
+\r
+@implementation Receiver\r
+- (id)initWithStruct:(struct fe_dlg *)aStruct\r
+{\r
+    self = [super init];\r
+    d = aStruct;\r
+    return self;\r
+}\r
+- (void)buttonPushed:(id)sender\r
+{\r
+    struct fe_ctrl *c = find_widget(d, sender);\r
+\r
+    assert(c && c->ctrl->generic.type == CTRL_BUTTON);\r
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);\r
+}\r
+- (void)checkboxChanged:(id)sender\r
+{\r
+    struct fe_ctrl *c = find_widget(d, sender);\r
+\r
+    assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);\r
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);\r
+}\r
+- (void)radioChanged:(id)sender\r
+{\r
+    struct fe_ctrl *c = find_widget(d, sender);\r
+    int j;\r
+\r
+    assert(c && c->radiobuttons);\r
+    for (j = 0; j < c->nradiobuttons; j++)\r
+       if (sender != c->radiobuttons[j])\r
+           [c->radiobuttons[j] setState:NSOffState];\r
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);\r
+}\r
+- (void)popupMenuSelected:(id)sender\r
+{\r
+    struct fe_ctrl *c = find_widget(d, sender);\r
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);\r
+}\r
+- (void)controlTextDidChange:(NSNotification *)notification\r
+{\r
+    id widget = [notification object];\r
+    struct fe_ctrl *c = find_widget(d, widget);\r
+    assert(c && c->ctrl->generic.type == CTRL_EDITBOX);\r
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);\r
+}\r
+- (void)controlTextDidEndEditing:(NSNotification *)notification\r
+{\r
+    id widget = [notification object];\r
+    struct fe_ctrl *c = find_widget(d, widget);\r
+    assert(c && c->ctrl->generic.type == CTRL_EDITBOX);\r
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_REFRESH);\r
+}\r
+- (void)tableViewSelectionDidChange:(NSNotification *)notification\r
+{\r
+    id widget = [notification object];\r
+    struct fe_ctrl *c = find_widget(d, widget);\r
+    assert(c && c->ctrl->generic.type == CTRL_LISTBOX);\r
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_SELCHANGE);\r
+}\r
+- (BOOL)tableView:(NSTableView *)aTableView\r
+    shouldEditTableColumn:(NSTableColumn *)aTableColumn\r
+    row:(int)rowIndex\r
+{\r
+    return NO;                        /* no editing permitted */\r
+}\r
+- (void)listDoubleClicked:(id)sender\r
+{\r
+    struct fe_ctrl *c = find_widget(d, sender);\r
+    assert(c && c->ctrl->generic.type == CTRL_LISTBOX);\r
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);\r
+}\r
+- (void)dragListButton:(id)sender\r
+{\r
+    struct fe_ctrl *c = find_widget(d, sender);\r
+    int direction, row, nrows;\r
+    assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&\r
+          c->ctrl->listbox.draglist);\r
+\r
+    if (sender == c->button)\r
+       direction = -1;                /* up */\r
+    else\r
+       direction = +1;                /* down */\r
+\r
+    row = [c->tableview selectedRow];\r
+    nrows = [c->tableview numberOfRows];\r
+\r
+    if (row + direction < 0 || row + direction >= nrows) {\r
+       NSBeep();\r
+       return;\r
+    }\r
+\r
+    [[c->tableview dataSource] swap:row with:row+direction];\r
+    [c->tableview reloadData];\r
+    [c->tableview selectRow:row+direction byExtendingSelection:NO];\r
+\r
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);\r
+}\r
+@end\r
+\r
+void create_ctrls(void *dv, NSView *parent, struct controlset *s,\r
+                 int *minw, int *minh)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    int ccw[100];                     /* cumulative column widths */\r
+    int cypos[100];\r
+    int ncols;\r
+    int wmin = 0, hmin = 0;\r
+    int i, j, cw, ch;\r
+    NSRect rect;\r
+    NSFont *textviewfont = nil;\r
+    int boxh = 0, boxw = 0;\r
+\r
+    if (!s->boxname && s->boxtitle) {\r
+        /* This controlset is a panel title. */\r
+\r
+       NSTextField *tf;\r
+\r
+       tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];\r
+       [tf setEditable:NO];\r
+       [tf setSelectable:NO];\r
+       [tf setBordered:NO];\r
+       [tf setDrawsBackground:NO];\r
+       [tf setStringValue:[NSString stringWithCString:s->boxtitle]];\r
+       [tf sizeToFit];\r
+       rect = [tf frame];\r
+       [parent addSubview:tf];\r
+\r
+       /*\r
+        * I'm going to store this NSTextField in the boxes tree,\r
+        * because I really can't face having a special tree234\r
+        * mapping controlsets to panel titles.\r
+        */\r
+       add_box(d, s, tf);\r
+\r
+       *minw = rect.size.width;\r
+       *minh = rect.size.height;\r
+\r
+       return;\r
+    }\r
+\r
+    if (*s->boxname) {\r
+       /*\r
+        * Create an NSBox to contain this subset of controls.\r
+        */\r
+       NSBox *box;\r
+       NSRect tmprect;\r
+\r
+       box = [[NSBox alloc] initWithFrame:NSMakeRect(0,0,1,1)];\r
+       if (s->boxtitle)\r
+           [box setTitle:[NSString stringWithCString:s->boxtitle]];\r
+       else\r
+           [box setTitlePosition:NSNoTitle];\r
+       add_box(d, s, box);\r
+       tmprect = [box frame];\r
+       [box setContentViewMargins:NSMakeSize(20,20)];\r
+       [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];\r
+       rect = [box frame];\r
+       [box setFrame:tmprect];\r
+       boxh = (int)(rect.size.height - 100);\r
+       boxw = (int)(rect.size.width - 100);\r
+       [parent addSubview:box];\r
+\r
+       if (s->boxtitle)\r
+           boxh += [[box titleFont] pointSize];\r
+\r
+       /*\r
+        * All subsequent controls will be placed within this box.\r
+        */\r
+       parent = box;\r
+    }\r
+\r
+    ncols = 1;\r
+    ccw[0] = 0;\r
+    ccw[1] = 100;\r
+    cypos[0] = 0;\r
+\r
+    /*\r
+     * Now iterate through the controls themselves, create them,\r
+     * and add their width and height to the overall width/height\r
+     * calculation.\r
+     */\r
+    for (i = 0; i < s->ncontrols; i++) {\r
+       union control *ctrl = s->ctrls[i];\r
+       struct fe_ctrl *c;\r
+       int colstart = COLUMN_START(ctrl->generic.column);\r
+       int colspan = COLUMN_SPAN(ctrl->generic.column);\r
+       int colend = colstart + colspan;\r
+       int ytop, wthis;\r
+\r
+        switch (ctrl->generic.type) {\r
+          case CTRL_COLUMNS:\r
+           for (j = 1; j < ncols; j++)\r
+               if (cypos[0] < cypos[j])\r
+                   cypos[0] = cypos[j];\r
+\r
+           assert(ctrl->columns.ncols < lenof(ccw));\r
+\r
+           ccw[0] = 0;\r
+           for (j = 0; j < ctrl->columns.ncols; j++) {\r
+               ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?\r
+                                    ctrl->columns.percentages[j] : 100);\r
+               cypos[j] = cypos[0];\r
+           }\r
+\r
+           ncols = ctrl->columns.ncols;\r
+\r
+            continue;                  /* no actual control created */\r
+          case CTRL_TABDELAY:\r
+           /*\r
+            * I'm currently uncertain that we can implement tab\r
+            * order in OS X.\r
+            */\r
+            continue;                  /* no actual control created */\r
+       }\r
+\r
+       c = fe_ctrl_new(ctrl);\r
+       add234(d->byctrl, c);\r
+\r
+       cw = ch = 0;\r
+\r
+        switch (ctrl->generic.type) {\r
+          case CTRL_BUTTON:\r
+          case CTRL_CHECKBOX:\r
+           {\r
+               NSButton *b;\r
+\r
+               b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];\r
+               [b setBezelStyle:NSRoundedBezelStyle];\r
+               if (ctrl->generic.type == CTRL_CHECKBOX)\r
+                   [b setButtonType:NSSwitchButton];\r
+               [b setTitle:[NSString stringWithCString:ctrl->generic.label]];\r
+               if (ctrl->button.isdefault)\r
+                   [b setKeyEquivalent:@"\r"];\r
+               else if (ctrl->button.iscancel)\r
+                   [b setKeyEquivalent:@"\033"];\r
+               [b sizeToFit];\r
+               rect = [b frame];\r
+\r
+               [parent addSubview:b];\r
+\r
+               [b setTarget:d->rec];\r
+               if (ctrl->generic.type == CTRL_CHECKBOX)\r
+                   [b setAction:@selector(checkboxChanged:)];\r
+               else\r
+                   [b setAction:@selector(buttonPushed:)];\r
+               add_widget(d, c, b);\r
+\r
+               c->button = b;\r
+\r
+               cw = rect.size.width;\r
+               ch = rect.size.height;\r
+           }\r
+           break;\r
+         case CTRL_EDITBOX:\r
+           {\r
+               int editp = ctrl->editbox.percentwidth;\r
+               int labelp = editp == 100 ? 100 : 100 - editp;\r
+               NSTextField *tf;\r
+               NSComboBox *cb;\r
+\r
+               tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];\r
+               [tf setEditable:NO];\r
+               [tf setSelectable:NO];\r
+               [tf setBordered:NO];\r
+               [tf setDrawsBackground:NO];\r
+               [tf setStringValue:[NSString\r
+                                   stringWithCString:ctrl->generic.label]];\r
+               [tf sizeToFit];\r
+               rect = [tf frame];\r
+               [parent addSubview:tf];\r
+               c->label = tf;\r
+\r
+               cw = rect.size.width * 100 / labelp;\r
+               ch = rect.size.height;\r
+\r
+               if (ctrl->editbox.has_list) {\r
+                   cb = [[NSComboBox alloc]\r
+                         initWithFrame:NSMakeRect(0,0,1,1)];\r
+                   [cb setStringValue:@"x"];\r
+                   [cb sizeToFit];\r
+                   rect = [cb frame];\r
+                   [parent addSubview:cb];\r
+                   c->combobox = cb;\r
+               } else {\r
+                   if (ctrl->editbox.password)\r
+                       tf = [NSSecureTextField alloc];\r
+                   else\r
+                       tf = [NSTextField alloc];\r
+\r
+                   tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];\r
+                   [tf setEditable:YES];\r
+                   [tf setSelectable:YES];\r
+                   [tf setBordered:YES];\r
+                   [tf setStringValue:@"x"];\r
+                   [tf sizeToFit];\r
+                   rect = [tf frame];\r
+                   [parent addSubview:tf];\r
+                   c->editbox = tf;\r
+\r
+                   [tf setDelegate:d->rec];\r
+                   add_widget(d, c, tf);\r
+               }\r
+\r
+               if (editp == 100) {\r
+                   /* the edit box and its label are vertically separated */\r
+                   ch += VSPACING + rect.size.height;\r
+               } else {\r
+                   /* the edit box and its label are horizontally separated */\r
+                   if (ch < rect.size.height)\r
+                       ch = rect.size.height;\r
+               }\r
+\r
+               if (cw < rect.size.width * 100 / editp)\r
+                   cw = rect.size.width * 100 / editp;\r
+           }\r
+           break;\r
+         case CTRL_TEXT:\r
+           {\r
+               NSTextView *tv;\r
+               int testwid;\r
+\r
+               if (!textviewfont) {\r
+                   NSTextField *tf;\r
+                   tf = [[NSTextField alloc] init];\r
+                   textviewfont = [tf font];\r
+                   [tf release];\r
+               }\r
+\r
+               testwid = (ccw[colend] - ccw[colstart]) * 3;\r
+\r
+               tv = [[NSTextView alloc]\r
+                     initWithFrame:NSMakeRect(0,0,testwid,1)];\r
+               [tv setEditable:NO];\r
+               [tv setSelectable:NO];\r
+               //[tv setBordered:NO];\r
+               [tv setDrawsBackground:NO];\r
+               [tv setFont:textviewfont];\r
+               [tv setString:\r
+                [NSString stringWithCString:ctrl->generic.label]];\r
+               rect = [tv frame];\r
+               [tv sizeToFit];\r
+               [parent addSubview:tv];\r
+               c->textview = tv;\r
+\r
+               cw = rect.size.width;\r
+               ch = rect.size.height;\r
+           }\r
+           break;\r
+         case CTRL_RADIO:\r
+           {\r
+               NSTextField *tf;\r
+               int j;\r
+\r
+               if (ctrl->generic.label) {\r
+                   tf = [[NSTextField alloc]\r
+                         initWithFrame:NSMakeRect(0,0,1,1)];\r
+                   [tf setEditable:NO];\r
+                   [tf setSelectable:NO];\r
+                   [tf setBordered:NO];\r
+                   [tf setDrawsBackground:NO];\r
+                   [tf setStringValue:\r
+                    [NSString stringWithCString:ctrl->generic.label]];\r
+                   [tf sizeToFit];\r
+                   rect = [tf frame];\r
+                   [parent addSubview:tf];\r
+                   c->label = tf;\r
+\r
+                   cw = rect.size.width;\r
+                   ch = rect.size.height;\r
+               } else {\r
+                   cw = 0;\r
+                   ch = -VSPACING;    /* compensate for next advance */\r
+               }\r
+\r
+               c->nradiobuttons = ctrl->radio.nbuttons;\r
+               c->radiobuttons = snewn(ctrl->radio.nbuttons, NSButton *);\r
+\r
+               for (j = 0; j < ctrl->radio.nbuttons; j++) {\r
+                   NSButton *b;\r
+                   int ncols;\r
+\r
+                   b = [[MyButton alloc] initWithFrame:NSMakeRect(0,0,1,1)];\r
+                   [b setBezelStyle:NSRoundedBezelStyle];\r
+                   [b setButtonType:NSRadioButton];\r
+                   [b setTitle:[NSString\r
+                                stringWithCString:ctrl->radio.buttons[j]]];\r
+                   [b sizeToFit];\r
+                   rect = [b frame];\r
+                   [parent addSubview:b];\r
+\r
+                   c->radiobuttons[j] = b;\r
+\r
+                   [b setTarget:d->rec];\r
+                   [b setAction:@selector(radioChanged:)];\r
+                   add_widget(d, c, b);\r
+\r
+                   /*\r
+                    * Add to the height every time we place a\r
+                    * button in column 0.\r
+                    */\r
+                   if (j % ctrl->radio.ncolumns == 0) {\r
+                       ch += rect.size.height + VSPACING;\r
+                   }\r
+\r
+                   /*\r
+                    * Add to the width by working out how many\r
+                    * columns this button spans.\r
+                    */\r
+                   if (j == ctrl->radio.nbuttons - 1)\r
+                       ncols = (ctrl->radio.ncolumns -\r
+                                (j % ctrl->radio.ncolumns));\r
+                   else\r
+                       ncols = 1;\r
+\r
+                   if (cw < rect.size.width * ctrl->radio.ncolumns / ncols)\r
+                       cw = rect.size.width * ctrl->radio.ncolumns / ncols;\r
+               }\r
+           }\r
+           break;\r
+         case CTRL_FILESELECT:\r
+         case CTRL_FONTSELECT:\r
+           {\r
+               NSTextField *tf;\r
+               NSButton *b;\r
+               int kh;\r
+\r
+               tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];\r
+               [tf setEditable:NO];\r
+               [tf setSelectable:NO];\r
+               [tf setBordered:NO];\r
+               [tf setDrawsBackground:NO];\r
+               [tf setStringValue:[NSString\r
+                                   stringWithCString:ctrl->generic.label]];\r
+               [tf sizeToFit];\r
+               rect = [tf frame];\r
+               [parent addSubview:tf];\r
+               c->label = tf;\r
+\r
+               cw = rect.size.width;\r
+               ch = rect.size.height;\r
+\r
+               tf = [NSTextField alloc];\r
+               tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];\r
+               if (ctrl->generic.type == CTRL_FILESELECT) {\r
+                   [tf setEditable:YES];\r
+                   [tf setSelectable:YES];\r
+                   [tf setBordered:YES];\r
+               } else {\r
+                   [tf setEditable:NO];\r
+                   [tf setSelectable:NO];\r
+                   [tf setBordered:NO];\r
+                   [tf setDrawsBackground:NO];\r
+               }\r
+               [tf setStringValue:@"x"];\r
+               [tf sizeToFit];\r
+               rect = [tf frame];\r
+               [parent addSubview:tf];\r
+               c->editbox = tf;\r
+\r
+               kh = rect.size.height;\r
+               if (cw < rect.size.width * 4 / 3)\r
+                   cw = rect.size.width * 4 / 3;\r
+\r
+               b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];\r
+               [b setBezelStyle:NSRoundedBezelStyle];\r
+               if (ctrl->generic.type == CTRL_FILESELECT)\r
+                   [b setTitle:@"Browse..."];\r
+               else\r
+                   [b setTitle:@"Change..."];\r
+               // [b setKeyEquivalent:somethingorother];\r
+               // [b setTarget:somethingorother];\r
+               // [b setAction:somethingorother];\r
+               [b sizeToFit];\r
+               rect = [b frame];\r
+               [parent addSubview:b];\r
+\r
+               c->button = b;\r
+\r
+               if (kh < rect.size.height)\r
+                   kh = rect.size.height;\r
+               ch += VSPACING + kh;\r
+               if (cw < rect.size.width * 4)\r
+                   cw = rect.size.width * 4;\r
+           }\r
+           break;\r
+         case CTRL_LISTBOX:\r
+           {\r
+               int listp = ctrl->listbox.percentwidth;\r
+               int labelp = listp == 100 ? 100 : 100 - listp;\r
+               NSTextField *tf;\r
+               NSPopUpButton *pb;\r
+               NSTableView *tv;\r
+               NSScrollView *sv;\r
+\r
+               if (ctrl->generic.label) {\r
+                   tf = [[NSTextField alloc]\r
+                         initWithFrame:NSMakeRect(0,0,1,1)];\r
+                   [tf setEditable:NO];\r
+                   [tf setSelectable:NO];\r
+                   [tf setBordered:NO];\r
+                   [tf setDrawsBackground:NO];\r
+                   [tf setStringValue:\r
+                    [NSString stringWithCString:ctrl->generic.label]];\r
+                   [tf sizeToFit];\r
+                   rect = [tf frame];\r
+                   [parent addSubview:tf];\r
+                   c->label = tf;\r
+\r
+                   cw = rect.size.width;\r
+                   ch = rect.size.height;\r
+               } else {\r
+                   cw = 0;\r
+                   ch = -VSPACING;    /* compensate for next advance */\r
+               }\r
+\r
+               if (ctrl->listbox.height == 0) {\r
+                   pb = [[NSPopUpButton alloc]\r
+                         initWithFrame:NSMakeRect(0,0,1,1)];\r
+                   [pb sizeToFit];\r
+                   rect = [pb frame];\r
+                   [parent addSubview:pb];\r
+                   c->popupbutton = pb;\r
+\r
+                   [pb setTarget:d->rec];\r
+                   [pb setAction:@selector(popupMenuSelected:)];\r
+                   add_widget(d, c, pb);\r
+               } else {\r
+                   assert(listp == 100);\r
+                   if (ctrl->listbox.draglist) {\r
+                       int bi;\r
+\r
+                       listp = 75;\r
+\r
+                       for (bi = 0; bi < 2; bi++) {\r
+                           NSButton *b;\r
+                           b = [[MyButton alloc]\r
+                                initWithFrame:NSMakeRect(0, 0, 1, 1)];\r
+                           [b setBezelStyle:NSRoundedBezelStyle];\r
+                           if (bi == 0)\r
+                               [b setTitle:@"Up"];\r
+                           else\r
+                               [b setTitle:@"Down"];\r
+                           [b sizeToFit];\r
+                           rect = [b frame];\r
+                           [parent addSubview:b];\r
+\r
+                           if (bi == 0)\r
+                               c->button = b;\r
+                           else\r
+                               c->button2 = b;\r
+\r
+                           [b setTarget:d->rec];\r
+                           [b setAction:@selector(dragListButton:)];\r
+                           add_widget(d, c, b);\r
+\r
+                           if (cw < rect.size.width * 4)\r
+                               cw = rect.size.width * 4;\r
+                       }\r
+                   }\r
+\r
+                   sv = [[NSScrollView alloc] initWithFrame:\r
+                         NSMakeRect(20,20,10,10)];\r
+                   [sv setBorderType:NSLineBorder];\r
+                   tv = [[NSTableView alloc] initWithFrame:[sv frame]];\r
+                   [[tv headerView] setFrame:NSMakeRect(0,0,0,0)];\r
+                   [sv setDocumentView:tv];\r
+                   [parent addSubview:sv];\r
+                   [sv setHasVerticalScroller:YES];\r
+                   [sv setAutohidesScrollers:YES];\r
+                   [tv setAllowsColumnReordering:NO];\r
+                   [tv setAllowsColumnResizing:NO];\r
+                   [tv setAllowsMultipleSelection:ctrl->listbox.multisel];\r
+                   [tv setAllowsEmptySelection:YES];\r
+                   [tv setAllowsColumnSelection:YES];\r
+                   [tv setDataSource:[[MyTableSource alloc] init]];\r
+                   rect = [tv frame];\r
+                   /*\r
+                    * For some reason this consistently comes out\r
+                    * one short. Add one.\r
+                    */\r
+                   rect.size.height = (ctrl->listbox.height+1)*[tv rowHeight];\r
+                   [sv setFrame:rect];\r
+                   c->tableview = tv;\r
+                   c->scrollview = sv;\r
+\r
+                   [tv setDelegate:d->rec];\r
+                   [tv setTarget:d->rec];\r
+                   [tv setDoubleAction:@selector(listDoubleClicked:)];\r
+                   add_widget(d, c, tv);\r
+               }\r
+\r
+               if (c->tableview) {\r
+                   int ncols, *percentages;\r
+                   int hundred = 100;\r
+\r
+                   if (ctrl->listbox.ncols) {\r
+                       ncols = ctrl->listbox.ncols;\r
+                       percentages = ctrl->listbox.percentages;\r
+                   } else {\r
+                       ncols = 1;\r
+                       percentages = &hundred;\r
+                   }\r
+\r
+                   for (j = 0; j < ncols; j++) {\r
+                       NSTableColumn *col;\r
+\r
+                       col = [[NSTableColumn alloc] initWithIdentifier:\r
+                              [NSNumber numberWithInt:j]];\r
+                       [c->tableview addTableColumn:col];\r
+                   }\r
+               }\r
+\r
+               if (labelp == 100) {\r
+                   /* the list and its label are vertically separated */\r
+                   ch += VSPACING + rect.size.height;\r
+               } else {\r
+                   /* the list and its label are horizontally separated */\r
+                   if (ch < rect.size.height)\r
+                       ch = rect.size.height;\r
+               }\r
+\r
+               if (cw < rect.size.width * 100 / listp)\r
+                   cw = rect.size.width * 100 / listp;\r
+           }\r
+           break;\r
+       }\r
+\r
+       /*\r
+        * Update the width and height data for the control we've\r
+        * just created.\r
+        */\r
+       ytop = 0;\r
+\r
+       for (j = colstart; j < colend; j++) {\r
+           if (ytop < cypos[j])\r
+               ytop = cypos[j];\r
+       }\r
+\r
+       for (j = colstart; j < colend; j++)\r
+           cypos[j] = ytop + ch + VSPACING;\r
+\r
+       if (hmin < ytop + ch)\r
+           hmin = ytop + ch;\r
+\r
+       wthis = (cw + HSPACING) * 100 / (ccw[colend] - ccw[colstart]);\r
+       wthis -= HSPACING;\r
+\r
+       if (wmin < wthis)\r
+           wmin = wthis;\r
+    }\r
+\r
+    if (*s->boxname) {\r
+       /*\r
+        * Add a bit to the width and height for the box.\r
+        */\r
+       wmin += boxw;\r
+       hmin += boxh;\r
+    }\r
+\r
+    //printf("For controlset %s/%s, returning w=%d h=%d\n",\r
+    //       s->pathname, s->boxname, wmin, hmin);\r
+    *minw = wmin;\r
+    *minh = hmin;\r
+}\r
+\r
+int place_ctrls(void *dv, struct controlset *s, int leftx, int topy,\r
+               int width)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    int ccw[100];                     /* cumulative column widths */\r
+    int cypos[100];\r
+    int ncols;\r
+    int i, j, ret;\r
+    int boxh = 0, boxw = 0;\r
+\r
+    if (!s->boxname && s->boxtitle) {\r
+        /* Size and place the panel title. */\r
+\r
+       NSTextField *tf = find_box(d, s);\r
+       NSRect rect;\r
+\r
+       rect = [tf frame];\r
+       [tf setFrame:NSMakeRect(leftx, topy-rect.size.height,\r
+                               width, rect.size.height)];\r
+       return rect.size.height;\r
+    }\r
+\r
+    if (*s->boxname) {\r
+       NSRect rect, tmprect;\r
+       NSBox *box = find_box(d, s);\r
+\r
+       assert(box != NULL);\r
+       tmprect = [box frame];\r
+       [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];\r
+       rect = [box frame];\r
+       [box setFrame:tmprect];\r
+       boxw = rect.size.width - 100;\r
+       boxh = rect.size.height - 100;\r
+       if (s->boxtitle)\r
+           boxh += [[box titleFont] pointSize];\r
+       topy -= boxh;\r
+       width -= boxw;\r
+    }\r
+\r
+    ncols = 1;\r
+    ccw[0] = 0;\r
+    ccw[1] = 100;\r
+    cypos[0] = topy;\r
+    ret = 0;\r
+\r
+    /*\r
+     * Now iterate through the controls themselves, placing them\r
+     * appropriately.\r
+     */\r
+    for (i = 0; i < s->ncontrols; i++) {\r
+       union control *ctrl = s->ctrls[i];\r
+       struct fe_ctrl *c;\r
+       int colstart = COLUMN_START(ctrl->generic.column);\r
+       int colspan = COLUMN_SPAN(ctrl->generic.column);\r
+       int colend = colstart + colspan;\r
+       int xthis, ythis, wthis, ch;\r
+       NSRect rect;\r
+\r
+        switch (ctrl->generic.type) {\r
+          case CTRL_COLUMNS:\r
+           for (j = 1; j < ncols; j++)\r
+               if (cypos[0] > cypos[j])\r
+                   cypos[0] = cypos[j];\r
+\r
+           assert(ctrl->columns.ncols < lenof(ccw));\r
+\r
+           ccw[0] = 0;\r
+           for (j = 0; j < ctrl->columns.ncols; j++) {\r
+               ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?\r
+                                    ctrl->columns.percentages[j] : 100);\r
+               cypos[j] = cypos[0];\r
+           }\r
+\r
+           ncols = ctrl->columns.ncols;\r
+\r
+            continue;                  /* no actual control created */\r
+          case CTRL_TABDELAY:\r
+            continue;                  /* nothing to do here, move along */\r
+       }\r
+\r
+       c = fe_ctrl_byctrl(d, ctrl);\r
+\r
+       ch = 0;\r
+       ythis = topy;\r
+\r
+       for (j = colstart; j < colend; j++) {\r
+           if (ythis > cypos[j])\r
+               ythis = cypos[j];\r
+       }\r
+\r
+       xthis = (width + HSPACING) * ccw[colstart] / 100;\r
+       wthis = (width + HSPACING) * ccw[colend] / 100 - HSPACING - xthis;\r
+       xthis += leftx;\r
+\r
+        switch (ctrl->generic.type) {\r
+          case CTRL_BUTTON:\r
+         case CTRL_CHECKBOX:\r
+           rect = [c->button frame];\r
+           [c->button setFrame:NSMakeRect(xthis,ythis-rect.size.height,wthis,\r
+                                          rect.size.height)];\r
+           ch = rect.size.height;\r
+           break;\r
+         case CTRL_EDITBOX:\r
+           {\r
+               int editp = ctrl->editbox.percentwidth;\r
+               int labelp = editp == 100 ? 100 : 100 - editp;\r
+               int lheight, theight, rheight, ynext, editw;\r
+               NSControl *edit = (c->editbox ? c->editbox : c->combobox);\r
+\r
+               rect = [c->label frame];\r
+               lheight = rect.size.height;\r
+               rect = [edit frame];\r
+               theight = rect.size.height;\r
+\r
+               if (editp == 100)\r
+                   rheight = lheight;\r
+               else\r
+                   rheight = (lheight < theight ? theight : lheight);\r
+\r
+               [c->label setFrame:\r
+                NSMakeRect(xthis, ythis-(rheight+lheight)/2,\r
+                           (wthis + HSPACING) * labelp / 100 - HSPACING,\r
+                           lheight)];\r
+               if (editp == 100) {\r
+                   ynext = ythis - rheight - VSPACING;\r
+                   rheight = theight;\r
+               } else {\r
+                   ynext = ythis;\r
+               }\r
+\r
+               editw = (wthis + HSPACING) * editp / 100 - HSPACING;\r
+\r
+               [edit setFrame:\r
+                NSMakeRect(xthis+wthis-editw, ynext-(rheight+theight)/2,\r
+                           editw, theight)];\r
+\r
+               ch = (ythis - ynext) + theight;\r
+           }\r
+           break;\r
+          case CTRL_TEXT:\r
+           [c->textview setFrame:NSMakeRect(xthis, 0, wthis, 1)];\r
+           [c->textview sizeToFit];\r
+           rect = [c->textview frame];\r
+           [c->textview setFrame:NSMakeRect(xthis, ythis-rect.size.height,\r
+                                            wthis, rect.size.height)];\r
+           ch = rect.size.height;\r
+           break;\r
+         case CTRL_RADIO:\r
+           {\r
+               int j, ynext;\r
+\r
+               if (c->label) {\r
+                   rect = [c->label frame];\r
+                   [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,\r
+                                                 wthis,rect.size.height)];\r
+                   ynext = ythis - rect.size.height - VSPACING;\r
+               } else\r
+                   ynext = ythis;\r
+\r
+               for (j = 0; j < ctrl->radio.nbuttons; j++) {\r
+                   int col = j % ctrl->radio.ncolumns;\r
+                   int ncols;\r
+                   int lx,rx;\r
+\r
+                   if (j == ctrl->radio.nbuttons - 1)\r
+                       ncols = ctrl->radio.ncolumns - col;\r
+                   else\r
+                       ncols = 1;\r
+\r
+                   lx = (wthis + HSPACING) * col / ctrl->radio.ncolumns;\r
+                   rx = ((wthis + HSPACING) *\r
+                         (col+ncols) / ctrl->radio.ncolumns) - HSPACING;\r
+\r
+                   /*\r
+                    * Set the frame size.\r
+                    */\r
+                   rect = [c->radiobuttons[j] frame];\r
+                   [c->radiobuttons[j] setFrame:\r
+                    NSMakeRect(lx+xthis, ynext-rect.size.height,\r
+                               rx-lx, rect.size.height)];\r
+\r
+                   /*\r
+                    * Advance to next line if we're in the last\r
+                    * column.\r
+                    */\r
+                   if (col + ncols == ctrl->radio.ncolumns)\r
+                       ynext -= rect.size.height + VSPACING;\r
+               }\r
+               ch = (ythis - ynext) - VSPACING;\r
+           }\r
+           break;\r
+         case CTRL_FILESELECT:\r
+         case CTRL_FONTSELECT:\r
+           {\r
+               int ynext, eh, bh, th, mx;\r
+\r
+               rect = [c->label frame];\r
+               [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,\r
+                                             wthis,rect.size.height)];\r
+               ynext = ythis - rect.size.height - VSPACING;\r
+\r
+               rect = [c->editbox frame];\r
+               eh = rect.size.height;\r
+               rect = [c->button frame];\r
+               bh = rect.size.height;\r
+               th = (eh > bh ? eh : bh);\r
+\r
+               mx = (wthis + HSPACING) * 3 / 4 - HSPACING;\r
+\r
+               [c->editbox setFrame:\r
+                NSMakeRect(xthis, ynext-(th+eh)/2, mx, eh)];\r
+               [c->button setFrame:\r
+                NSMakeRect(xthis+mx+HSPACING, ynext-(th+bh)/2,\r
+                           wthis-mx-HSPACING, bh)];\r
+\r
+               ch = (ythis - ynext) + th + VSPACING;\r
+           }\r
+           break;\r
+         case CTRL_LISTBOX:\r
+           {\r
+               int listp = ctrl->listbox.percentwidth;\r
+               int labelp = listp == 100 ? 100 : 100 - listp;\r
+               int lheight, theight, rheight, ynext, listw, xlist;\r
+               NSControl *list = (c->scrollview ? (id)c->scrollview :\r
+                                  (id)c->popupbutton);\r
+\r
+               if (ctrl->listbox.draglist) {\r
+                   assert(listp == 100);\r
+                   listp = 75;\r
+               }\r
+\r
+               rect = [list frame];\r
+               theight = rect.size.height;\r
+\r
+               if (c->label) {\r
+                   rect = [c->label frame];\r
+                   lheight = rect.size.height;\r
+\r
+                   if (labelp == 100)\r
+                       rheight = lheight;\r
+                   else\r
+                       rheight = (lheight < theight ? theight : lheight);\r
+\r
+                   [c->label setFrame:\r
+                    NSMakeRect(xthis, ythis-(rheight+lheight)/2,\r
+                               (wthis + HSPACING) * labelp / 100 - HSPACING,\r
+                               lheight)];\r
+                   if (labelp == 100) {\r
+                       ynext = ythis - rheight - VSPACING;\r
+                       rheight = theight;\r
+                   } else {\r
+                       ynext = ythis;\r
+                   }\r
+               } else {\r
+                   ynext = ythis;\r
+                   rheight = theight;\r
+               }\r
+\r
+               listw = (wthis + HSPACING) * listp / 100 - HSPACING;\r
+\r
+               if (labelp == 100)\r
+                   xlist = xthis;\r
+               else\r
+                   xlist = xthis+wthis-listw;\r
+\r
+               [list setFrame: NSMakeRect(xlist, ynext-(rheight+theight)/2,\r
+                                          listw, theight)];\r
+\r
+               /*\r
+                * Size the columns for the table view.\r
+                */\r
+               if (c->tableview) {\r
+                   int ncols, *percentages;\r
+                   int hundred = 100;\r
+                   int cpercent = 0, cpixels = 0;\r
+                   NSArray *cols;\r
+\r
+                   if (ctrl->listbox.ncols) {\r
+                       ncols = ctrl->listbox.ncols;\r
+                       percentages = ctrl->listbox.percentages;\r
+                   } else {\r
+                       ncols = 1;\r
+                       percentages = &hundred;\r
+                   }\r
+\r
+                   cols = [c->tableview tableColumns];\r
+\r
+                   for (j = 0; j < ncols; j++) {\r
+                       NSTableColumn *col = [cols objectAtIndex:j];\r
+                       int newcpixels;\r
+\r
+                       cpercent += percentages[j];\r
+                       newcpixels = listw * cpercent / 100;\r
+                       [col setWidth:newcpixels-cpixels];\r
+                       cpixels = newcpixels;\r
+                   }\r
+               }\r
+\r
+               ch = (ythis - ynext) + theight;\r
+\r
+               if (c->button) {\r
+                   int b2height, centre;\r
+                   int bx, bw;\r
+\r
+                   /*\r
+                    * Place the Up and Down buttons for a drag list.\r
+                    */\r
+                   assert(c->button2);\r
+\r
+                   rect = [c->button frame];\r
+                   b2height = VSPACING + 2 * rect.size.height;\r
+\r
+                   centre = ynext - rheight/2;\r
+\r
+                   bx = (wthis + HSPACING) * 3 / 4;\r
+                   bw = wthis - bx;\r
+                   bx += leftx;\r
+\r
+                   [c->button setFrame:\r
+                    NSMakeRect(bx, centre+b2height/2-rect.size.height,\r
+                               bw, rect.size.height)];\r
+                   [c->button2 setFrame:\r
+                    NSMakeRect(bx, centre-b2height/2,\r
+                               bw, rect.size.height)];\r
+               }\r
+           }\r
+           break;\r
+       }\r
+\r
+       for (j = colstart; j < colend; j++)\r
+           cypos[j] = ythis - ch - VSPACING;\r
+       if (ret < topy - (ythis - ch))\r
+           ret = topy - (ythis - ch);\r
+    }\r
+\r
+    if (*s->boxname) {\r
+       NSBox *box = find_box(d, s);\r
+       assert(box != NULL);\r
+       [box sizeToFit];\r
+\r
+       if (s->boxtitle) {\r
+           NSRect rect = [box frame];\r
+           rect.size.height += [[box titleFont] pointSize];\r
+           [box setFrame:rect];\r
+       }\r
+\r
+       ret += boxh;\r
+    }\r
+\r
+    //printf("For controlset %s/%s, returning ret=%d\n",\r
+    //       s->pathname, s->boxname, ret);\r
+    return ret;\r
+}\r
+\r
+void select_panel(void *dv, struct controlbox *b, const char *name)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    int i, j, hidden;\r
+    struct controlset *s;\r
+    union control *ctrl;\r
+    struct fe_ctrl *c;\r
+    NSBox *box;\r
+\r
+    for (i = 0; i < b->nctrlsets; i++) {\r
+       s = b->ctrlsets[i];\r
+\r
+       if (*s->pathname) {\r
+           hidden = !strcmp(s->pathname, name) ? NO : YES;\r
+\r
+           if ((box = find_box(d, s)) != NULL) {\r
+               [box setHidden:hidden];\r
+           } else {\r
+               for (j = 0; j < s->ncontrols; j++) {\r
+                   ctrl = s->ctrls[j];\r
+                   c = fe_ctrl_byctrl(d, ctrl);\r
+\r
+                   if (!c)\r
+                       continue;\r
+\r
+                   if (c->label)\r
+                       [c->label setHidden:hidden];\r
+                   if (c->button)\r
+                       [c->button setHidden:hidden];\r
+                   if (c->button2)\r
+                       [c->button2 setHidden:hidden];\r
+                   if (c->editbox)\r
+                       [c->editbox setHidden:hidden];\r
+                   if (c->combobox)\r
+                       [c->combobox setHidden:hidden];\r
+                   if (c->textview)\r
+                       [c->textview setHidden:hidden];\r
+                   if (c->tableview)\r
+                       [c->tableview setHidden:hidden];\r
+                   if (c->scrollview)\r
+                       [c->scrollview setHidden:hidden];\r
+                   if (c->popupbutton)\r
+                       [c->popupbutton setHidden:hidden];\r
+                   if (c->radiobuttons) {\r
+                       int j;\r
+                       for (j = 0; j < c->nradiobuttons; j++)\r
+                           [c->radiobuttons[j] setHidden:hidden];\r
+                   }\r
+                   break;\r
+               }\r
+           }\r
+       }\r
+    }\r
+}\r
+\r
+void dlg_radiobutton_set(union control *ctrl, void *dv, int whichbutton)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+    int j;\r
+\r
+    assert(c->radiobuttons);\r
+    for (j = 0; j < c->nradiobuttons; j++)\r
+       [c->radiobuttons[j] setState:\r
+        (j == whichbutton ? NSOnState : NSOffState)];\r
+}\r
+\r
+int dlg_radiobutton_get(union control *ctrl, void *dv)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+    int j;\r
+\r
+    assert(c->radiobuttons);\r
+    for (j = 0; j < c->nradiobuttons; j++)\r
+       if ([c->radiobuttons[j] state] == NSOnState)\r
+           return j;\r
+\r
+    return 0;                         /* should never reach here */\r
+}\r
+\r
+void dlg_checkbox_set(union control *ctrl, void *dv, int checked)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+\r
+    assert(c->button);\r
+    [c->button setState:(checked ? NSOnState : NSOffState)];\r
+}\r
+\r
+int dlg_checkbox_get(union control *ctrl, void *dv)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+\r
+    assert(c->button);\r
+    return ([c->button state] == NSOnState);\r
+}\r
+\r
+void dlg_editbox_set(union control *ctrl, void *dv, char const *text)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+\r
+    if (c->editbox) {\r
+       [c->editbox setStringValue:[NSString stringWithCString:text]];\r
+    } else {\r
+       assert(c->combobox);\r
+       [c->combobox setStringValue:[NSString stringWithCString:text]];\r
+    }\r
+}\r
+\r
+void dlg_editbox_get(union control *ctrl, void *dv, char *buffer, int length)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+    NSString *str;\r
+\r
+    if (c->editbox) {\r
+       str = [c->editbox stringValue];\r
+    } else {\r
+       assert(c->combobox);\r
+       str = [c->combobox stringValue];\r
+    }\r
+    if (!str)\r
+       str = @"";\r
+\r
+    /* The length parameter to this method doesn't include a trailing NUL */\r
+    [str getCString:buffer maxLength:length-1];\r
+}\r
+\r
+void dlg_listbox_clear(union control *ctrl, void *dv)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+\r
+    if (c->tableview) {\r
+       [[c->tableview dataSource] clear];\r
+       [c->tableview reloadData];\r
+    } else {\r
+       [c->popupbutton removeAllItems];\r
+    }\r
+}\r
+\r
+void dlg_listbox_del(union control *ctrl, void *dv, int index)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+\r
+    if (c->tableview) {\r
+       [[c->tableview dataSource] removestr:index];\r
+       [c->tableview reloadData];\r
+    } else {\r
+       [c->popupbutton removeItemAtIndex:index];\r
+    }\r
+}\r
+\r
+void dlg_listbox_addwithid(union control *ctrl, void *dv,\r
+                          char const *text, int id)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+\r
+    if (c->tableview) {\r
+       [[c->tableview dataSource] add:text withId:id];\r
+       [c->tableview reloadData];\r
+    } else {\r
+       [c->popupbutton addItemWithTitle:[NSString stringWithCString:text]];\r
+       [[c->popupbutton lastItem] setTag:id];\r
+    }\r
+}\r
+\r
+void dlg_listbox_add(union control *ctrl, void *dv, char const *text)\r
+{\r
+    dlg_listbox_addwithid(ctrl, dv, text, -1);\r
+}\r
+\r
+int dlg_listbox_getid(union control *ctrl, void *dv, int index)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+\r
+    if (c->tableview) {\r
+       return [[c->tableview dataSource] getid:index];\r
+    } else {\r
+       return [[c->popupbutton itemAtIndex:index] tag];\r
+    }\r
+}\r
+\r
+int dlg_listbox_index(union control *ctrl, void *dv)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+\r
+    if (c->tableview) {\r
+       return [c->tableview selectedRow];\r
+    } else {\r
+       return [c->popupbutton indexOfSelectedItem];\r
+    }\r
+}\r
+\r
+int dlg_listbox_issel(union control *ctrl, void *dv, int index)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+\r
+    if (c->tableview) {\r
+       return [c->tableview isRowSelected:index];\r
+    } else {\r
+       return [c->popupbutton indexOfSelectedItem] == index;\r
+    }\r
+}\r
+\r
+void dlg_listbox_select(union control *ctrl, void *dv, int index)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+\r
+    if (c->tableview) {\r
+       [c->tableview selectRow:index byExtendingSelection:NO];\r
+    } else {\r
+       [c->popupbutton selectItemAtIndex:index];\r
+    }\r
+}\r
+\r
+void dlg_text_set(union control *ctrl, void *dv, char const *text)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+\r
+    assert(c->textview);\r
+    [c->textview setString:[NSString stringWithCString:text]];\r
+}\r
+\r
+void dlg_label_change(union control *ctrl, void *dlg, char const *text)\r
+{\r
+    /*\r
+     * This function is currently only used by the config box to\r
+     * switch the labels on the host and port boxes between serial\r
+     * and network modes. Since OS X does not (yet?) have a serial\r
+     * back end, this function can safely do nothing for the\r
+     * moment.\r
+     */\r
+}\r
+\r
+void dlg_filesel_set(union control *ctrl, void *dv, Filename fn)\r
+{\r
+    /* FIXME */\r
+}\r
+\r
+void dlg_filesel_get(union control *ctrl, void *dv, Filename *fn)\r
+{\r
+    /* FIXME */\r
+}\r
+\r
+void dlg_fontsel_set(union control *ctrl, void *dv, FontSpec fn)\r
+{\r
+    /* FIXME */\r
+}\r
+\r
+void dlg_fontsel_get(union control *ctrl, void *dv, FontSpec *fn)\r
+{\r
+    /* FIXME */\r
+}\r
+\r
+void dlg_update_start(union control *ctrl, void *dv)\r
+{\r
+    /* FIXME */\r
+}\r
+\r
+void dlg_update_done(union control *ctrl, void *dv)\r
+{\r
+    /* FIXME */\r
+}\r
+\r
+void dlg_set_focus(union control *ctrl, void *dv)\r
+{\r
+    /* FIXME */\r
+}\r
+\r
+union control *dlg_last_focused(union control *ctrl, void *dv)\r
+{\r
+    return NULL; /* FIXME */\r
+}\r
+\r
+void dlg_beep(void *dv)\r
+{\r
+    NSBeep();\r
+}\r
+\r
+void dlg_error_msg(void *dv, char *msg)\r
+{\r
+    /* FIXME */\r
+}\r
+\r
+void dlg_end(void *dv, int value)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    [d->target performSelector:d->action\r
+     withObject:[NSNumber numberWithInt:value]];\r
+}\r
+\r
+void dlg_coloursel_start(union control *ctrl, void *dv,\r
+                        int r, int g, int b)\r
+{\r
+    /* FIXME */\r
+}\r
+\r
+int dlg_coloursel_results(union control *ctrl, void *dv,\r
+                         int *r, int *g, int *b)\r
+{\r
+    return 0; /* FIXME */\r
+}\r
+\r
+void dlg_refresh(union control *ctrl, void *dv)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c;\r
+\r
+    if (ctrl) {\r
+       if (ctrl->generic.handler != NULL)\r
+           ctrl->generic.handler(ctrl, d, d->data, EVENT_REFRESH);\r
+    } else {\r
+       int i;\r
+\r
+       for (i = 0; (c = index234(d->byctrl, i)) != NULL; i++) {\r
+           assert(c->ctrl != NULL);\r
+           if (c->ctrl->generic.handler != NULL)\r
+               c->ctrl->generic.handler(c->ctrl, d,\r
+                                        d->data, EVENT_REFRESH);\r
+       }\r
+    }\r
+}\r
+\r
+void *dlg_get_privdata(union control *ctrl, void *dv)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+    return c->privdata;\r
+}\r
+\r
+void dlg_set_privdata(union control *ctrl, void *dv, void *ptr)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+    c->privdata = ptr;\r
+    c->privdata_needs_free = FALSE;\r
+}\r
+\r
+void *dlg_alloc_privdata(union control *ctrl, void *dv, size_t size)\r
+{\r
+    struct fe_dlg *d = (struct fe_dlg *)dv;\r
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);\r
+    /*\r
+     * This is an internal allocation routine, so it's allowed to\r
+     * use smalloc directly.\r
+     */\r
+    c->privdata = smalloc(size);\r
+    c->privdata_needs_free = TRUE;\r
+    return c->privdata;\r
+}\r