]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - macosx/osxctrls.m
e3780ff682d402fb085697df18c497f6ee0b067c
[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     void *privdata;
247     int privdata_needs_free;
248 };
249
250 static int fe_ctrl_cmp_by_ctrl(void *av, void *bv)
251 {
252     struct fe_ctrl *a = (struct fe_ctrl *)av;
253     struct fe_ctrl *b = (struct fe_ctrl *)bv;
254
255     if (a->ctrl < b->ctrl)
256         return -1;
257     if (a->ctrl > b->ctrl)
258         return +1;
259     return 0;
260 }
261
262 static int fe_ctrl_find_by_ctrl(void *av, void *bv)
263 {
264     union control *a = (union control *)av;
265     struct fe_ctrl *b = (struct fe_ctrl *)bv;
266
267     if (a < b->ctrl)
268         return -1;
269     if (a > b->ctrl)
270         return +1;
271     return 0;
272 }
273
274 struct fe_box {
275     struct controlset *s;
276     id box;
277 };
278
279 static int fe_boxcmp(void *av, void *bv)
280 {
281     struct fe_box *a = (struct fe_box *)av;
282     struct fe_box *b = (struct fe_box *)bv;
283
284     if (a->s < b->s)
285         return -1;
286     if (a->s > b->s)
287         return +1;
288     return 0;
289 }
290
291 static int fe_boxfind(void *av, void *bv)
292 {
293     struct controlset *a = (struct controlset *)av;
294     struct fe_box *b = (struct fe_box *)bv;
295
296     if (a < b->s)
297         return -1;
298     if (a > b->s)
299         return +1;
300     return 0;
301 }
302
303 struct fe_backwards {                  /* map Cocoa widgets back to fe_ctrls */
304     id widget;
305     struct fe_ctrl *c;
306 };
307
308 static int fe_backwards_cmp_by_widget(void *av, void *bv)
309 {
310     struct fe_backwards *a = (struct fe_backwards *)av;
311     struct fe_backwards *b = (struct fe_backwards *)bv;
312
313     if (a->widget < b->widget)
314         return -1;
315     if (a->widget > b->widget)
316         return +1;
317     return 0;
318 }
319
320 static int fe_backwards_find_by_widget(void *av, void *bv)
321 {
322     id a = (id)av;
323     struct fe_backwards *b = (struct fe_backwards *)bv;
324
325     if (a < b->widget)
326         return -1;
327     if (a > b->widget)
328         return +1;
329     return 0;
330 }
331
332 static struct fe_ctrl *fe_ctrl_new(union control *ctrl)
333 {
334     struct fe_ctrl *c;
335
336     c = snew(struct fe_ctrl);
337     c->ctrl = ctrl;
338
339     c->button = c->button2 = nil;
340     c->label = nil;
341     c->editbox = nil;
342     c->combobox = nil;
343     c->textview = nil;
344     c->popupbutton = nil;
345     c->tableview = nil;
346     c->scrollview = nil;
347     c->radiobuttons = NULL;
348     c->nradiobuttons = 0;
349     c->privdata = NULL;
350     c->privdata_needs_free = FALSE;
351
352     return c;
353 }
354
355 static void fe_ctrl_free(struct fe_ctrl *c)
356 {
357     if (c->privdata_needs_free)
358         sfree(c->privdata);
359     sfree(c->radiobuttons);
360     sfree(c);
361 }
362
363 static struct fe_ctrl *fe_ctrl_byctrl(struct fe_dlg *d, union control *ctrl)
364 {
365     return find234(d->byctrl, ctrl, fe_ctrl_find_by_ctrl);
366 }
367
368 static void add_box(struct fe_dlg *d, struct controlset *s, id box)
369 {
370     struct fe_box *b = snew(struct fe_box);
371     b->box = box;
372     b->s = s;
373     add234(d->boxes, b);
374 }
375
376 static id find_box(struct fe_dlg *d, struct controlset *s)
377 {
378     struct fe_box *b = find234(d->boxes, s, fe_boxfind);
379     return b ? b->box : NULL;
380 }
381
382 static void add_widget(struct fe_dlg *d, struct fe_ctrl *c, id widget)
383 {
384     struct fe_backwards *b = snew(struct fe_backwards);
385     b->widget = widget;
386     b->c = c;
387     add234(d->bywidget, b);
388 }
389
390 static struct fe_ctrl *find_widget(struct fe_dlg *d, id widget)
391 {
392     struct fe_backwards *b = find234(d->bywidget, widget,
393                                      fe_backwards_find_by_widget);
394     return b ? b->c : NULL;
395 }
396
397 void *fe_dlg_init(void *data, NSWindow *window, NSObject *target, SEL action)
398 {
399     struct fe_dlg *d;
400
401     d = snew(struct fe_dlg);
402     d->window = window;
403     d->target = target;
404     d->action = action;
405     d->byctrl = newtree234(fe_ctrl_cmp_by_ctrl);
406     d->bywidget = newtree234(fe_backwards_cmp_by_widget);
407     d->boxes = newtree234(fe_boxcmp);
408     d->data = data;
409     d->rec = [[Receiver alloc] initWithStruct:d];
410
411     return d;
412 }
413
414 void fe_dlg_free(void *dv)
415 {
416     struct fe_dlg *d = (struct fe_dlg *)dv;
417     struct fe_ctrl *c;
418     struct fe_box *b;
419
420     while ( (c = delpos234(d->byctrl, 0)) != NULL )
421         fe_ctrl_free(c);
422     freetree234(d->byctrl);
423
424     while ( (c = delpos234(d->bywidget, 0)) != NULL )
425         sfree(c);
426     freetree234(d->bywidget);
427
428     while ( (b = delpos234(d->boxes, 0)) != NULL )
429         sfree(b);
430     freetree234(d->boxes);
431
432     [d->rec release];
433
434     sfree(d);
435 }
436
437 @implementation Receiver
438 - (id)initWithStruct:(struct fe_dlg *)aStruct
439 {
440     self = [super init];
441     d = aStruct;
442     return self;
443 }
444 - (void)buttonPushed:(id)sender
445 {
446     struct fe_ctrl *c = find_widget(d, sender);
447
448     assert(c && c->ctrl->generic.type == CTRL_BUTTON);
449     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
450 }
451 - (void)checkboxChanged:(id)sender
452 {
453     struct fe_ctrl *c = find_widget(d, sender);
454
455     assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
456     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
457 }
458 - (void)radioChanged:(id)sender
459 {
460     struct fe_ctrl *c = find_widget(d, sender);
461     int j;
462
463     assert(c && c->radiobuttons);
464     for (j = 0; j < c->nradiobuttons; j++)
465         if (sender != c->radiobuttons[j])
466             [c->radiobuttons[j] setState:NSOffState];
467     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
468 }
469 - (void)popupMenuSelected:(id)sender
470 {
471     struct fe_ctrl *c = find_widget(d, sender);
472     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
473 }
474 - (void)controlTextDidChange:(NSNotification *)notification
475 {
476     id widget = [notification object];
477     struct fe_ctrl *c = find_widget(d, widget);
478     assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
479     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
480 }
481 - (void)controlTextDidEndEditing:(NSNotification *)notification
482 {
483     id widget = [notification object];
484     struct fe_ctrl *c = find_widget(d, widget);
485     assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
486     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_REFRESH);
487 }
488 - (void)tableViewSelectionDidChange:(NSNotification *)notification
489 {
490     id widget = [notification object];
491     struct fe_ctrl *c = find_widget(d, widget);
492     assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
493     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_SELCHANGE);
494 }
495 - (BOOL)tableView:(NSTableView *)aTableView
496     shouldEditTableColumn:(NSTableColumn *)aTableColumn
497     row:(int)rowIndex
498 {
499     return NO;                         /* no editing permitted */
500 }
501 - (void)listDoubleClicked:(id)sender
502 {
503     struct fe_ctrl *c = find_widget(d, sender);
504     assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
505     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
506 }
507 - (void)dragListButton:(id)sender
508 {
509     struct fe_ctrl *c = find_widget(d, sender);
510     int direction, row, nrows;
511     assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
512            c->ctrl->listbox.draglist);
513
514     if (sender == c->button)
515         direction = -1;                /* up */
516     else
517         direction = +1;                /* down */
518
519     row = [c->tableview selectedRow];
520     nrows = [c->tableview numberOfRows];
521
522     if (row + direction < 0 || row + direction >= nrows) {
523         NSBeep();
524         return;
525     }
526
527     [[c->tableview dataSource] swap:row with:row+direction];
528     [c->tableview reloadData];
529     [c->tableview selectRow:row+direction byExtendingSelection:NO];
530
531     c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
532 }
533 @end
534
535 void create_ctrls(void *dv, NSView *parent, struct controlset *s,
536                   int *minw, int *minh)
537 {
538     struct fe_dlg *d = (struct fe_dlg *)dv;
539     int ccw[100];                      /* cumulative column widths */
540     int cypos[100];
541     int ncols;
542     int wmin = 0, hmin = 0;
543     int i, j, cw, ch;
544     NSRect rect;
545     NSFont *textviewfont = nil;
546     int boxh = 0, boxw = 0;
547
548     if (!s->boxname && s->boxtitle) {
549         /* This controlset is a panel title. */
550
551         NSTextField *tf;
552
553         tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
554         [tf setEditable:NO];
555         [tf setSelectable:NO];
556         [tf setBordered:NO];
557         [tf setDrawsBackground:NO];
558         [tf setStringValue:[NSString stringWithCString:s->boxtitle]];
559         [tf sizeToFit];
560         rect = [tf frame];
561         [parent addSubview:tf];
562
563         /*
564          * I'm going to store this NSTextField in the boxes tree,
565          * because I really can't face having a special tree234
566          * mapping controlsets to panel titles.
567          */
568         add_box(d, s, tf);
569
570         *minw = rect.size.width;
571         *minh = rect.size.height;
572
573         return;
574     }
575
576     if (*s->boxname) {
577         /*
578          * Create an NSBox to contain this subset of controls.
579          */
580         NSBox *box;
581         NSRect tmprect;
582
583         box = [[NSBox alloc] initWithFrame:NSMakeRect(0,0,1,1)];
584         if (s->boxtitle)
585             [box setTitle:[NSString stringWithCString:s->boxtitle]];
586         else
587             [box setTitlePosition:NSNoTitle];
588         add_box(d, s, box);
589         tmprect = [box frame];
590         [box setContentViewMargins:NSMakeSize(20,20)];
591         [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
592         rect = [box frame];
593         [box setFrame:tmprect];
594         boxh = (int)(rect.size.height - 100);
595         boxw = (int)(rect.size.width - 100);
596         [parent addSubview:box];
597
598         if (s->boxtitle)
599             boxh += [[box titleFont] pointSize];
600
601         /*
602          * All subsequent controls will be placed within this box.
603          */
604         parent = box;
605     }
606
607     ncols = 1;
608     ccw[0] = 0;
609     ccw[1] = 100;
610     cypos[0] = 0;
611
612     /*
613      * Now iterate through the controls themselves, create them,
614      * and add their width and height to the overall width/height
615      * calculation.
616      */
617     for (i = 0; i < s->ncontrols; i++) {
618         union control *ctrl = s->ctrls[i];
619         struct fe_ctrl *c;
620         int colstart = COLUMN_START(ctrl->generic.column);
621         int colspan = COLUMN_SPAN(ctrl->generic.column);
622         int colend = colstart + colspan;
623         int ytop, wthis;
624
625         switch (ctrl->generic.type) {
626           case CTRL_COLUMNS:
627             for (j = 1; j < ncols; j++)
628                 if (cypos[0] < cypos[j])
629                     cypos[0] = cypos[j];
630
631             assert(ctrl->columns.ncols < lenof(ccw));
632
633             ccw[0] = 0;
634             for (j = 0; j < ctrl->columns.ncols; j++) {
635                 ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
636                                      ctrl->columns.percentages[j] : 100);
637                 cypos[j] = cypos[0];
638             }
639
640             ncols = ctrl->columns.ncols;
641
642             continue;                  /* no actual control created */
643           case CTRL_TABDELAY:
644             /*
645              * I'm currently uncertain that we can implement tab
646              * order in OS X.
647              */
648             continue;                  /* no actual control created */
649         }
650
651         c = fe_ctrl_new(ctrl);
652         add234(d->byctrl, c);
653
654         cw = ch = 0;
655
656         switch (ctrl->generic.type) {
657           case CTRL_BUTTON:
658           case CTRL_CHECKBOX:
659             {
660                 NSButton *b;
661
662                 b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
663                 [b setBezelStyle:NSRoundedBezelStyle];
664                 if (ctrl->generic.type == CTRL_CHECKBOX)
665                     [b setButtonType:NSSwitchButton];
666                 [b setTitle:[NSString stringWithCString:ctrl->generic.label]];
667                 if (ctrl->button.isdefault)
668                     [b setKeyEquivalent:@"\r"];
669                 else if (ctrl->button.iscancel)
670                     [b setKeyEquivalent:@"\033"];
671                 [b sizeToFit];
672                 rect = [b frame];
673
674                 [parent addSubview:b];
675
676                 [b setTarget:d->rec];
677                 if (ctrl->generic.type == CTRL_CHECKBOX)
678                     [b setAction:@selector(checkboxChanged:)];
679                 else
680                     [b setAction:@selector(buttonPushed:)];
681                 add_widget(d, c, b);
682
683                 c->button = b;
684
685                 cw = rect.size.width;
686                 ch = rect.size.height;
687             }
688             break;
689           case CTRL_EDITBOX:
690             {
691                 int editp = ctrl->editbox.percentwidth;
692                 int labelp = editp == 100 ? 100 : 100 - editp;
693                 NSTextField *tf;
694                 NSComboBox *cb;
695
696                 tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
697                 [tf setEditable:NO];
698                 [tf setSelectable:NO];
699                 [tf setBordered:NO];
700                 [tf setDrawsBackground:NO];
701                 [tf setStringValue:[NSString
702                                     stringWithCString:ctrl->generic.label]];
703                 [tf sizeToFit];
704                 rect = [tf frame];
705                 [parent addSubview:tf];
706                 c->label = tf;
707
708                 cw = rect.size.width * 100 / labelp;
709                 ch = rect.size.height;
710
711                 if (ctrl->editbox.has_list) {
712                     cb = [[NSComboBox alloc]
713                           initWithFrame:NSMakeRect(0,0,1,1)];
714                     [cb setStringValue:@"x"];
715                     [cb sizeToFit];
716                     rect = [cb frame];
717                     [parent addSubview:cb];
718                     c->combobox = cb;
719                 } else {
720                     if (ctrl->editbox.password)
721                         tf = [NSSecureTextField alloc];
722                     else
723                         tf = [NSTextField alloc];
724
725                     tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
726                     [tf setEditable:YES];
727                     [tf setSelectable:YES];
728                     [tf setBordered:YES];
729                     [tf setStringValue:@"x"];
730                     [tf sizeToFit];
731                     rect = [tf frame];
732                     [parent addSubview:tf];
733                     c->editbox = tf;
734
735                     [tf setDelegate:d->rec];
736                     add_widget(d, c, tf);
737                 }
738
739                 if (editp == 100) {
740                     /* the edit box and its label are vertically separated */
741                     ch += VSPACING + rect.size.height;
742                 } else {
743                     /* the edit box and its label are horizontally separated */
744                     if (ch < rect.size.height)
745                         ch = rect.size.height;
746                 }
747
748                 if (cw < rect.size.width * 100 / editp)
749                     cw = rect.size.width * 100 / editp;
750             }
751             break;
752           case CTRL_TEXT:
753             {
754                 NSTextView *tv;
755                 int testwid;
756
757                 if (!textviewfont) {
758                     NSTextField *tf;
759                     tf = [[NSTextField alloc] init];
760                     textviewfont = [tf font];
761                     [tf release];
762                 }
763
764                 testwid = (ccw[colend] - ccw[colstart]) * 3;
765
766                 tv = [[NSTextView alloc]
767                       initWithFrame:NSMakeRect(0,0,testwid,1)];
768                 [tv setEditable:NO];
769                 [tv setSelectable:NO];
770                 //[tv setBordered:NO];
771                 [tv setDrawsBackground:NO];
772                 [tv setFont:textviewfont];
773                 [tv setString:
774                  [NSString stringWithCString:ctrl->generic.label]];
775                 rect = [tv frame];
776                 [tv sizeToFit];
777                 [parent addSubview:tv];
778                 c->textview = tv;
779
780                 cw = rect.size.width;
781                 ch = rect.size.height;
782             }
783             break;
784           case CTRL_RADIO:
785             {
786                 NSTextField *tf;
787                 int j;
788
789                 if (ctrl->generic.label) {
790                     tf = [[NSTextField alloc]
791                           initWithFrame:NSMakeRect(0,0,1,1)];
792                     [tf setEditable:NO];
793                     [tf setSelectable:NO];
794                     [tf setBordered:NO];
795                     [tf setDrawsBackground:NO];
796                     [tf setStringValue:
797                      [NSString stringWithCString:ctrl->generic.label]];
798                     [tf sizeToFit];
799                     rect = [tf frame];
800                     [parent addSubview:tf];
801                     c->label = tf;
802
803                     cw = rect.size.width;
804                     ch = rect.size.height;
805                 } else {
806                     cw = 0;
807                     ch = -VSPACING;    /* compensate for next advance */
808                 }
809
810                 c->nradiobuttons = ctrl->radio.nbuttons;
811                 c->radiobuttons = snewn(ctrl->radio.nbuttons, NSButton *);
812
813                 for (j = 0; j < ctrl->radio.nbuttons; j++) {
814                     NSButton *b;
815                     int ncols;
816
817                     b = [[MyButton alloc] initWithFrame:NSMakeRect(0,0,1,1)];
818                     [b setBezelStyle:NSRoundedBezelStyle];
819                     [b setButtonType:NSRadioButton];
820                     [b setTitle:[NSString
821                                  stringWithCString:ctrl->radio.buttons[j]]];
822                     [b sizeToFit];
823                     rect = [b frame];
824                     [parent addSubview:b];
825
826                     c->radiobuttons[j] = b;
827
828                     [b setTarget:d->rec];
829                     [b setAction:@selector(radioChanged:)];
830                     add_widget(d, c, b);
831
832                     /*
833                      * Add to the height every time we place a
834                      * button in column 0.
835                      */
836                     if (j % ctrl->radio.ncolumns == 0) {
837                         ch += rect.size.height + VSPACING;
838                     }
839
840                     /*
841                      * Add to the width by working out how many
842                      * columns this button spans.
843                      */
844                     if (j == ctrl->radio.nbuttons - 1)
845                         ncols = (ctrl->radio.ncolumns -
846                                  (j % ctrl->radio.ncolumns));
847                     else
848                         ncols = 1;
849
850                     if (cw < rect.size.width * ctrl->radio.ncolumns / ncols)
851                         cw = rect.size.width * ctrl->radio.ncolumns / ncols;
852                 }
853             }
854             break;
855           case CTRL_FILESELECT:
856           case CTRL_FONTSELECT:
857             {
858                 NSTextField *tf;
859                 NSButton *b;
860                 int kh;
861
862                 tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
863                 [tf setEditable:NO];
864                 [tf setSelectable:NO];
865                 [tf setBordered:NO];
866                 [tf setDrawsBackground:NO];
867                 [tf setStringValue:[NSString
868                                     stringWithCString:ctrl->generic.label]];
869                 [tf sizeToFit];
870                 rect = [tf frame];
871                 [parent addSubview:tf];
872                 c->label = tf;
873
874                 cw = rect.size.width;
875                 ch = rect.size.height;
876
877                 tf = [NSTextField alloc];
878                 tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
879                 if (ctrl->generic.type == CTRL_FILESELECT) {
880                     [tf setEditable:YES];
881                     [tf setSelectable:YES];
882                     [tf setBordered:YES];
883                 } else {
884                     [tf setEditable:NO];
885                     [tf setSelectable:NO];
886                     [tf setBordered:NO];
887                     [tf setDrawsBackground:NO];
888                 }
889                 [tf setStringValue:@"x"];
890                 [tf sizeToFit];
891                 rect = [tf frame];
892                 [parent addSubview:tf];
893                 c->editbox = tf;
894
895                 kh = rect.size.height;
896                 if (cw < rect.size.width * 4 / 3)
897                     cw = rect.size.width * 4 / 3;
898
899                 b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
900                 [b setBezelStyle:NSRoundedBezelStyle];
901                 if (ctrl->generic.type == CTRL_FILESELECT)
902                     [b setTitle:@"Browse..."];
903                 else
904                     [b setTitle:@"Change..."];
905                 // [b setKeyEquivalent:somethingorother];
906                 // [b setTarget:somethingorother];
907                 // [b setAction:somethingorother];
908                 [b sizeToFit];
909                 rect = [b frame];
910                 [parent addSubview:b];
911
912                 c->button = b;
913
914                 if (kh < rect.size.height)
915                     kh = rect.size.height;
916                 ch += VSPACING + kh;
917                 if (cw < rect.size.width * 4)
918                     cw = rect.size.width * 4;
919             }
920             break;
921           case CTRL_LISTBOX:
922             {
923                 int listp = ctrl->listbox.percentwidth;
924                 int labelp = listp == 100 ? 100 : 100 - listp;
925                 NSTextField *tf;
926                 NSPopUpButton *pb;
927                 NSTableView *tv;
928                 NSScrollView *sv;
929
930                 if (ctrl->generic.label) {
931                     tf = [[NSTextField alloc]
932                           initWithFrame:NSMakeRect(0,0,1,1)];
933                     [tf setEditable:NO];
934                     [tf setSelectable:NO];
935                     [tf setBordered:NO];
936                     [tf setDrawsBackground:NO];
937                     [tf setStringValue:
938                      [NSString stringWithCString:ctrl->generic.label]];
939                     [tf sizeToFit];
940                     rect = [tf frame];
941                     [parent addSubview:tf];
942                     c->label = tf;
943
944                     cw = rect.size.width;
945                     ch = rect.size.height;
946                 } else {
947                     cw = 0;
948                     ch = -VSPACING;    /* compensate for next advance */
949                 }
950
951                 if (ctrl->listbox.height == 0) {
952                     pb = [[NSPopUpButton alloc]
953                           initWithFrame:NSMakeRect(0,0,1,1)];
954                     [pb sizeToFit];
955                     rect = [pb frame];
956                     [parent addSubview:pb];
957                     c->popupbutton = pb;
958
959                     [pb setTarget:d->rec];
960                     [pb setAction:@selector(popupMenuSelected:)];
961                     add_widget(d, c, pb);
962                 } else {
963                     assert(listp == 100);
964                     if (ctrl->listbox.draglist) {
965                         int bi;
966
967                         listp = 75;
968
969                         for (bi = 0; bi < 2; bi++) {
970                             NSButton *b;
971                             b = [[MyButton alloc]
972                                  initWithFrame:NSMakeRect(0, 0, 1, 1)];
973                             [b setBezelStyle:NSRoundedBezelStyle];
974                             if (bi == 0)
975                                 [b setTitle:@"Up"];
976                             else
977                                 [b setTitle:@"Down"];
978                             [b sizeToFit];
979                             rect = [b frame];
980                             [parent addSubview:b];
981
982                             if (bi == 0)
983                                 c->button = b;
984                             else
985                                 c->button2 = b;
986
987                             [b setTarget:d->rec];
988                             [b setAction:@selector(dragListButton:)];
989                             add_widget(d, c, b);
990
991                             if (cw < rect.size.width * 4)
992                                 cw = rect.size.width * 4;
993                         }
994                     }
995
996                     sv = [[NSScrollView alloc] initWithFrame:
997                           NSMakeRect(20,20,10,10)];
998                     [sv setBorderType:NSLineBorder];
999                     tv = [[NSTableView alloc] initWithFrame:[sv frame]];
1000                     [[tv headerView] setFrame:NSMakeRect(0,0,0,0)];
1001                     [sv setDocumentView:tv];
1002                     [parent addSubview:sv];
1003                     [sv setHasVerticalScroller:YES];
1004                     [sv setAutohidesScrollers:YES];
1005                     [tv setAllowsColumnReordering:NO];
1006                     [tv setAllowsColumnResizing:NO];
1007                     [tv setAllowsMultipleSelection:ctrl->listbox.multisel];
1008                     [tv setAllowsEmptySelection:YES];
1009                     [tv setAllowsColumnSelection:YES];
1010                     [tv setDataSource:[[MyTableSource alloc] init]];
1011                     rect = [tv frame];
1012                     /*
1013                      * For some reason this consistently comes out
1014                      * one short. Add one.
1015                      */
1016                     rect.size.height = (ctrl->listbox.height+1)*[tv rowHeight];
1017                     [sv setFrame:rect];
1018                     c->tableview = tv;
1019                     c->scrollview = sv;
1020
1021                     [tv setDelegate:d->rec];
1022                     [tv setTarget:d->rec];
1023                     [tv setDoubleAction:@selector(listDoubleClicked:)];
1024                     add_widget(d, c, tv);
1025                 }
1026
1027                 if (c->tableview) {
1028                     int ncols, *percentages;
1029                     int hundred = 100;
1030
1031                     if (ctrl->listbox.ncols) {
1032                         ncols = ctrl->listbox.ncols;
1033                         percentages = ctrl->listbox.percentages;
1034                     } else {
1035                         ncols = 1;
1036                         percentages = &hundred;
1037                     }
1038
1039                     for (j = 0; j < ncols; j++) {
1040                         NSTableColumn *col;
1041
1042                         col = [[NSTableColumn alloc] initWithIdentifier:
1043                                [NSNumber numberWithInt:j]];
1044                         [c->tableview addTableColumn:col];
1045                     }
1046                 }
1047
1048                 if (labelp == 100) {
1049                     /* the list and its label are vertically separated */
1050                     ch += VSPACING + rect.size.height;
1051                 } else {
1052                     /* the list and its label are horizontally separated */
1053                     if (ch < rect.size.height)
1054                         ch = rect.size.height;
1055                 }
1056
1057                 if (cw < rect.size.width * 100 / listp)
1058                     cw = rect.size.width * 100 / listp;
1059             }
1060             break;
1061         }
1062
1063         /*
1064          * Update the width and height data for the control we've
1065          * just created.
1066          */
1067         ytop = 0;
1068
1069         for (j = colstart; j < colend; j++) {
1070             if (ytop < cypos[j])
1071                 ytop = cypos[j];
1072         }
1073
1074         for (j = colstart; j < colend; j++)
1075             cypos[j] = ytop + ch + VSPACING;
1076
1077         if (hmin < ytop + ch)
1078             hmin = ytop + ch;
1079
1080         wthis = (cw + HSPACING) * 100 / (ccw[colend] - ccw[colstart]);
1081         wthis -= HSPACING;
1082
1083         if (wmin < wthis)
1084             wmin = wthis;
1085     }
1086
1087     if (*s->boxname) {
1088         /*
1089          * Add a bit to the width and height for the box.
1090          */
1091         wmin += boxw;
1092         hmin += boxh;
1093     }
1094
1095     //printf("For controlset %s/%s, returning w=%d h=%d\n",
1096     //       s->pathname, s->boxname, wmin, hmin);
1097     *minw = wmin;
1098     *minh = hmin;
1099 }
1100
1101 int place_ctrls(void *dv, struct controlset *s, int leftx, int topy,
1102                 int width)
1103 {
1104     struct fe_dlg *d = (struct fe_dlg *)dv;
1105     int ccw[100];                      /* cumulative column widths */
1106     int cypos[100];
1107     int ncols;
1108     int i, j, ret;
1109     int boxh = 0, boxw = 0;
1110
1111     if (!s->boxname && s->boxtitle) {
1112         /* Size and place the panel title. */
1113
1114         NSTextField *tf = find_box(d, s);
1115         NSRect rect;
1116
1117         rect = [tf frame];
1118         [tf setFrame:NSMakeRect(leftx, topy-rect.size.height,
1119                                 width, rect.size.height)];
1120         return rect.size.height;
1121     }
1122
1123     if (*s->boxname) {
1124         NSRect rect, tmprect;
1125         NSBox *box = find_box(d, s);
1126
1127         assert(box != NULL);
1128         tmprect = [box frame];
1129         [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
1130         rect = [box frame];
1131         [box setFrame:tmprect];
1132         boxw = rect.size.width - 100;
1133         boxh = rect.size.height - 100;
1134         if (s->boxtitle)
1135             boxh += [[box titleFont] pointSize];
1136         topy -= boxh;
1137         width -= boxw;
1138     }
1139
1140     ncols = 1;
1141     ccw[0] = 0;
1142     ccw[1] = 100;
1143     cypos[0] = topy;
1144     ret = 0;
1145
1146     /*
1147      * Now iterate through the controls themselves, placing them
1148      * appropriately.
1149      */
1150     for (i = 0; i < s->ncontrols; i++) {
1151         union control *ctrl = s->ctrls[i];
1152         struct fe_ctrl *c;
1153         int colstart = COLUMN_START(ctrl->generic.column);
1154         int colspan = COLUMN_SPAN(ctrl->generic.column);
1155         int colend = colstart + colspan;
1156         int xthis, ythis, wthis, ch;
1157         NSRect rect;
1158
1159         switch (ctrl->generic.type) {
1160           case CTRL_COLUMNS:
1161             for (j = 1; j < ncols; j++)
1162                 if (cypos[0] > cypos[j])
1163                     cypos[0] = cypos[j];
1164
1165             assert(ctrl->columns.ncols < lenof(ccw));
1166
1167             ccw[0] = 0;
1168             for (j = 0; j < ctrl->columns.ncols; j++) {
1169                 ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
1170                                      ctrl->columns.percentages[j] : 100);
1171                 cypos[j] = cypos[0];
1172             }
1173
1174             ncols = ctrl->columns.ncols;
1175
1176             continue;                  /* no actual control created */
1177           case CTRL_TABDELAY:
1178             continue;                  /* nothing to do here, move along */
1179         }
1180
1181         c = fe_ctrl_byctrl(d, ctrl);
1182
1183         ch = 0;
1184         ythis = topy;
1185
1186         for (j = colstart; j < colend; j++) {
1187             if (ythis > cypos[j])
1188                 ythis = cypos[j];
1189         }
1190
1191         xthis = (width + HSPACING) * ccw[colstart] / 100;
1192         wthis = (width + HSPACING) * ccw[colend] / 100 - HSPACING - xthis;
1193         xthis += leftx;
1194
1195         switch (ctrl->generic.type) {
1196           case CTRL_BUTTON:
1197           case CTRL_CHECKBOX:
1198             rect = [c->button frame];
1199             [c->button setFrame:NSMakeRect(xthis,ythis-rect.size.height,wthis,
1200                                            rect.size.height)];
1201             ch = rect.size.height;
1202             break;
1203           case CTRL_EDITBOX:
1204             {
1205                 int editp = ctrl->editbox.percentwidth;
1206                 int labelp = editp == 100 ? 100 : 100 - editp;
1207                 int lheight, theight, rheight, ynext, editw;
1208                 NSControl *edit = (c->editbox ? c->editbox : c->combobox);
1209
1210                 rect = [c->label frame];
1211                 lheight = rect.size.height;
1212                 rect = [edit frame];
1213                 theight = rect.size.height;
1214
1215                 if (editp == 100)
1216                     rheight = lheight;
1217                 else
1218                     rheight = (lheight < theight ? theight : lheight);
1219
1220                 [c->label setFrame:
1221                  NSMakeRect(xthis, ythis-(rheight+lheight)/2,
1222                             (wthis + HSPACING) * labelp / 100 - HSPACING,
1223                             lheight)];
1224                 if (editp == 100) {
1225                     ynext = ythis - rheight - VSPACING;
1226                     rheight = theight;
1227                 } else {
1228                     ynext = ythis;
1229                 }
1230
1231                 editw = (wthis + HSPACING) * editp / 100 - HSPACING;
1232
1233                 [edit setFrame:
1234                  NSMakeRect(xthis+wthis-editw, ynext-(rheight+theight)/2,
1235                             editw, theight)];
1236
1237                 ch = (ythis - ynext) + theight;
1238             }
1239             break;
1240           case CTRL_TEXT:
1241             [c->textview setFrame:NSMakeRect(xthis, 0, wthis, 1)];
1242             [c->textview sizeToFit];
1243             rect = [c->textview frame];
1244             [c->textview setFrame:NSMakeRect(xthis, ythis-rect.size.height,
1245                                              wthis, rect.size.height)];
1246             ch = rect.size.height;
1247             break;
1248           case CTRL_RADIO:
1249             {
1250                 int j, ynext;
1251
1252                 if (c->label) {
1253                     rect = [c->label frame];
1254                     [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
1255                                                   wthis,rect.size.height)];
1256                     ynext = ythis - rect.size.height - VSPACING;
1257                 } else
1258                     ynext = ythis;
1259
1260                 for (j = 0; j < ctrl->radio.nbuttons; j++) {
1261                     int col = j % ctrl->radio.ncolumns;
1262                     int ncols;
1263                     int lx,rx;
1264
1265                     if (j == ctrl->radio.nbuttons - 1)
1266                         ncols = ctrl->radio.ncolumns - col;
1267                     else
1268                         ncols = 1;
1269
1270                     lx = (wthis + HSPACING) * col / ctrl->radio.ncolumns;
1271                     rx = ((wthis + HSPACING) *
1272                           (col+ncols) / ctrl->radio.ncolumns) - HSPACING;
1273
1274                     /*
1275                      * Set the frame size.
1276                      */
1277                     rect = [c->radiobuttons[j] frame];
1278                     [c->radiobuttons[j] setFrame:
1279                      NSMakeRect(lx+xthis, ynext-rect.size.height,
1280                                 rx-lx, rect.size.height)];
1281
1282                     /*
1283                      * Advance to next line if we're in the last
1284                      * column.
1285                      */
1286                     if (col + ncols == ctrl->radio.ncolumns)
1287                         ynext -= rect.size.height + VSPACING;
1288                 }
1289                 ch = (ythis - ynext) - VSPACING;
1290             }
1291             break;
1292           case CTRL_FILESELECT:
1293           case CTRL_FONTSELECT:
1294             {
1295                 int ynext, eh, bh, th, mx;
1296
1297                 rect = [c->label frame];
1298                 [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
1299                                               wthis,rect.size.height)];
1300                 ynext = ythis - rect.size.height - VSPACING;
1301
1302                 rect = [c->editbox frame];
1303                 eh = rect.size.height;
1304                 rect = [c->button frame];
1305                 bh = rect.size.height;
1306                 th = (eh > bh ? eh : bh);
1307
1308                 mx = (wthis + HSPACING) * 3 / 4 - HSPACING;
1309
1310                 [c->editbox setFrame:
1311                  NSMakeRect(xthis, ynext-(th+eh)/2, mx, eh)];
1312                 [c->button setFrame:
1313                  NSMakeRect(xthis+mx+HSPACING, ynext-(th+bh)/2,
1314                             wthis-mx-HSPACING, bh)];
1315
1316                 ch = (ythis - ynext) + th + VSPACING;
1317             }
1318             break;
1319           case CTRL_LISTBOX:
1320             {
1321                 int listp = ctrl->listbox.percentwidth;
1322                 int labelp = listp == 100 ? 100 : 100 - listp;
1323                 int lheight, theight, rheight, ynext, listw, xlist;
1324                 NSControl *list = (c->scrollview ? (id)c->scrollview :
1325                                    (id)c->popupbutton);
1326
1327                 if (ctrl->listbox.draglist) {
1328                     assert(listp == 100);
1329                     listp = 75;
1330                 }
1331
1332                 rect = [list frame];
1333                 theight = rect.size.height;
1334
1335                 if (c->label) {
1336                     rect = [c->label frame];
1337                     lheight = rect.size.height;
1338
1339                     if (labelp == 100)
1340                         rheight = lheight;
1341                     else
1342                         rheight = (lheight < theight ? theight : lheight);
1343
1344                     [c->label setFrame:
1345                      NSMakeRect(xthis, ythis-(rheight+lheight)/2,
1346                                 (wthis + HSPACING) * labelp / 100 - HSPACING,
1347                                 lheight)];
1348                     if (labelp == 100) {
1349                         ynext = ythis - rheight - VSPACING;
1350                         rheight = theight;
1351                     } else {
1352                         ynext = ythis;
1353                     }
1354                 } else {
1355                     ynext = ythis;
1356                     rheight = theight;
1357                 }
1358
1359                 listw = (wthis + HSPACING) * listp / 100 - HSPACING;
1360
1361                 if (labelp == 100)
1362                     xlist = xthis;
1363                 else
1364                     xlist = xthis+wthis-listw;
1365
1366                 [list setFrame: NSMakeRect(xlist, ynext-(rheight+theight)/2,
1367                                            listw, theight)];
1368
1369                 /*
1370                  * Size the columns for the table view.
1371                  */
1372                 if (c->tableview) {
1373                     int ncols, *percentages;
1374                     int hundred = 100;
1375                     int cpercent = 0, cpixels = 0;
1376                     NSArray *cols;
1377
1378                     if (ctrl->listbox.ncols) {
1379                         ncols = ctrl->listbox.ncols;
1380                         percentages = ctrl->listbox.percentages;
1381                     } else {
1382                         ncols = 1;
1383                         percentages = &hundred;
1384                     }
1385
1386                     cols = [c->tableview tableColumns];
1387
1388                     for (j = 0; j < ncols; j++) {
1389                         NSTableColumn *col = [cols objectAtIndex:j];
1390                         int newcpixels;
1391
1392                         cpercent += percentages[j];
1393                         newcpixels = listw * cpercent / 100;
1394                         [col setWidth:newcpixels-cpixels];
1395                         cpixels = newcpixels;
1396                     }
1397                 }
1398
1399                 ch = (ythis - ynext) + theight;
1400
1401                 if (c->button) {
1402                     int b2height, centre;
1403                     int bx, bw;
1404
1405                     /*
1406                      * Place the Up and Down buttons for a drag list.
1407                      */
1408                     assert(c->button2);
1409
1410                     rect = [c->button frame];
1411                     b2height = VSPACING + 2 * rect.size.height;
1412
1413                     centre = ynext - rheight/2;
1414
1415                     bx = (wthis + HSPACING) * 3 / 4;
1416                     bw = wthis - bx;
1417                     bx += leftx;
1418
1419                     [c->button setFrame:
1420                      NSMakeRect(bx, centre+b2height/2-rect.size.height,
1421                                 bw, rect.size.height)];
1422                     [c->button2 setFrame:
1423                      NSMakeRect(bx, centre-b2height/2,
1424                                 bw, rect.size.height)];
1425                 }
1426             }
1427             break;
1428         }
1429
1430         for (j = colstart; j < colend; j++)
1431             cypos[j] = ythis - ch - VSPACING;
1432         if (ret < topy - (ythis - ch))
1433             ret = topy - (ythis - ch);
1434     }
1435
1436     if (*s->boxname) {
1437         NSBox *box = find_box(d, s);
1438         assert(box != NULL);
1439         [box sizeToFit];
1440
1441         if (s->boxtitle) {
1442             NSRect rect = [box frame];
1443             rect.size.height += [[box titleFont] pointSize];
1444             [box setFrame:rect];
1445         }
1446
1447         ret += boxh;
1448     }
1449
1450     //printf("For controlset %s/%s, returning ret=%d\n",
1451     //       s->pathname, s->boxname, ret);
1452     return ret;
1453 }
1454
1455 void select_panel(void *dv, struct controlbox *b, const char *name)
1456 {
1457     struct fe_dlg *d = (struct fe_dlg *)dv;
1458     int i, j, hidden;
1459     struct controlset *s;
1460     union control *ctrl;
1461     struct fe_ctrl *c;
1462     NSBox *box;
1463
1464     for (i = 0; i < b->nctrlsets; i++) {
1465         s = b->ctrlsets[i];
1466
1467         if (*s->pathname) {
1468             hidden = !strcmp(s->pathname, name) ? NO : YES;
1469
1470             if ((box = find_box(d, s)) != NULL) {
1471                 [box setHidden:hidden];
1472             } else {
1473                 for (j = 0; j < s->ncontrols; j++) {
1474                     ctrl = s->ctrls[j];
1475                     c = fe_ctrl_byctrl(d, ctrl);
1476
1477                     if (!c)
1478                         continue;
1479
1480                     if (c->label)
1481                         [c->label setHidden:hidden];
1482                     if (c->button)
1483                         [c->button setHidden:hidden];
1484                     if (c->button2)
1485                         [c->button2 setHidden:hidden];
1486                     if (c->editbox)
1487                         [c->editbox setHidden:hidden];
1488                     if (c->combobox)
1489                         [c->combobox setHidden:hidden];
1490                     if (c->textview)
1491                         [c->textview setHidden:hidden];
1492                     if (c->tableview)
1493                         [c->tableview setHidden:hidden];
1494                     if (c->scrollview)
1495                         [c->scrollview setHidden:hidden];
1496                     if (c->popupbutton)
1497                         [c->popupbutton setHidden:hidden];
1498                     if (c->radiobuttons) {
1499                         int j;
1500                         for (j = 0; j < c->nradiobuttons; j++)
1501                             [c->radiobuttons[j] setHidden:hidden];
1502                     }
1503                     break;
1504                 }
1505             }
1506         }
1507     }
1508 }
1509
1510 void dlg_radiobutton_set(union control *ctrl, void *dv, int whichbutton)
1511 {
1512     struct fe_dlg *d = (struct fe_dlg *)dv;
1513     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1514     int j;
1515
1516     assert(c->radiobuttons);
1517     for (j = 0; j < c->nradiobuttons; j++)
1518         [c->radiobuttons[j] setState:
1519          (j == whichbutton ? NSOnState : NSOffState)];
1520 }
1521
1522 int dlg_radiobutton_get(union control *ctrl, void *dv)
1523 {
1524     struct fe_dlg *d = (struct fe_dlg *)dv;
1525     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1526     int j;
1527
1528     assert(c->radiobuttons);
1529     for (j = 0; j < c->nradiobuttons; j++)
1530         if ([c->radiobuttons[j] state] == NSOnState)
1531             return j;
1532
1533     return 0;                          /* should never reach here */
1534 }
1535
1536 void dlg_checkbox_set(union control *ctrl, void *dv, int checked)
1537 {
1538     struct fe_dlg *d = (struct fe_dlg *)dv;
1539     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1540
1541     assert(c->button);
1542     [c->button setState:(checked ? NSOnState : NSOffState)];
1543 }
1544
1545 int dlg_checkbox_get(union control *ctrl, void *dv)
1546 {
1547     struct fe_dlg *d = (struct fe_dlg *)dv;
1548     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1549
1550     assert(c->button);
1551     return ([c->button state] == NSOnState);
1552 }
1553
1554 void dlg_editbox_set(union control *ctrl, void *dv, char const *text)
1555 {
1556     struct fe_dlg *d = (struct fe_dlg *)dv;
1557     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1558
1559     if (c->editbox) {
1560         [c->editbox setStringValue:[NSString stringWithCString:text]];
1561     } else {
1562         assert(c->combobox);
1563         [c->combobox setStringValue:[NSString stringWithCString:text]];
1564     }
1565 }
1566
1567 void dlg_editbox_get(union control *ctrl, void *dv, char *buffer, int length)
1568 {
1569     struct fe_dlg *d = (struct fe_dlg *)dv;
1570     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1571     NSString *str;
1572
1573     if (c->editbox) {
1574         str = [c->editbox stringValue];
1575     } else {
1576         assert(c->combobox);
1577         str = [c->combobox stringValue];
1578     }
1579     if (!str)
1580         str = @"";
1581
1582     /* The length parameter to this method doesn't include a trailing NUL */
1583     [str getCString:buffer maxLength:length-1];
1584 }
1585
1586 void dlg_listbox_clear(union control *ctrl, void *dv)
1587 {
1588     struct fe_dlg *d = (struct fe_dlg *)dv;
1589     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1590
1591     if (c->tableview) {
1592         [[c->tableview dataSource] clear];
1593         [c->tableview reloadData];
1594     } else {
1595         [c->popupbutton removeAllItems];
1596     }
1597 }
1598
1599 void dlg_listbox_del(union control *ctrl, void *dv, int index)
1600 {
1601     struct fe_dlg *d = (struct fe_dlg *)dv;
1602     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1603
1604     if (c->tableview) {
1605         [[c->tableview dataSource] removestr:index];
1606         [c->tableview reloadData];
1607     } else {
1608         [c->popupbutton removeItemAtIndex:index];
1609     }
1610 }
1611
1612 void dlg_listbox_addwithid(union control *ctrl, void *dv,
1613                            char const *text, int id)
1614 {
1615     struct fe_dlg *d = (struct fe_dlg *)dv;
1616     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1617
1618     if (c->tableview) {
1619         [[c->tableview dataSource] add:text withId:id];
1620         [c->tableview reloadData];
1621     } else {
1622         [c->popupbutton addItemWithTitle:[NSString stringWithCString:text]];
1623         [[c->popupbutton lastItem] setTag:id];
1624     }
1625 }
1626
1627 void dlg_listbox_add(union control *ctrl, void *dv, char const *text)
1628 {
1629     dlg_listbox_addwithid(ctrl, dv, text, -1);
1630 }
1631
1632 int dlg_listbox_getid(union control *ctrl, void *dv, int index)
1633 {
1634     struct fe_dlg *d = (struct fe_dlg *)dv;
1635     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1636
1637     if (c->tableview) {
1638         return [[c->tableview dataSource] getid:index];
1639     } else {
1640         return [[c->popupbutton itemAtIndex:index] tag];
1641     }
1642 }
1643
1644 int dlg_listbox_index(union control *ctrl, void *dv)
1645 {
1646     struct fe_dlg *d = (struct fe_dlg *)dv;
1647     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1648
1649     if (c->tableview) {
1650         return [c->tableview selectedRow];
1651     } else {
1652         return [c->popupbutton indexOfSelectedItem];
1653     }
1654 }
1655
1656 int dlg_listbox_issel(union control *ctrl, void *dv, int index)
1657 {
1658     struct fe_dlg *d = (struct fe_dlg *)dv;
1659     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1660
1661     if (c->tableview) {
1662         return [c->tableview isRowSelected:index];
1663     } else {
1664         return [c->popupbutton indexOfSelectedItem] == index;
1665     }
1666 }
1667
1668 void dlg_listbox_select(union control *ctrl, void *dv, int index)
1669 {
1670     struct fe_dlg *d = (struct fe_dlg *)dv;
1671     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1672
1673     if (c->tableview) {
1674         [c->tableview selectRow:index byExtendingSelection:NO];
1675     } else {
1676         [c->popupbutton selectItemAtIndex:index];
1677     }
1678 }
1679
1680 void dlg_text_set(union control *ctrl, void *dv, char const *text)
1681 {
1682     struct fe_dlg *d = (struct fe_dlg *)dv;
1683     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1684
1685     assert(c->textview);
1686     [c->textview setString:[NSString stringWithCString:text]];
1687 }
1688
1689 void dlg_filesel_set(union control *ctrl, void *dv, Filename fn)
1690 {
1691     /* FIXME */
1692 }
1693
1694 void dlg_filesel_get(union control *ctrl, void *dv, Filename *fn)
1695 {
1696     /* FIXME */
1697 }
1698
1699 void dlg_fontsel_set(union control *ctrl, void *dv, FontSpec fn)
1700 {
1701     /* FIXME */
1702 }
1703
1704 void dlg_fontsel_get(union control *ctrl, void *dv, FontSpec *fn)
1705 {
1706     /* FIXME */
1707 }
1708
1709 void dlg_update_start(union control *ctrl, void *dv)
1710 {
1711     /* FIXME */
1712 }
1713
1714 void dlg_update_done(union control *ctrl, void *dv)
1715 {
1716     /* FIXME */
1717 }
1718
1719 void dlg_set_focus(union control *ctrl, void *dv)
1720 {
1721     /* FIXME */
1722 }
1723
1724 union control *dlg_last_focused(union control *ctrl, void *dv)
1725 {
1726     return NULL; /* FIXME */
1727 }
1728
1729 void dlg_beep(void *dv)
1730 {
1731     NSBeep();
1732 }
1733
1734 void dlg_error_msg(void *dv, char *msg)
1735 {
1736     /* FIXME */
1737 }
1738
1739 void dlg_end(void *dv, int value)
1740 {
1741     struct fe_dlg *d = (struct fe_dlg *)dv;
1742     [d->target performSelector:d->action
1743      withObject:[NSNumber numberWithInt:value]];
1744 }
1745
1746 void dlg_coloursel_start(union control *ctrl, void *dv,
1747                          int r, int g, int b)
1748 {
1749     /* FIXME */
1750 }
1751
1752 int dlg_coloursel_results(union control *ctrl, void *dv,
1753                           int *r, int *g, int *b)
1754 {
1755     return 0; /* FIXME */
1756 }
1757
1758 void dlg_refresh(union control *ctrl, void *dv)
1759 {
1760     struct fe_dlg *d = (struct fe_dlg *)dv;
1761     struct fe_ctrl *c;
1762
1763     if (ctrl) {
1764         if (ctrl->generic.handler != NULL)
1765             ctrl->generic.handler(ctrl, d, d->data, EVENT_REFRESH);
1766     } else {
1767         int i;
1768
1769         for (i = 0; (c = index234(d->byctrl, i)) != NULL; i++) {
1770             assert(c->ctrl != NULL);
1771             if (c->ctrl->generic.handler != NULL)
1772                 c->ctrl->generic.handler(c->ctrl, d,
1773                                          d->data, EVENT_REFRESH);
1774         }
1775     }
1776 }
1777
1778 void *dlg_get_privdata(union control *ctrl, void *dv)
1779 {
1780     struct fe_dlg *d = (struct fe_dlg *)dv;
1781     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1782     return c->privdata;
1783 }
1784
1785 void dlg_set_privdata(union control *ctrl, void *dv, void *ptr)
1786 {
1787     struct fe_dlg *d = (struct fe_dlg *)dv;
1788     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1789     c->privdata = ptr;
1790     c->privdata_needs_free = FALSE;
1791 }
1792
1793 void *dlg_alloc_privdata(union control *ctrl, void *dv, size_t size)
1794 {
1795     struct fe_dlg *d = (struct fe_dlg *)dv;
1796     struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1797     /*
1798      * This is an internal allocation routine, so it's allowed to
1799      * use smalloc directly.
1800      */
1801     c->privdata = smalloc(size);
1802     c->privdata_needs_free = TRUE;
1803     return c->privdata;
1804 }