]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - macosx/osxctrls.m
Giant const-correctness patch of doom!
[PuTTY.git] / macosx / osxctrls.m
1 /*
2  * osxctrls.m: OS X implementation of the dialog.h interface.
3  */
4
5 #import <Cocoa/Cocoa.h>
6 #include "putty.h"
7 #include "dialog.h"
8 #include "osxclass.h"
9 #include "tree234.h"
10
11 /*
12  * Still to be implemented:
13  * 
14  *  - file selectors (NSOpenPanel / NSSavePanel)
15  * 
16  *  - font selectors
17  *  - colour selectors
18  *     * both of these have a conceptual oddity in Cocoa that
19  *       you're only supposed to have one per application. But I
20  *       currently expect to be able to have multiple PuTTY config
21  *       boxes on screen at once; what happens if you trigger the
22  *       font selector in each one at the same time?
23  *     * if it comes to that, the _font_ selector can probably be
24  *       managed by other means: nobody is forcing me to implement
25  *       a font selector using a `Change...' button. The portable
26  *       dialog interface gives me the flexibility to do this how I
27  *       want.
28  *     * The colour selector interface, in its present form, is
29  *       more interesting and _if_ a radical change of plan is
30  *       required then it may stretch across the interface into the
31  *       portable side.
32  *     * Before I do anything rash I should start by looking at the
33  *       Mac Classic port and see how it's done there, on the basis
34  *       that Apple seem reasonably unlikely to have invented this
35  *       crazy restriction specifically for OS X.
36  * 
37  *  - focus management
38  *     * I tried using makeFirstResponder to give keyboard focus,
39  *       but it appeared not to work. Try again, and work out how
40  *       it should be done.
41  *     * also look into tab order. Currently pressing Tab suggests
42  *       that only edit boxes and list boxes can get the keyboard
43  *       focus, and that buttons (in all their forms) are unable to
44  *       be driven by the keyboard. Find out for sure.
45  * 
46  *  - dlg_error_msg
47  *     * this may run into the usual aggro with modal dialog boxes.
48  */
49
50 /*
51  * For Cocoa control layout, I need a two-stage process. In stage
52  * one, I allocate all the controls and measure their natural
53  * sizes, which allows me to compute the _minimum_ width and height
54  * of a given section of dialog. Then, in stage two, I lay out the
55  * dialog box as a whole, decide how much each section of the box
56  * needs to receive, and assign it its final size.
57  */
58
59 /*
60  * As yet unsolved issues [FIXME]:
61  * 
62  *  - Sometimes the height returned from create_ctrls and the
63  *    height returned from place_ctrls differ. Find out why. It may
64  *    be harmless (e.g. results of NSTextView being odd), but I
65  *    want to know.
66  * 
67  *  - NSTextViews are indented a bit. It'd be nice to put their
68  *    left margin at the same place as everything else's.
69  * 
70  *  - I don't yet know whether we even _can_ support tab order or
71  *    keyboard shortcuts. If we can't, then fair enough, we can't.
72  *    But if we can, we should.
73  * 
74  *  - I would _really_ like to know of a better way to correct
75  *    NSButton's stupid size estimates than by subclassing it and
76  *    overriding sizeToFit with hard-wired sensible values!
77  * 
78  *  - Speaking of stupid size estimates, the amount by which I'm
79  *    adjusting a titled NSBox (currently equal to the point size
80  *    of its title font) looks as if it isn't _quite_ enough.
81  *    Figure out what the real amount should be and use it.
82  * 
83  *  - I don't understand why there's always a scrollbar displayed
84  *    in each list box. I thought I told it to autohide scrollers?
85  * 
86  *  - Why do I have to fudge list box heights by adding one? (Might
87  *    it be to do with the missing header view?)
88  */
89
90 /*
91  * Subclass of NSButton which corrects the fact that the normal
92  * one's sizeToFit method persistently returns 32 as its height,
93  * which is simply a lie. I have yet to work out a better
94  * alternative than hard-coding the real heights.
95  */
96 @interface MyButton : NSButton
97 {
98     int minht;
99 }
100 @end
101 @implementation MyButton
102 - (id)initWithFrame:(NSRect)r
103 {
104     self = [super initWithFrame:r];
105     minht = 25;
106     return self;
107 }
108 - (void)setButtonType:(NSButtonType)t
109 {
110     if (t == NSRadioButton || t == NSSwitchButton)
111         minht = 18;
112     else
113         minht = 25;
114     [super setButtonType:t];
115 }
116 - (void)sizeToFit
117 {
118     NSRect r;
119     [super sizeToFit];
120     r = [self frame];
121     r.size.height = minht;
122     [self setFrame:r];
123 }
124 @end
125
126 /*
127  * Class used as the data source for NSTableViews.
128  */
129 @interface MyTableSource : NSObject
130 {
131     tree234 *tree;
132 }
133 - (id)init;
134 - (void)add:(const char *)str withId:(int)id;
135 - (int)getid:(int)index;
136 - (void)swap:(int)index1 with:(int)index2;
137 - (void)removestr:(int)index;
138 - (void)clear;
139 @end
140 @implementation MyTableSource
141 - (id)init
142 {
143     self = [super init];
144     tree = newtree234(NULL);
145     return self;
146 }
147 - (void)dealloc
148 {
149     char *p;
150     while ((p = delpos234(tree, 0)) != NULL)
151         sfree(p);
152     freetree234(tree);
153     [super dealloc];
154 }
155 - (void)add:(const char *)str withId:(int)id
156 {
157     addpos234(tree, dupprintf("%d\t%s", id, str), count234(tree));
158 }
159 - (int)getid:(int)index
160 {
161     char *p = index234(tree, index);
162     return atoi(p);
163 }
164 - (void)removestr:(int)index
165 {
166     char *p = delpos234(tree, index);
167     sfree(p);
168 }
169 - (void)swap:(int)index1 with:(int)index2
170 {
171     char *p1, *p2;
172
173     if (index1 > index2) {
174         int t = index1; index1 = index2; index2 = t;
175     }
176
177     /* delete later one first so it doesn't affect index of earlier one */
178     p2 = delpos234(tree, index2);
179     p1 = delpos234(tree, index1);
180
181     /* now insert earlier one before later one for the inverse reason */
182     addpos234(tree, p2, index1);
183     addpos234(tree, p1, index2);
184 }
185 - (void)clear
186 {
187     char *p;
188     while ((p = delpos234(tree, 0)) != NULL)
189         sfree(p);
190 }
191 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
192 {
193     return count234(tree);
194 }
195 - (id)tableView:(NSTableView *)aTableView
196     objectValueForTableColumn:(NSTableColumn *)aTableColumn
197     row:(int)rowIndex
198 {
199     int j = [[aTableColumn identifier] intValue];
200     char *p = index234(tree, rowIndex);
201
202     while (j >= 0) {
203         p += strcspn(p, "\t");
204         if (*p) p++;
205         j--;
206     }
207
208     return [NSString stringWithCString:p length:strcspn(p, "\t")];
209 }
210 @end
211
212 /*
213  * Object to receive messages from various control classes.
214  */
215 @class Receiver;
216
217 struct fe_dlg {
218     NSWindow *window;
219     NSObject *target;
220     SEL action;
221     tree234 *byctrl;
222     tree234 *bywidget;
223     tree234 *boxes;
224     void *data;                        /* passed to portable side */
225     Receiver *rec;
226 };
227
228 @interface Receiver : NSObject
229 {
230     struct fe_dlg *d;
231 }
232 - (id)initWithStruct:(struct fe_dlg *)aStruct;
233 @end
234
235 struct fe_ctrl {
236     union control *ctrl;
237     NSButton *button, *button2;
238     NSTextField *label, *editbox;
239     NSComboBox *combobox;
240     NSButton **radiobuttons;
241     NSTextView *textview;
242     NSPopUpButton *popupbutton;
243     NSTableView *tableview;
244     NSScrollView *scrollview;
245     int nradiobuttons;
246 };
247
248 static int fe_ctrl_cmp_by_ctrl(void *av, void *bv)
249 {
250     struct fe_ctrl *a = (struct fe_ctrl *)av;
251     struct fe_ctrl *b = (struct fe_ctrl *)bv;
252
253     if (a->ctrl < b->ctrl)
254         return -1;
255     if (a->ctrl > b->ctrl)
256         return +1;
257     return 0;
258 }
259
260 static int fe_ctrl_find_by_ctrl(void *av, void *bv)
261 {
262     union control *a = (union control *)av;
263     struct fe_ctrl *b = (struct fe_ctrl *)bv;
264
265     if (a < b->ctrl)
266         return -1;
267     if (a > b->ctrl)
268         return +1;
269     return 0;
270 }
271
272 struct fe_box {
273     struct controlset *s;
274     id box;
275 };
276
277 static int fe_boxcmp(void *av, void *bv)
278 {
279     struct fe_box *a = (struct fe_box *)av;
280     struct fe_box *b = (struct fe_box *)bv;
281
282     if (a->s < b->s)
283         return -1;
284     if (a->s > b->s)
285         return +1;
286     return 0;
287 }
288
289 static int fe_boxfind(void *av, void *bv)
290 {
291     struct controlset *a = (struct controlset *)av;
292     struct fe_box *b = (struct fe_box *)bv;
293
294     if (a < b->s)
295         return -1;
296     if (a > b->s)
297         return +1;
298     return 0;
299 }
300
301 struct fe_backwards {                  /* map Cocoa widgets back to fe_ctrls */
302     id widget;
303     struct fe_ctrl *c;
304 };
305
306 static int fe_backwards_cmp_by_widget(void *av, void *bv)
307 {
308     struct fe_backwards *a = (struct fe_backwards *)av;
309     struct fe_backwards *b = (struct fe_backwards *)bv;
310
311     if (a->widget < b->widget)
312         return -1;
313     if (a->widget > b->widget)
314         return +1;
315     return 0;
316 }
317
318 static int fe_backwards_find_by_widget(void *av, void *bv)
319 {
320     id a = (id)av;
321     struct fe_backwards *b = (struct fe_backwards *)bv;
322
323     if (a < b->widget)
324         return -1;
325     if (a > b->widget)
326         return +1;
327     return 0;
328 }
329
330 static struct fe_ctrl *fe_ctrl_new(union control *ctrl)
331 {
332     struct fe_ctrl *c;
333
334     c = snew(struct fe_ctrl);
335     c->ctrl = ctrl;
336
337     c->button = c->button2 = nil;
338     c->label = nil;
339     c->editbox = nil;
340     c->combobox = nil;
341     c->textview = nil;
342     c->popupbutton = nil;
343     c->tableview = nil;
344     c->scrollview = nil;
345     c->radiobuttons = NULL;
346     c->nradiobuttons = 0;
347
348     return c;
349 }
350
351 static void fe_ctrl_free(struct fe_ctrl *c)
352 {
353     sfree(c->radiobuttons);
354     sfree(c);
355 }
356
357 static struct fe_ctrl *fe_ctrl_byctrl(struct fe_dlg *d, union control *ctrl)
358 {
359     return find234(d->byctrl, ctrl, fe_ctrl_find_by_ctrl);
360 }
361
362 static void add_box(struct fe_dlg *d, struct controlset *s, id box)
363 {
364     struct fe_box *b = snew(struct fe_box);
365     b->box = box;
366     b->s = s;
367     add234(d->boxes, b);
368 }
369
370 static id find_box(struct fe_dlg *d, struct controlset *s)
371 {
372     struct fe_box *b = find234(d->boxes, s, fe_boxfind);
373     return b ? b->box : NULL;
374 }
375
376 static void add_widget(struct fe_dlg *d, struct fe_ctrl *c, id widget)
377 {
378     struct fe_backwards *b = snew(struct fe_backwards);
379     b->widget = widget;
380     b->c = c;
381     add234(d->bywidget, b);
382 }
383
384 static struct fe_ctrl *find_widget(struct fe_dlg *d, id widget)
385 {
386     struct fe_backwards *b = find234(d->bywidget, widget,
387                                      fe_backwards_find_by_widget);
388     return b ? b->c : NULL;
389 }
390
391 void *fe_dlg_init(void *data, NSWindow *window, NSObject *target, SEL action)
392 {
393     struct fe_dlg *d;
394
395     d = snew(struct fe_dlg);
396     d->window = window;
397     d->target = target;
398     d->action = action;
399     d->byctrl = newtree234(fe_ctrl_cmp_by_ctrl);
400     d->bywidget = newtree234(fe_backwards_cmp_by_widget);
401     d->boxes = newtree234(fe_boxcmp);
402     d->data = data;
403     d->rec = [[Receiver alloc] initWithStruct:d];
404
405     return d;
406 }
407
408 void fe_dlg_free(void *dv)
409 {
410     struct fe_dlg *d = (struct fe_dlg *)dv;
411     struct fe_ctrl *c;
412     struct fe_box *b;
413
414     while ( (c = delpos234(d->byctrl, 0)) != NULL )
415         fe_ctrl_free(c);
416     freetree234(d->byctrl);
417
418     while ( (c = delpos234(d->bywidget, 0)) != NULL )
419         sfree(c);
420     freetree234(d->bywidget);
421
422     while ( (b = delpos234(d->boxes, 0)) != NULL )
423         sfree(b);
424     freetree234(d->boxes);
425
426     [d->rec release];
427
428     sfree(d);
429 }
430
431 @implementation Receiver
432 - (id)initWithStruct:(struct fe_dlg *)aStruct
433 {
434     self = [super init];
435     d = aStruct;
436     return self;
437 }
438 - (void)buttonPushed:(id)sender
439 {
440     struct fe_ctrl *c = find_widget(d, sender);
441
442     assert(c && c->ctrl->generic.type == CTRL_BUTTON);
443     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
444 }
445 - (void)checkboxChanged:(id)sender
446 {
447     struct fe_ctrl *c = find_widget(d, sender);
448
449     assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
450     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
451 }
452 - (void)radioChanged:(id)sender
453 {
454     struct fe_ctrl *c = find_widget(d, sender);
455     int j;
456
457     assert(c && c->radiobuttons);
458     for (j = 0; j < c->nradiobuttons; j++)
459         if (sender != c->radiobuttons[j])
460             [c->radiobuttons[j] setState:NSOffState];
461     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
462 }
463 - (void)popupMenuSelected:(id)sender
464 {
465     struct fe_ctrl *c = find_widget(d, sender);
466     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
467 }
468 - (void)controlTextDidChange:(NSNotification *)notification
469 {
470     id widget = [notification object];
471     struct fe_ctrl *c = find_widget(d, widget);
472     assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
473     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
474 }
475 - (void)controlTextDidEndEditing:(NSNotification *)notification
476 {
477     id widget = [notification object];
478     struct fe_ctrl *c = find_widget(d, widget);
479     assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
480     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_REFRESH);
481 }
482 - (void)tableViewSelectionDidChange:(NSNotification *)notification
483 {
484     id widget = [notification object];
485     struct fe_ctrl *c = find_widget(d, widget);
486     assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
487     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_SELCHANGE);
488 }
489 - (BOOL)tableView:(NSTableView *)aTableView
490     shouldEditTableColumn:(NSTableColumn *)aTableColumn
491     row:(int)rowIndex
492 {
493     return NO;                         /* no editing permitted */
494 }
495 - (void)listDoubleClicked:(id)sender
496 {
497     struct fe_ctrl *c = find_widget(d, sender);
498     assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
499     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
500 }
501 - (void)dragListButton:(id)sender
502 {
503     struct fe_ctrl *c = find_widget(d, sender);
504     int direction, row, nrows;
505     assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
506            c->ctrl->listbox.draglist);
507
508     if (sender == c->button)
509         direction = -1;                /* up */
510     else
511         direction = +1;                /* down */
512
513     row = [c->tableview selectedRow];
514     nrows = [c->tableview numberOfRows];
515
516     if (row + direction < 0 || row + direction >= nrows) {
517         NSBeep();
518         return;
519     }
520
521     [[c->tableview dataSource] swap:row with:row+direction];
522     [c->tableview reloadData];
523     [c->tableview selectRow:row+direction byExtendingSelection:NO];
524
525     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
526 }
527 @end
528
529 void create_ctrls(void *dv, NSView *parent, struct controlset *s,
530                   int *minw, int *minh)
531 {
532     struct fe_dlg *d = (struct fe_dlg *)dv;
533     int ccw[100];                      /* cumulative column widths */
534     int cypos[100];
535     int ncols;
536     int wmin = 0, hmin = 0;
537     int i, j, cw, ch;
538     NSRect rect;
539     NSFont *textviewfont = nil;
540     int boxh = 0, boxw = 0;
541
542     if (!s->boxname && s->boxtitle) {
543         /* This controlset is a panel title. */
544
545         NSTextField *tf;
546
547         tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
548         [tf setEditable:NO];
549         [tf setSelectable:NO];
550         [tf setBordered:NO];
551         [tf setDrawsBackground:NO];
552         [tf setStringValue:[NSString stringWithCString:s->boxtitle]];
553         [tf sizeToFit];
554         rect = [tf frame];
555         [parent addSubview:tf];
556
557         /*
558          * I'm going to store this NSTextField in the boxes tree,
559          * because I really can't face having a special tree234
560          * mapping controlsets to panel titles.
561          */
562         add_box(d, s, tf);
563
564         *minw = rect.size.width;
565         *minh = rect.size.height;
566
567         return;
568     }
569
570     if (*s->boxname) {
571         /*
572          * Create an NSBox to contain this subset of controls.
573          */
574         NSBox *box;
575         NSRect tmprect;
576
577         box = [[NSBox alloc] initWithFrame:NSMakeRect(0,0,1,1)];
578         if (s->boxtitle)
579             [box setTitle:[NSString stringWithCString:s->boxtitle]];
580         else
581             [box setTitlePosition:NSNoTitle];
582         add_box(d, s, box);
583         tmprect = [box frame];
584         [box setContentViewMargins:NSMakeSize(20,20)];
585         [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
586         rect = [box frame];
587         [box setFrame:tmprect];
588         boxh = (int)(rect.size.height - 100);
589         boxw = (int)(rect.size.width - 100);
590         [parent addSubview:box];
591
592         if (s->boxtitle)
593             boxh += [[box titleFont] pointSize];
594
595         /*
596          * All subsequent controls will be placed within this box.
597          */
598         parent = box;
599     }
600
601     ncols = 1;
602     ccw[0] = 0;
603     ccw[1] = 100;
604     cypos[0] = 0;
605
606     /*
607      * Now iterate through the controls themselves, create them,
608      * and add their width and height to the overall width/height
609      * calculation.
610      */
611     for (i = 0; i < s->ncontrols; i++) {
612         union control *ctrl = s->ctrls[i];
613         struct fe_ctrl *c;
614         int colstart = COLUMN_START(ctrl->generic.column);
615         int colspan = COLUMN_SPAN(ctrl->generic.column);
616         int colend = colstart + colspan;
617         int ytop, wthis;
618
619         switch (ctrl->generic.type) {
620           case CTRL_COLUMNS:
621             for (j = 1; j < ncols; j++)
622                 if (cypos[0] < cypos[j])
623                     cypos[0] = cypos[j];
624
625             assert(ctrl->columns.ncols < lenof(ccw));
626
627             ccw[0] = 0;
628             for (j = 0; j < ctrl->columns.ncols; j++) {
629                 ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
630                                      ctrl->columns.percentages[j] : 100);
631                 cypos[j] = cypos[0];
632             }
633
634             ncols = ctrl->columns.ncols;
635
636             continue;                  /* no actual control created */
637           case CTRL_TABDELAY:
638             /*
639              * I'm currently uncertain that we can implement tab
640              * order in OS X.
641              */
642             continue;                  /* no actual control created */
643         }
644
645         c = fe_ctrl_new(ctrl);
646         add234(d->byctrl, c);
647
648         cw = ch = 0;
649
650         switch (ctrl->generic.type) {
651           case CTRL_BUTTON:
652           case CTRL_CHECKBOX:
653             {
654                 NSButton *b;
655
656                 b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
657                 [b setBezelStyle:NSRoundedBezelStyle];
658                 if (ctrl->generic.type == CTRL_CHECKBOX)
659                     [b setButtonType:NSSwitchButton];
660                 [b setTitle:[NSString stringWithCString:ctrl->generic.label]];
661                 if (ctrl->button.isdefault)
662                     [b setKeyEquivalent:@"\r"];
663                 else if (ctrl->button.iscancel)
664                     [b setKeyEquivalent:@"\033"];
665                 [b sizeToFit];
666                 rect = [b frame];
667
668                 [parent addSubview:b];
669
670                 [b setTarget:d->rec];
671                 if (ctrl->generic.type == CTRL_CHECKBOX)
672                     [b setAction:@selector(checkboxChanged:)];
673                 else
674                     [b setAction:@selector(buttonPushed:)];
675                 add_widget(d, c, b);
676
677                 c->button = b;
678
679                 cw = rect.size.width;
680                 ch = rect.size.height;
681             }
682             break;
683           case CTRL_EDITBOX:
684             {
685                 int editp = ctrl->editbox.percentwidth;
686                 int labelp = editp == 100 ? 100 : 100 - editp;
687                 NSTextField *tf;
688                 NSComboBox *cb;
689
690                 tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
691                 [tf setEditable:NO];
692                 [tf setSelectable:NO];
693                 [tf setBordered:NO];
694                 [tf setDrawsBackground:NO];
695                 [tf setStringValue:[NSString
696                                     stringWithCString:ctrl->generic.label]];
697                 [tf sizeToFit];
698                 rect = [tf frame];
699                 [parent addSubview:tf];
700                 c->label = tf;
701
702                 cw = rect.size.width * 100 / labelp;
703                 ch = rect.size.height;
704
705                 if (ctrl->editbox.has_list) {
706                     cb = [[NSComboBox alloc]
707                           initWithFrame:NSMakeRect(0,0,1,1)];
708                     [cb setStringValue:@"x"];
709                     [cb sizeToFit];
710                     rect = [cb frame];
711                     [parent addSubview:cb];
712                     c->combobox = cb;
713                 } else {
714                     if (ctrl->editbox.password)
715                         tf = [NSSecureTextField alloc];
716                     else
717                         tf = [NSTextField alloc];
718
719                     tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
720                     [tf setEditable:YES];
721                     [tf setSelectable:YES];
722                     [tf setBordered:YES];
723                     [tf setStringValue:@"x"];
724                     [tf sizeToFit];
725                     rect = [tf frame];
726                     [parent addSubview:tf];
727                     c->editbox = tf;
728
729                     [tf setDelegate:d->rec];
730                     add_widget(d, c, tf);
731                 }
732
733                 if (editp == 100) {
734                     /* the edit box and its label are vertically separated */
735                     ch += VSPACING + rect.size.height;
736                 } else {
737                     /* the edit box and its label are horizontally separated */
738                     if (ch < rect.size.height)
739                         ch = rect.size.height;
740                 }
741
742                 if (cw < rect.size.width * 100 / editp)
743                     cw = rect.size.width * 100 / editp;
744             }
745             break;
746           case CTRL_TEXT:
747             {
748                 NSTextView *tv;
749                 int testwid;
750
751                 if (!textviewfont) {
752                     NSTextField *tf;
753                     tf = [[NSTextField alloc] init];
754                     textviewfont = [tf font];
755                     [tf release];
756                 }
757
758                 testwid = (ccw[colend] - ccw[colstart]) * 3;
759
760                 tv = [[NSTextView alloc]
761                       initWithFrame:NSMakeRect(0,0,testwid,1)];
762                 [tv setEditable:NO];
763                 [tv setSelectable:NO];
764                 //[tv setBordered:NO];
765                 [tv setDrawsBackground:NO];
766                 [tv setFont:textviewfont];
767                 [tv setString:
768                  [NSString stringWithCString:ctrl->generic.label]];
769                 rect = [tv frame];
770                 [tv sizeToFit];
771                 [parent addSubview:tv];
772                 c->textview = tv;
773
774                 cw = rect.size.width;
775                 ch = rect.size.height;
776             }
777             break;
778           case CTRL_RADIO:
779             {
780                 NSTextField *tf;
781                 int j;
782
783                 if (ctrl->generic.label) {
784                     tf = [[NSTextField alloc]
785                           initWithFrame:NSMakeRect(0,0,1,1)];
786                     [tf setEditable:NO];
787                     [tf setSelectable:NO];
788                     [tf setBordered:NO];
789                     [tf setDrawsBackground:NO];
790                     [tf setStringValue:
791                      [NSString stringWithCString:ctrl->generic.label]];
792                     [tf sizeToFit];
793                     rect = [tf frame];
794                     [parent addSubview:tf];
795                     c->label = tf;
796
797                     cw = rect.size.width;
798                     ch = rect.size.height;
799                 } else {
800                     cw = 0;
801                     ch = -VSPACING;    /* compensate for next advance */
802                 }
803
804                 c->nradiobuttons = ctrl->radio.nbuttons;
805                 c->radiobuttons = snewn(ctrl->radio.nbuttons, NSButton *);
806
807                 for (j = 0; j < ctrl->radio.nbuttons; j++) {
808                     NSButton *b;
809                     int ncols;
810
811                     b = [[MyButton alloc] initWithFrame:NSMakeRect(0,0,1,1)];
812                     [b setBezelStyle:NSRoundedBezelStyle];
813                     [b setButtonType:NSRadioButton];
814                     [b setTitle:[NSString
815                                  stringWithCString:ctrl->radio.buttons[j]]];
816                     [b sizeToFit];
817                     rect = [b frame];
818                     [parent addSubview:b];
819
820                     c->radiobuttons[j] = b;
821
822                     [b setTarget:d->rec];
823                     [b setAction:@selector(radioChanged:)];
824                     add_widget(d, c, b);
825
826                     /*
827                      * Add to the height every time we place a
828                      * button in column 0.
829                      */
830                     if (j % ctrl->radio.ncolumns == 0) {
831                         ch += rect.size.height + VSPACING;
832                     }
833
834                     /*
835                      * Add to the width by working out how many
836                      * columns this button spans.
837                      */
838                     if (j == ctrl->radio.nbuttons - 1)
839                         ncols = (ctrl->radio.ncolumns -
840                                  (j % ctrl->radio.ncolumns));
841                     else
842                         ncols = 1;
843
844                     if (cw < rect.size.width * ctrl->radio.ncolumns / ncols)
845                         cw = rect.size.width * ctrl->radio.ncolumns / ncols;
846                 }
847             }
848             break;
849           case CTRL_FILESELECT:
850           case CTRL_FONTSELECT:
851             {
852                 NSTextField *tf;
853                 NSButton *b;
854                 int kh;
855
856                 tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
857                 [tf setEditable:NO];
858                 [tf setSelectable:NO];
859                 [tf setBordered:NO];
860                 [tf setDrawsBackground:NO];
861                 [tf setStringValue:[NSString
862                                     stringWithCString:ctrl->generic.label]];
863                 [tf sizeToFit];
864                 rect = [tf frame];
865                 [parent addSubview:tf];
866                 c->label = tf;
867
868                 cw = rect.size.width;
869                 ch = rect.size.height;
870
871                 tf = [NSTextField alloc];
872                 tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
873                 if (ctrl->generic.type == CTRL_FILESELECT) {
874                     [tf setEditable:YES];
875                     [tf setSelectable:YES];
876                     [tf setBordered:YES];
877                 } else {
878                     [tf setEditable:NO];
879                     [tf setSelectable:NO];
880                     [tf setBordered:NO];
881                     [tf setDrawsBackground:NO];
882                 }
883                 [tf setStringValue:@"x"];
884                 [tf sizeToFit];
885                 rect = [tf frame];
886                 [parent addSubview:tf];
887                 c->editbox = tf;
888
889                 kh = rect.size.height;
890                 if (cw < rect.size.width * 4 / 3)
891                     cw = rect.size.width * 4 / 3;
892
893                 b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
894                 [b setBezelStyle:NSRoundedBezelStyle];
895                 if (ctrl->generic.type == CTRL_FILESELECT)
896                     [b setTitle:@"Browse..."];
897                 else
898                     [b setTitle:@"Change..."];
899                 // [b setKeyEquivalent:somethingorother];
900                 // [b setTarget:somethingorother];
901                 // [b setAction:somethingorother];
902                 [b sizeToFit];
903                 rect = [b frame];
904                 [parent addSubview:b];
905
906                 c->button = b;
907
908                 if (kh < rect.size.height)
909                     kh = rect.size.height;
910                 ch += VSPACING + kh;
911                 if (cw < rect.size.width * 4)
912                     cw = rect.size.width * 4;
913             }
914             break;
915           case CTRL_LISTBOX:
916             {
917                 int listp = ctrl->listbox.percentwidth;
918                 int labelp = listp == 100 ? 100 : 100 - listp;
919                 NSTextField *tf;
920                 NSPopUpButton *pb;
921                 NSTableView *tv;
922                 NSScrollView *sv;
923
924                 if (ctrl->generic.label) {
925                     tf = [[NSTextField alloc]
926                           initWithFrame:NSMakeRect(0,0,1,1)];
927                     [tf setEditable:NO];
928                     [tf setSelectable:NO];
929                     [tf setBordered:NO];
930                     [tf setDrawsBackground:NO];
931                     [tf setStringValue:
932                      [NSString stringWithCString:ctrl->generic.label]];
933                     [tf sizeToFit];
934                     rect = [tf frame];
935                     [parent addSubview:tf];
936                     c->label = tf;
937
938                     cw = rect.size.width;
939                     ch = rect.size.height;
940                 } else {
941                     cw = 0;
942                     ch = -VSPACING;    /* compensate for next advance */
943                 }
944
945                 if (ctrl->listbox.height == 0) {
946                     pb = [[NSPopUpButton alloc]
947                           initWithFrame:NSMakeRect(0,0,1,1)];
948                     [pb sizeToFit];
949                     rect = [pb frame];
950                     [parent addSubview:pb];
951                     c->popupbutton = pb;
952
953                     [pb setTarget:d->rec];
954                     [pb setAction:@selector(popupMenuSelected:)];
955                     add_widget(d, c, pb);
956                 } else {
957                     assert(listp == 100);
958                     if (ctrl->listbox.draglist) {
959                         int bi;
960
961                         listp = 75;
962
963                         for (bi = 0; bi < 2; bi++) {
964                             NSButton *b;
965                             b = [[MyButton alloc]
966                                  initWithFrame:NSMakeRect(0, 0, 1, 1)];
967                             [b setBezelStyle:NSRoundedBezelStyle];
968                             if (bi == 0)
969                                 [b setTitle:@"Up"];
970                             else
971                                 [b setTitle:@"Down"];
972                             [b sizeToFit];
973                             rect = [b frame];
974                             [parent addSubview:b];
975
976                             if (bi == 0)
977                                 c->button = b;
978                             else
979                                 c->button2 = b;
980
981                             [b setTarget:d->rec];
982                             [b setAction:@selector(dragListButton:)];
983                             add_widget(d, c, b);
984
985                             if (cw < rect.size.width * 4)
986                                 cw = rect.size.width * 4;
987                         }
988                     }
989
990                     sv = [[NSScrollView alloc] initWithFrame:
991                           NSMakeRect(20,20,10,10)];
992                     [sv setBorderType:NSLineBorder];
993                     tv = [[NSTableView alloc] initWithFrame:[sv frame]];
994                     [[tv headerView] setFrame:NSMakeRect(0,0,0,0)];
995                     [sv setDocumentView:tv];
996                     [parent addSubview:sv];
997                     [sv setHasVerticalScroller:YES];
998                     [sv setAutohidesScrollers:YES];
999                     [tv setAllowsColumnReordering:NO];
1000                     [tv setAllowsColumnResizing:NO];
1001                     [tv setAllowsMultipleSelection:ctrl->listbox.multisel];
1002                     [tv setAllowsEmptySelection:YES];
1003                     [tv setAllowsColumnSelection:YES];
1004                     [tv setDataSource:[[MyTableSource alloc] init]];
1005                     rect = [tv frame];
1006                     /*
1007                      * For some reason this consistently comes out
1008                      * one short. Add one.
1009                      */
1010                     rect.size.height = (ctrl->listbox.height+1)*[tv rowHeight];
1011                     [sv setFrame:rect];
1012                     c->tableview = tv;
1013                     c->scrollview = sv;
1014
1015                     [tv setDelegate:d->rec];
1016                     [tv setTarget:d->rec];
1017                     [tv setDoubleAction:@selector(listDoubleClicked:)];
1018                     add_widget(d, c, tv);
1019                 }
1020
1021                 if (c->tableview) {
1022                     int ncols, *percentages;
1023                     int hundred = 100;
1024
1025                     if (ctrl->listbox.ncols) {
1026                         ncols = ctrl->listbox.ncols;
1027                         percentages = ctrl->listbox.percentages;
1028                     } else {
1029                         ncols = 1;
1030                         percentages = &hundred;
1031                     }
1032
1033                     for (j = 0; j < ncols; j++) {
1034                         NSTableColumn *col;
1035
1036                         col = [[NSTableColumn alloc] initWithIdentifier:
1037                                [NSNumber numberWithInt:j]];
1038                         [c->tableview addTableColumn:col];
1039                     }
1040                 }
1041
1042                 if (labelp == 100) {
1043                     /* the list and its label are vertically separated */
1044                     ch += VSPACING + rect.size.height;
1045                 } else {
1046                     /* the list and its label are horizontally separated */
1047                     if (ch < rect.size.height)
1048                         ch = rect.size.height;
1049                 }
1050
1051                 if (cw < rect.size.width * 100 / listp)
1052                     cw = rect.size.width * 100 / listp;
1053             }
1054             break;
1055         }
1056
1057         /*
1058          * Update the width and height data for the control we've
1059          * just created.
1060          */
1061         ytop = 0;
1062
1063         for (j = colstart; j < colend; j++) {
1064             if (ytop < cypos[j])
1065                 ytop = cypos[j];
1066         }
1067
1068         for (j = colstart; j < colend; j++)
1069             cypos[j] = ytop + ch + VSPACING;
1070
1071         if (hmin < ytop + ch)
1072             hmin = ytop + ch;
1073
1074         wthis = (cw + HSPACING) * 100 / (ccw[colend] - ccw[colstart]);
1075         wthis -= HSPACING;
1076
1077         if (wmin < wthis)
1078             wmin = wthis;
1079     }
1080
1081     if (*s->boxname) {
1082         /*
1083          * Add a bit to the width and height for the box.
1084          */
1085         wmin += boxw;
1086         hmin += boxh;
1087     }
1088
1089     //printf("For controlset %s/%s, returning w=%d h=%d\n",
1090     //       s->pathname, s->boxname, wmin, hmin);
1091     *minw = wmin;
1092     *minh = hmin;
1093 }
1094
1095 int place_ctrls(void *dv, struct controlset *s, int leftx, int topy,
1096                 int width)
1097 {
1098     struct fe_dlg *d = (struct fe_dlg *)dv;
1099     int ccw[100];                      /* cumulative column widths */
1100     int cypos[100];
1101     int ncols;
1102     int i, j, ret;
1103     int boxh = 0, boxw = 0;
1104
1105     if (!s->boxname && s->boxtitle) {
1106         /* Size and place the panel title. */
1107
1108         NSTextField *tf = find_box(d, s);
1109         NSRect rect;
1110
1111         rect = [tf frame];
1112         [tf setFrame:NSMakeRect(leftx, topy-rect.size.height,
1113                                 width, rect.size.height)];
1114         return rect.size.height;
1115     }
1116
1117     if (*s->boxname) {
1118         NSRect rect, tmprect;
1119         NSBox *box = find_box(d, s);
1120
1121         assert(box != NULL);
1122         tmprect = [box frame];
1123         [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
1124         rect = [box frame];
1125         [box setFrame:tmprect];
1126         boxw = rect.size.width - 100;
1127         boxh = rect.size.height - 100;
1128         if (s->boxtitle)
1129             boxh += [[box titleFont] pointSize];
1130         topy -= boxh;
1131         width -= boxw;
1132     }
1133
1134     ncols = 1;
1135     ccw[0] = 0;
1136     ccw[1] = 100;
1137     cypos[0] = topy;
1138     ret = 0;
1139
1140     /*
1141      * Now iterate through the controls themselves, placing them
1142      * appropriately.
1143      */
1144     for (i = 0; i < s->ncontrols; i++) {
1145         union control *ctrl = s->ctrls[i];
1146         struct fe_ctrl *c;
1147         int colstart = COLUMN_START(ctrl->generic.column);
1148         int colspan = COLUMN_SPAN(ctrl->generic.column);
1149         int colend = colstart + colspan;
1150         int xthis, ythis, wthis, ch;
1151         NSRect rect;
1152
1153         switch (ctrl->generic.type) {
1154           case CTRL_COLUMNS:
1155             for (j = 1; j < ncols; j++)
1156                 if (cypos[0] > cypos[j])
1157                     cypos[0] = cypos[j];
1158
1159             assert(ctrl->columns.ncols < lenof(ccw));
1160
1161             ccw[0] = 0;
1162             for (j = 0; j < ctrl->columns.ncols; j++) {
1163                 ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
1164                                      ctrl->columns.percentages[j] : 100);
1165                 cypos[j] = cypos[0];
1166             }
1167
1168             ncols = ctrl->columns.ncols;
1169
1170             continue;                  /* no actual control created */
1171           case CTRL_TABDELAY:
1172             continue;                  /* nothing to do here, move along */
1173         }
1174
1175         c = fe_ctrl_byctrl(d, ctrl);
1176
1177         ch = 0;
1178         ythis = topy;
1179
1180         for (j = colstart; j < colend; j++) {
1181             if (ythis > cypos[j])
1182                 ythis = cypos[j];
1183         }
1184
1185         xthis = (width + HSPACING) * ccw[colstart] / 100;
1186         wthis = (width + HSPACING) * ccw[colend] / 100 - HSPACING - xthis;
1187         xthis += leftx;
1188
1189         switch (ctrl->generic.type) {
1190           case CTRL_BUTTON:
1191           case CTRL_CHECKBOX:
1192             rect = [c->button frame];
1193             [c->button setFrame:NSMakeRect(xthis,ythis-rect.size.height,wthis,
1194                                            rect.size.height)];
1195             ch = rect.size.height;
1196             break;
1197           case CTRL_EDITBOX:
1198             {
1199                 int editp = ctrl->editbox.percentwidth;
1200                 int labelp = editp == 100 ? 100 : 100 - editp;
1201                 int lheight, theight, rheight, ynext, editw;
1202                 NSControl *edit = (c->editbox ? c->editbox : c->combobox);
1203
1204                 rect = [c->label frame];
1205                 lheight = rect.size.height;
1206                 rect = [edit frame];
1207                 theight = rect.size.height;
1208
1209                 if (editp == 100)
1210                     rheight = lheight;
1211                 else
1212                     rheight = (lheight < theight ? theight : lheight);
1213
1214                 [c->label setFrame:
1215                  NSMakeRect(xthis, ythis-(rheight+lheight)/2,
1216                             (wthis + HSPACING) * labelp / 100 - HSPACING,
1217                             lheight)];
1218                 if (editp == 100) {
1219                     ynext = ythis - rheight - VSPACING;
1220                     rheight = theight;
1221                 } else {
1222                     ynext = ythis;
1223                 }
1224
1225                 editw = (wthis + HSPACING) * editp / 100 - HSPACING;
1226
1227                 [edit setFrame:
1228                  NSMakeRect(xthis+wthis-editw, ynext-(rheight+theight)/2,
1229                             editw, theight)];
1230
1231                 ch = (ythis - ynext) + theight;
1232             }
1233             break;
1234           case CTRL_TEXT:
1235             [c->textview setFrame:NSMakeRect(xthis, 0, wthis, 1)];
1236             [c->textview sizeToFit];
1237             rect = [c->textview frame];
1238             [c->textview setFrame:NSMakeRect(xthis, ythis-rect.size.height,
1239                                              wthis, rect.size.height)];
1240             ch = rect.size.height;
1241             break;
1242           case CTRL_RADIO:
1243             {
1244                 int j, ynext;
1245
1246                 if (c->label) {
1247                     rect = [c->label frame];
1248                     [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
1249                                                   wthis,rect.size.height)];
1250                     ynext = ythis - rect.size.height - VSPACING;
1251                 } else
1252                     ynext = ythis;
1253
1254                 for (j = 0; j < ctrl->radio.nbuttons; j++) {
1255                     int col = j % ctrl->radio.ncolumns;
1256                     int ncols;
1257                     int lx,rx;
1258
1259                     if (j == ctrl->radio.nbuttons - 1)
1260                         ncols = ctrl->radio.ncolumns - col;
1261                     else
1262                         ncols = 1;
1263
1264                     lx = (wthis + HSPACING) * col / ctrl->radio.ncolumns;
1265                     rx = ((wthis + HSPACING) *
1266                           (col+ncols) / ctrl->radio.ncolumns) - HSPACING;
1267
1268                     /*
1269                      * Set the frame size.
1270                      */
1271                     rect = [c->radiobuttons[j] frame];
1272                     [c->radiobuttons[j] setFrame:
1273                      NSMakeRect(lx+xthis, ynext-rect.size.height,
1274                                 rx-lx, rect.size.height)];
1275
1276                     /*
1277                      * Advance to next line if we're in the last
1278                      * column.
1279                      */
1280                     if (col + ncols == ctrl->radio.ncolumns)
1281                         ynext -= rect.size.height + VSPACING;
1282                 }
1283                 ch = (ythis - ynext) - VSPACING;
1284             }
1285             break;
1286           case CTRL_FILESELECT:
1287           case CTRL_FONTSELECT:
1288             {
1289                 int ynext, eh, bh, th, mx;
1290
1291                 rect = [c->label frame];
1292                 [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
1293                                               wthis,rect.size.height)];
1294                 ynext = ythis - rect.size.height - VSPACING;
1295
1296                 rect = [c->editbox frame];
1297                 eh = rect.size.height;
1298                 rect = [c->button frame];
1299                 bh = rect.size.height;
1300                 th = (eh > bh ? eh : bh);
1301
1302                 mx = (wthis + HSPACING) * 3 / 4 - HSPACING;
1303
1304                 [c->editbox setFrame:
1305                  NSMakeRect(xthis, ynext-(th+eh)/2, mx, eh)];
1306                 [c->button setFrame:
1307                  NSMakeRect(xthis+mx+HSPACING, ynext-(th+bh)/2,
1308                             wthis-mx-HSPACING, bh)];
1309
1310                 ch = (ythis - ynext) + th + VSPACING;
1311             }
1312             break;
1313           case CTRL_LISTBOX:
1314             {
1315                 int listp = ctrl->listbox.percentwidth;
1316                 int labelp = listp == 100 ? 100 : 100 - listp;
1317                 int lheight, theight, rheight, ynext, listw, xlist;
1318                 NSControl *list = (c->scrollview ? (id)c->scrollview :
1319                                    (id)c->popupbutton);
1320
1321                 if (ctrl->listbox.draglist) {
1322                     assert(listp == 100);
1323                     listp = 75;
1324                 }
1325
1326                 rect = [list frame];
1327                 theight = rect.size.height;
1328
1329                 if (c->label) {
1330                     rect = [c->label frame];
1331                     lheight = rect.size.height;
1332
1333                     if (labelp == 100)
1334                         rheight = lheight;
1335                     else
1336                         rheight = (lheight < theight ? theight : lheight);
1337
1338                     [c->label setFrame:
1339                      NSMakeRect(xthis, ythis-(rheight+lheight)/2,
1340                                 (wthis + HSPACING) * labelp / 100 - HSPACING,
1341                                 lheight)];
1342                     if (labelp == 100) {
1343                         ynext = ythis - rheight - VSPACING;
1344                         rheight = theight;
1345                     } else {
1346                         ynext = ythis;
1347                     }
1348                 } else {
1349                     ynext = ythis;
1350                     rheight = theight;
1351                 }
1352
1353                 listw = (wthis + HSPACING) * listp / 100 - HSPACING;
1354
1355                 if (labelp == 100)
1356                     xlist = xthis;
1357                 else
1358                     xlist = xthis+wthis-listw;
1359
1360                 [list setFrame: NSMakeRect(xlist, ynext-(rheight+theight)/2,
1361                                            listw, theight)];
1362
1363                 /*
1364                  * Size the columns for the table view.
1365                  */
1366                 if (c->tableview) {
1367                     int ncols, *percentages;
1368                     int hundred = 100;
1369                     int cpercent = 0, cpixels = 0;
1370                     NSArray *cols;
1371
1372                     if (ctrl->listbox.ncols) {
1373                         ncols = ctrl->listbox.ncols;
1374                         percentages = ctrl->listbox.percentages;
1375                     } else {
1376                         ncols = 1;
1377                         percentages = &hundred;
1378                     }
1379
1380                     cols = [c->tableview tableColumns];
1381
1382                     for (j = 0; j < ncols; j++) {
1383                         NSTableColumn *col = [cols objectAtIndex:j];
1384                         int newcpixels;
1385
1386                         cpercent += percentages[j];
1387                         newcpixels = listw * cpercent / 100;
1388                         [col setWidth:newcpixels-cpixels];
1389                         cpixels = newcpixels;
1390                     }
1391                 }
1392
1393                 ch = (ythis - ynext) + theight;
1394
1395                 if (c->button) {
1396                     int b2height, centre;
1397                     int bx, bw;
1398
1399                     /*
1400                      * Place the Up and Down buttons for a drag list.
1401                      */
1402                     assert(c->button2);
1403
1404                     rect = [c->button frame];
1405                     b2height = VSPACING + 2 * rect.size.height;
1406
1407                     centre = ynext - rheight/2;
1408
1409                     bx = (wthis + HSPACING) * 3 / 4;
1410                     bw = wthis - bx;
1411                     bx += leftx;
1412
1413                     [c->button setFrame:
1414                      NSMakeRect(bx, centre+b2height/2-rect.size.height,
1415                                 bw, rect.size.height)];
1416                     [c->button2 setFrame:
1417                      NSMakeRect(bx, centre-b2height/2,
1418                                 bw, rect.size.height)];
1419                 }
1420             }
1421             break;
1422         }
1423
1424         for (j = colstart; j < colend; j++)
1425             cypos[j] = ythis - ch - VSPACING;
1426         if (ret < topy - (ythis - ch))
1427             ret = topy - (ythis - ch);
1428     }
1429
1430     if (*s->boxname) {
1431         NSBox *box = find_box(d, s);
1432         assert(box != NULL);
1433         [box sizeToFit];
1434
1435         if (s->boxtitle) {
1436             NSRect rect = [box frame];
1437             rect.size.height += [[box titleFont] pointSize];
1438             [box setFrame:rect];
1439         }
1440
1441         ret += boxh;
1442     }
1443
1444     //printf("For controlset %s/%s, returning ret=%d\n",
1445     //       s->pathname, s->boxname, ret);
1446     return ret;
1447 }
1448
1449 void select_panel(void *dv, struct controlbox *b, const char *name)
1450 {
1451     struct fe_dlg *d = (struct fe_dlg *)dv;
1452     int i, j, hidden;
1453     struct controlset *s;
1454     union control *ctrl;
1455     struct fe_ctrl *c;
1456     NSBox *box;
1457
1458     for (i = 0; i < b->nctrlsets; i++) {
1459         s = b->ctrlsets[i];
1460
1461         if (*s->pathname) {
1462             hidden = !strcmp(s->pathname, name) ? NO : YES;
1463
1464             if ((box = find_box(d, s)) != NULL) {
1465                 [box setHidden:hidden];
1466             } else {
1467                 for (j = 0; j < s->ncontrols; j++) {
1468                     ctrl = s->ctrls[j];
1469                     c = fe_ctrl_byctrl(d, ctrl);
1470
1471                     if (!c)
1472                         continue;
1473
1474                     if (c->label)
1475                         [c->label setHidden:hidden];
1476                     if (c->button)
1477                         [c->button setHidden:hidden];
1478                     if (c->button2)
1479                         [c->button2 setHidden:hidden];
1480                     if (c->editbox)
1481                         [c->editbox setHidden:hidden];
1482                     if (c->combobox)
1483                         [c->combobox setHidden:hidden];
1484                     if (c->textview)
1485                         [c->textview setHidden:hidden];
1486                     if (c->tableview)
1487                         [c->tableview setHidden:hidden];
1488                     if (c->scrollview)
1489                         [c->scrollview setHidden:hidden];
1490                     if (c->popupbutton)
1491                         [c->popupbutton setHidden:hidden];
1492                     if (c->radiobuttons) {
1493                         int j;
1494                         for (j = 0; j < c->nradiobuttons; j++)
1495                             [c->radiobuttons[j] setHidden:hidden];
1496                     }
1497                     break;
1498                 }
1499             }
1500         }
1501     }
1502 }
1503
1504 void dlg_radiobutton_set(union control *ctrl, void *dv, int whichbutton)
1505 {
1506     struct fe_dlg *d = (struct fe_dlg *)dv;
1507     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1508     int j;
1509
1510     assert(c->radiobuttons);
1511     for (j = 0; j < c->nradiobuttons; j++)
1512         [c->radiobuttons[j] setState:
1513          (j == whichbutton ? NSOnState : NSOffState)];
1514 }
1515
1516 int dlg_radiobutton_get(union control *ctrl, void *dv)
1517 {
1518     struct fe_dlg *d = (struct fe_dlg *)dv;
1519     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1520     int j;
1521
1522     assert(c->radiobuttons);
1523     for (j = 0; j < c->nradiobuttons; j++)
1524         if ([c->radiobuttons[j] state] == NSOnState)
1525             return j;
1526
1527     return 0;                          /* should never reach here */
1528 }
1529
1530 void dlg_checkbox_set(union control *ctrl, void *dv, int checked)
1531 {
1532     struct fe_dlg *d = (struct fe_dlg *)dv;
1533     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1534
1535     assert(c->button);
1536     [c->button setState:(checked ? NSOnState : NSOffState)];
1537 }
1538
1539 int dlg_checkbox_get(union control *ctrl, void *dv)
1540 {
1541     struct fe_dlg *d = (struct fe_dlg *)dv;
1542     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1543
1544     assert(c->button);
1545     return ([c->button state] == NSOnState);
1546 }
1547
1548 void dlg_editbox_set(union control *ctrl, void *dv, char const *text)
1549 {
1550     struct fe_dlg *d = (struct fe_dlg *)dv;
1551     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1552
1553     if (c->editbox) {
1554         [c->editbox setStringValue:[NSString stringWithCString:text]];
1555     } else {
1556         assert(c->combobox);
1557         [c->combobox setStringValue:[NSString stringWithCString:text]];
1558     }
1559 }
1560
1561 void dlg_editbox_get(union control *ctrl, void *dv, char *buffer, int length)
1562 {
1563     struct fe_dlg *d = (struct fe_dlg *)dv;
1564     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1565     NSString *str;
1566
1567     if (c->editbox) {
1568         str = [c->editbox stringValue];
1569     } else {
1570         assert(c->combobox);
1571         str = [c->combobox stringValue];
1572     }
1573     if (!str)
1574         str = @"";
1575
1576     /* The length parameter to this method doesn't include a trailing NUL */
1577     [str getCString:buffer maxLength:length-1];
1578 }
1579
1580 void dlg_listbox_clear(union control *ctrl, void *dv)
1581 {
1582     struct fe_dlg *d = (struct fe_dlg *)dv;
1583     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1584
1585     if (c->tableview) {
1586         [[c->tableview dataSource] clear];
1587         [c->tableview reloadData];
1588     } else {
1589         [c->popupbutton removeAllItems];
1590     }
1591 }
1592
1593 void dlg_listbox_del(union control *ctrl, void *dv, int index)
1594 {
1595     struct fe_dlg *d = (struct fe_dlg *)dv;
1596     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1597
1598     if (c->tableview) {
1599         [[c->tableview dataSource] removestr:index];
1600         [c->tableview reloadData];
1601     } else {
1602         [c->popupbutton removeItemAtIndex:index];
1603     }
1604 }
1605
1606 void dlg_listbox_addwithid(union control *ctrl, void *dv,
1607                            char const *text, int id)
1608 {
1609     struct fe_dlg *d = (struct fe_dlg *)dv;
1610     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1611
1612     if (c->tableview) {
1613         [[c->tableview dataSource] add:text withId:id];
1614         [c->tableview reloadData];
1615     } else {
1616         [c->popupbutton addItemWithTitle:[NSString stringWithCString:text]];
1617         [[c->popupbutton lastItem] setTag:id];
1618     }
1619 }
1620
1621 void dlg_listbox_add(union control *ctrl, void *dv, char const *text)
1622 {
1623     dlg_listbox_addwithid(ctrl, dv, text, -1);
1624 }
1625
1626 int dlg_listbox_getid(union control *ctrl, void *dv, int index)
1627 {
1628     struct fe_dlg *d = (struct fe_dlg *)dv;
1629     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1630
1631     if (c->tableview) {
1632         return [[c->tableview dataSource] getid:index];
1633     } else {
1634         return [[c->popupbutton itemAtIndex:index] tag];
1635     }
1636 }
1637
1638 int dlg_listbox_index(union control *ctrl, void *dv)
1639 {
1640     struct fe_dlg *d = (struct fe_dlg *)dv;
1641     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1642
1643     if (c->tableview) {
1644         return [c->tableview selectedRow];
1645     } else {
1646         return [c->popupbutton indexOfSelectedItem];
1647     }
1648 }
1649
1650 int dlg_listbox_issel(union control *ctrl, void *dv, int index)
1651 {
1652     struct fe_dlg *d = (struct fe_dlg *)dv;
1653     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1654
1655     if (c->tableview) {
1656         return [c->tableview isRowSelected:index];
1657     } else {
1658         return [c->popupbutton indexOfSelectedItem] == index;
1659     }
1660 }
1661
1662 void dlg_listbox_select(union control *ctrl, void *dv, int index)
1663 {
1664     struct fe_dlg *d = (struct fe_dlg *)dv;
1665     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1666
1667     if (c->tableview) {
1668         [c->tableview selectRow:index byExtendingSelection:NO];
1669     } else {
1670         [c->popupbutton selectItemAtIndex:index];
1671     }
1672 }
1673
1674 void dlg_text_set(union control *ctrl, void *dv, char const *text)
1675 {
1676     struct fe_dlg *d = (struct fe_dlg *)dv;
1677     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1678
1679     assert(c->textview);
1680     [c->textview setString:[NSString stringWithCString:text]];
1681 }
1682
1683 void dlg_label_change(union control *ctrl, void *dlg, char const *text)
1684 {
1685     /*
1686      * This function is currently only used by the config box to
1687      * switch the labels on the host and port boxes between serial
1688      * and network modes. Since OS X does not (yet?) have a serial
1689      * back end, this function can safely do nothing for the
1690      * moment.
1691      */
1692 }
1693
1694 void dlg_filesel_set(union control *ctrl, void *dv, Filename fn)
1695 {
1696     /* FIXME */
1697 }
1698
1699 void dlg_filesel_get(union control *ctrl, void *dv, Filename *fn)
1700 {
1701     /* FIXME */
1702 }
1703
1704 void dlg_fontsel_set(union control *ctrl, void *dv, FontSpec fn)
1705 {
1706     /* FIXME */
1707 }
1708
1709 void dlg_fontsel_get(union control *ctrl, void *dv, FontSpec *fn)
1710 {
1711     /* FIXME */
1712 }
1713
1714 void dlg_update_start(union control *ctrl, void *dv)
1715 {
1716     /* FIXME */
1717 }
1718
1719 void dlg_update_done(union control *ctrl, void *dv)
1720 {
1721     /* FIXME */
1722 }
1723
1724 void dlg_set_focus(union control *ctrl, void *dv)
1725 {
1726     /* FIXME */
1727 }
1728
1729 union control *dlg_last_focused(union control *ctrl, void *dv)
1730 {
1731     return NULL; /* FIXME */
1732 }
1733
1734 void dlg_beep(void *dv)
1735 {
1736     NSBeep();
1737 }
1738
1739 void dlg_error_msg(void *dv, const char *msg)
1740 {
1741     /* FIXME */
1742 }
1743
1744 void dlg_end(void *dv, int value)
1745 {
1746     struct fe_dlg *d = (struct fe_dlg *)dv;
1747     [d->target performSelector:d->action
1748      withObject:[NSNumber numberWithInt:value]];
1749 }
1750
1751 void dlg_coloursel_start(union control *ctrl, void *dv,
1752                          int r, int g, int b)
1753 {
1754     /* FIXME */
1755 }
1756
1757 int dlg_coloursel_results(union control *ctrl, void *dv,
1758                           int *r, int *g, int *b)
1759 {
1760     return 0; /* FIXME */
1761 }
1762
1763 void dlg_refresh(union control *ctrl, void *dv)
1764 {
1765     struct fe_dlg *d = (struct fe_dlg *)dv;
1766     struct fe_ctrl *c;
1767
1768     if (ctrl) {
1769         if (ctrl->generic.handler != NULL)
1770             ctrl->generic.handler(ctrl, d, d->data, EVENT_REFRESH);
1771     } else {
1772         int i;
1773
1774         for (i = 0; (c = index234(d->byctrl, i)) != NULL; i++) {
1775             assert(c->ctrl != NULL);
1776             if (c->ctrl->generic.handler != NULL)
1777                 c->ctrl->generic.handler(c->ctrl, d,
1778                                          d->data, EVENT_REFRESH);
1779         }
1780     }
1781 }