2 * osxctrls.m: OS X implementation of the dialog.h interface.
5 #import <Cocoa/Cocoa.h>
12 * Still to be implemented:
14 * - file selectors (NSOpenPanel / NSSavePanel)
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
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
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.
38 * * I tried using makeFirstResponder to give keyboard focus,
39 * but it appeared not to work. Try again, and work out how
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.
47 * * this may run into the usual aggro with modal dialog boxes.
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.
60 * As yet unsolved issues [FIXME]:
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
67 * - NSTextViews are indented a bit. It'd be nice to put their
68 * left margin at the same place as everything else's.
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.
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!
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.
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?
86 * - Why do I have to fudge list box heights by adding one? (Might
87 * it be to do with the missing header view?)
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.
96 @interface MyButton : NSButton
101 @implementation MyButton
102 - (id)initWithFrame:(NSRect)r
104 self = [super initWithFrame:r];
108 - (void)setButtonType:(NSButtonType)t
110 if (t == NSRadioButton || t == NSSwitchButton)
114 [super setButtonType:t];
121 r.size.height = minht;
127 * Class used as the data source for NSTableViews.
129 @interface MyTableSource : NSObject
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;
140 @implementation MyTableSource
144 tree = newtree234(NULL);
150 while ((p = delpos234(tree, 0)) != NULL)
155 - (void)add:(const char *)str withId:(int)id
157 addpos234(tree, dupprintf("%d\t%s", id, str), count234(tree));
159 - (int)getid:(int)index
161 char *p = index234(tree, index);
164 - (void)removestr:(int)index
166 char *p = delpos234(tree, index);
169 - (void)swap:(int)index1 with:(int)index2
173 if (index1 > index2) {
174 int t = index1; index1 = index2; index2 = t;
177 /* delete later one first so it doesn't affect index of earlier one */
178 p2 = delpos234(tree, index2);
179 p1 = delpos234(tree, index1);
181 /* now insert earlier one before later one for the inverse reason */
182 addpos234(tree, p2, index1);
183 addpos234(tree, p1, index2);
188 while ((p = delpos234(tree, 0)) != NULL)
191 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
193 return count234(tree);
195 - (id)tableView:(NSTableView *)aTableView
196 objectValueForTableColumn:(NSTableColumn *)aTableColumn
199 int j = [[aTableColumn identifier] intValue];
200 char *p = index234(tree, rowIndex);
203 p += strcspn(p, "\t");
208 return [NSString stringWithCString:p length:strcspn(p, "\t")];
213 * Object to receive messages from various control classes.
224 void *data; /* passed to portable side */
228 @interface Receiver : NSObject
232 - (id)initWithStruct:(struct fe_dlg *)aStruct;
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;
247 int privdata_needs_free;
250 static int fe_ctrl_cmp_by_ctrl(void *av, void *bv)
252 struct fe_ctrl *a = (struct fe_ctrl *)av;
253 struct fe_ctrl *b = (struct fe_ctrl *)bv;
255 if (a->ctrl < b->ctrl)
257 if (a->ctrl > b->ctrl)
262 static int fe_ctrl_find_by_ctrl(void *av, void *bv)
264 union control *a = (union control *)av;
265 struct fe_ctrl *b = (struct fe_ctrl *)bv;
275 struct controlset *s;
279 static int fe_boxcmp(void *av, void *bv)
281 struct fe_box *a = (struct fe_box *)av;
282 struct fe_box *b = (struct fe_box *)bv;
291 static int fe_boxfind(void *av, void *bv)
293 struct controlset *a = (struct controlset *)av;
294 struct fe_box *b = (struct fe_box *)bv;
303 struct fe_backwards { /* map Cocoa widgets back to fe_ctrls */
308 static int fe_backwards_cmp_by_widget(void *av, void *bv)
310 struct fe_backwards *a = (struct fe_backwards *)av;
311 struct fe_backwards *b = (struct fe_backwards *)bv;
313 if (a->widget < b->widget)
315 if (a->widget > b->widget)
320 static int fe_backwards_find_by_widget(void *av, void *bv)
323 struct fe_backwards *b = (struct fe_backwards *)bv;
332 static struct fe_ctrl *fe_ctrl_new(union control *ctrl)
336 c = snew(struct fe_ctrl);
339 c->button = c->button2 = nil;
344 c->popupbutton = nil;
347 c->radiobuttons = NULL;
348 c->nradiobuttons = 0;
350 c->privdata_needs_free = FALSE;
355 static void fe_ctrl_free(struct fe_ctrl *c)
357 if (c->privdata_needs_free)
359 sfree(c->radiobuttons);
363 static struct fe_ctrl *fe_ctrl_byctrl(struct fe_dlg *d, union control *ctrl)
365 return find234(d->byctrl, ctrl, fe_ctrl_find_by_ctrl);
368 static void add_box(struct fe_dlg *d, struct controlset *s, id box)
370 struct fe_box *b = snew(struct fe_box);
376 static id find_box(struct fe_dlg *d, struct controlset *s)
378 struct fe_box *b = find234(d->boxes, s, fe_boxfind);
379 return b ? b->box : NULL;
382 static void add_widget(struct fe_dlg *d, struct fe_ctrl *c, id widget)
384 struct fe_backwards *b = snew(struct fe_backwards);
387 add234(d->bywidget, b);
390 static struct fe_ctrl *find_widget(struct fe_dlg *d, id widget)
392 struct fe_backwards *b = find234(d->bywidget, widget,
393 fe_backwards_find_by_widget);
394 return b ? b->c : NULL;
397 void *fe_dlg_init(void *data, NSWindow *window, NSObject *target, SEL action)
401 d = snew(struct fe_dlg);
405 d->byctrl = newtree234(fe_ctrl_cmp_by_ctrl);
406 d->bywidget = newtree234(fe_backwards_cmp_by_widget);
407 d->boxes = newtree234(fe_boxcmp);
409 d->rec = [[Receiver alloc] initWithStruct:d];
414 void fe_dlg_free(void *dv)
416 struct fe_dlg *d = (struct fe_dlg *)dv;
420 while ( (c = delpos234(d->byctrl, 0)) != NULL )
422 freetree234(d->byctrl);
424 while ( (c = delpos234(d->bywidget, 0)) != NULL )
426 freetree234(d->bywidget);
428 while ( (b = delpos234(d->boxes, 0)) != NULL )
430 freetree234(d->boxes);
437 @implementation Receiver
438 - (id)initWithStruct:(struct fe_dlg *)aStruct
444 - (void)buttonPushed:(id)sender
446 struct fe_ctrl *c = find_widget(d, sender);
448 assert(c && c->ctrl->generic.type == CTRL_BUTTON);
449 c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
451 - (void)checkboxChanged:(id)sender
453 struct fe_ctrl *c = find_widget(d, sender);
455 assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
456 c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
458 - (void)radioChanged:(id)sender
460 struct fe_ctrl *c = find_widget(d, sender);
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);
469 - (void)popupMenuSelected:(id)sender
471 struct fe_ctrl *c = find_widget(d, sender);
472 c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
474 - (void)controlTextDidChange:(NSNotification *)notification
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);
481 - (void)controlTextDidEndEditing:(NSNotification *)notification
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);
488 - (void)tableViewSelectionDidChange:(NSNotification *)notification
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);
495 - (BOOL)tableView:(NSTableView *)aTableView
496 shouldEditTableColumn:(NSTableColumn *)aTableColumn
499 return NO; /* no editing permitted */
501 - (void)listDoubleClicked:(id)sender
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);
507 - (void)dragListButton:(id)sender
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);
514 if (sender == c->button)
515 direction = -1; /* up */
517 direction = +1; /* down */
519 row = [c->tableview selectedRow];
520 nrows = [c->tableview numberOfRows];
522 if (row + direction < 0 || row + direction >= nrows) {
527 [[c->tableview dataSource] swap:row with:row+direction];
528 [c->tableview reloadData];
529 [c->tableview selectRow:row+direction byExtendingSelection:NO];
531 c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
535 void create_ctrls(void *dv, NSView *parent, struct controlset *s,
536 int *minw, int *minh)
538 struct fe_dlg *d = (struct fe_dlg *)dv;
539 int ccw[100]; /* cumulative column widths */
542 int wmin = 0, hmin = 0;
545 NSFont *textviewfont = nil;
546 int boxh = 0, boxw = 0;
548 if (!s->boxname && s->boxtitle) {
549 /* This controlset is a panel title. */
553 tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
555 [tf setSelectable:NO];
557 [tf setDrawsBackground:NO];
558 [tf setStringValue:[NSString stringWithCString:s->boxtitle]];
561 [parent addSubview:tf];
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.
570 *minw = rect.size.width;
571 *minh = rect.size.height;
578 * Create an NSBox to contain this subset of controls.
583 box = [[NSBox alloc] initWithFrame:NSMakeRect(0,0,1,1)];
585 [box setTitle:[NSString stringWithCString:s->boxtitle]];
587 [box setTitlePosition:NSNoTitle];
589 tmprect = [box frame];
590 [box setContentViewMargins:NSMakeSize(20,20)];
591 [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
593 [box setFrame:tmprect];
594 boxh = (int)(rect.size.height - 100);
595 boxw = (int)(rect.size.width - 100);
596 [parent addSubview:box];
599 boxh += [[box titleFont] pointSize];
602 * All subsequent controls will be placed within this box.
613 * Now iterate through the controls themselves, create them,
614 * and add their width and height to the overall width/height
617 for (i = 0; i < s->ncontrols; i++) {
618 union control *ctrl = s->ctrls[i];
620 int colstart = COLUMN_START(ctrl->generic.column);
621 int colspan = COLUMN_SPAN(ctrl->generic.column);
622 int colend = colstart + colspan;
625 switch (ctrl->generic.type) {
627 for (j = 1; j < ncols; j++)
628 if (cypos[0] < cypos[j])
631 assert(ctrl->columns.ncols < lenof(ccw));
634 for (j = 0; j < ctrl->columns.ncols; j++) {
635 ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
636 ctrl->columns.percentages[j] : 100);
640 ncols = ctrl->columns.ncols;
642 continue; /* no actual control created */
645 * I'm currently uncertain that we can implement tab
648 continue; /* no actual control created */
651 c = fe_ctrl_new(ctrl);
652 add234(d->byctrl, c);
656 switch (ctrl->generic.type) {
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"];
674 [parent addSubview:b];
676 [b setTarget:d->rec];
677 if (ctrl->generic.type == CTRL_CHECKBOX)
678 [b setAction:@selector(checkboxChanged:)];
680 [b setAction:@selector(buttonPushed:)];
685 cw = rect.size.width;
686 ch = rect.size.height;
691 int editp = ctrl->editbox.percentwidth;
692 int labelp = editp == 100 ? 100 : 100 - editp;
696 tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
698 [tf setSelectable:NO];
700 [tf setDrawsBackground:NO];
701 [tf setStringValue:[NSString
702 stringWithCString:ctrl->generic.label]];
705 [parent addSubview:tf];
708 cw = rect.size.width * 100 / labelp;
709 ch = rect.size.height;
711 if (ctrl->editbox.has_list) {
712 cb = [[NSComboBox alloc]
713 initWithFrame:NSMakeRect(0,0,1,1)];
714 [cb setStringValue:@"x"];
717 [parent addSubview:cb];
720 if (ctrl->editbox.password)
721 tf = [NSSecureTextField alloc];
723 tf = [NSTextField alloc];
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"];
732 [parent addSubview:tf];
735 [tf setDelegate:d->rec];
736 add_widget(d, c, tf);
740 /* the edit box and its label are vertically separated */
741 ch += VSPACING + rect.size.height;
743 /* the edit box and its label are horizontally separated */
744 if (ch < rect.size.height)
745 ch = rect.size.height;
748 if (cw < rect.size.width * 100 / editp)
749 cw = rect.size.width * 100 / editp;
759 tf = [[NSTextField alloc] init];
760 textviewfont = [tf font];
764 testwid = (ccw[colend] - ccw[colstart]) * 3;
766 tv = [[NSTextView alloc]
767 initWithFrame:NSMakeRect(0,0,testwid,1)];
769 [tv setSelectable:NO];
770 //[tv setBordered:NO];
771 [tv setDrawsBackground:NO];
772 [tv setFont:textviewfont];
774 [NSString stringWithCString:ctrl->generic.label]];
777 [parent addSubview:tv];
780 cw = rect.size.width;
781 ch = rect.size.height;
789 if (ctrl->generic.label) {
790 tf = [[NSTextField alloc]
791 initWithFrame:NSMakeRect(0,0,1,1)];
793 [tf setSelectable:NO];
795 [tf setDrawsBackground:NO];
797 [NSString stringWithCString:ctrl->generic.label]];
800 [parent addSubview:tf];
803 cw = rect.size.width;
804 ch = rect.size.height;
807 ch = -VSPACING; /* compensate for next advance */
810 c->nradiobuttons = ctrl->radio.nbuttons;
811 c->radiobuttons = snewn(ctrl->radio.nbuttons, NSButton *);
813 for (j = 0; j < ctrl->radio.nbuttons; j++) {
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]]];
824 [parent addSubview:b];
826 c->radiobuttons[j] = b;
828 [b setTarget:d->rec];
829 [b setAction:@selector(radioChanged:)];
833 * Add to the height every time we place a
834 * button in column 0.
836 if (j % ctrl->radio.ncolumns == 0) {
837 ch += rect.size.height + VSPACING;
841 * Add to the width by working out how many
842 * columns this button spans.
844 if (j == ctrl->radio.nbuttons - 1)
845 ncols = (ctrl->radio.ncolumns -
846 (j % ctrl->radio.ncolumns));
850 if (cw < rect.size.width * ctrl->radio.ncolumns / ncols)
851 cw = rect.size.width * ctrl->radio.ncolumns / ncols;
855 case CTRL_FILESELECT:
856 case CTRL_FONTSELECT:
862 tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
864 [tf setSelectable:NO];
866 [tf setDrawsBackground:NO];
867 [tf setStringValue:[NSString
868 stringWithCString:ctrl->generic.label]];
871 [parent addSubview:tf];
874 cw = rect.size.width;
875 ch = rect.size.height;
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];
885 [tf setSelectable:NO];
887 [tf setDrawsBackground:NO];
889 [tf setStringValue:@"x"];
892 [parent addSubview:tf];
895 kh = rect.size.height;
896 if (cw < rect.size.width * 4 / 3)
897 cw = rect.size.width * 4 / 3;
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..."];
904 [b setTitle:@"Change..."];
905 // [b setKeyEquivalent:somethingorother];
906 // [b setTarget:somethingorother];
907 // [b setAction:somethingorother];
910 [parent addSubview:b];
914 if (kh < rect.size.height)
915 kh = rect.size.height;
917 if (cw < rect.size.width * 4)
918 cw = rect.size.width * 4;
923 int listp = ctrl->listbox.percentwidth;
924 int labelp = listp == 100 ? 100 : 100 - listp;
930 if (ctrl->generic.label) {
931 tf = [[NSTextField alloc]
932 initWithFrame:NSMakeRect(0,0,1,1)];
934 [tf setSelectable:NO];
936 [tf setDrawsBackground:NO];
938 [NSString stringWithCString:ctrl->generic.label]];
941 [parent addSubview:tf];
944 cw = rect.size.width;
945 ch = rect.size.height;
948 ch = -VSPACING; /* compensate for next advance */
951 if (ctrl->listbox.height == 0) {
952 pb = [[NSPopUpButton alloc]
953 initWithFrame:NSMakeRect(0,0,1,1)];
956 [parent addSubview:pb];
959 [pb setTarget:d->rec];
960 [pb setAction:@selector(popupMenuSelected:)];
961 add_widget(d, c, pb);
963 assert(listp == 100);
964 if (ctrl->listbox.draglist) {
969 for (bi = 0; bi < 2; bi++) {
971 b = [[MyButton alloc]
972 initWithFrame:NSMakeRect(0, 0, 1, 1)];
973 [b setBezelStyle:NSRoundedBezelStyle];
977 [b setTitle:@"Down"];
980 [parent addSubview:b];
987 [b setTarget:d->rec];
988 [b setAction:@selector(dragListButton:)];
991 if (cw < rect.size.width * 4)
992 cw = rect.size.width * 4;
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]];
1013 * For some reason this consistently comes out
1014 * one short. Add one.
1016 rect.size.height = (ctrl->listbox.height+1)*[tv rowHeight];
1021 [tv setDelegate:d->rec];
1022 [tv setTarget:d->rec];
1023 [tv setDoubleAction:@selector(listDoubleClicked:)];
1024 add_widget(d, c, tv);
1028 int ncols, *percentages;
1031 if (ctrl->listbox.ncols) {
1032 ncols = ctrl->listbox.ncols;
1033 percentages = ctrl->listbox.percentages;
1036 percentages = &hundred;
1039 for (j = 0; j < ncols; j++) {
1042 col = [[NSTableColumn alloc] initWithIdentifier:
1043 [NSNumber numberWithInt:j]];
1044 [c->tableview addTableColumn:col];
1048 if (labelp == 100) {
1049 /* the list and its label are vertically separated */
1050 ch += VSPACING + rect.size.height;
1052 /* the list and its label are horizontally separated */
1053 if (ch < rect.size.height)
1054 ch = rect.size.height;
1057 if (cw < rect.size.width * 100 / listp)
1058 cw = rect.size.width * 100 / listp;
1064 * Update the width and height data for the control we've
1069 for (j = colstart; j < colend; j++) {
1070 if (ytop < cypos[j])
1074 for (j = colstart; j < colend; j++)
1075 cypos[j] = ytop + ch + VSPACING;
1077 if (hmin < ytop + ch)
1080 wthis = (cw + HSPACING) * 100 / (ccw[colend] - ccw[colstart]);
1089 * Add a bit to the width and height for the box.
1095 //printf("For controlset %s/%s, returning w=%d h=%d\n",
1096 // s->pathname, s->boxname, wmin, hmin);
1101 int place_ctrls(void *dv, struct controlset *s, int leftx, int topy,
1104 struct fe_dlg *d = (struct fe_dlg *)dv;
1105 int ccw[100]; /* cumulative column widths */
1109 int boxh = 0, boxw = 0;
1111 if (!s->boxname && s->boxtitle) {
1112 /* Size and place the panel title. */
1114 NSTextField *tf = find_box(d, s);
1118 [tf setFrame:NSMakeRect(leftx, topy-rect.size.height,
1119 width, rect.size.height)];
1120 return rect.size.height;
1124 NSRect rect, tmprect;
1125 NSBox *box = find_box(d, s);
1127 assert(box != NULL);
1128 tmprect = [box frame];
1129 [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
1131 [box setFrame:tmprect];
1132 boxw = rect.size.width - 100;
1133 boxh = rect.size.height - 100;
1135 boxh += [[box titleFont] pointSize];
1147 * Now iterate through the controls themselves, placing them
1150 for (i = 0; i < s->ncontrols; i++) {
1151 union control *ctrl = s->ctrls[i];
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;
1159 switch (ctrl->generic.type) {
1161 for (j = 1; j < ncols; j++)
1162 if (cypos[0] > cypos[j])
1163 cypos[0] = cypos[j];
1165 assert(ctrl->columns.ncols < lenof(ccw));
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];
1174 ncols = ctrl->columns.ncols;
1176 continue; /* no actual control created */
1178 continue; /* nothing to do here, move along */
1181 c = fe_ctrl_byctrl(d, ctrl);
1186 for (j = colstart; j < colend; j++) {
1187 if (ythis > cypos[j])
1191 xthis = (width + HSPACING) * ccw[colstart] / 100;
1192 wthis = (width + HSPACING) * ccw[colend] / 100 - HSPACING - xthis;
1195 switch (ctrl->generic.type) {
1198 rect = [c->button frame];
1199 [c->button setFrame:NSMakeRect(xthis,ythis-rect.size.height,wthis,
1201 ch = rect.size.height;
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);
1210 rect = [c->label frame];
1211 lheight = rect.size.height;
1212 rect = [edit frame];
1213 theight = rect.size.height;
1218 rheight = (lheight < theight ? theight : lheight);
1221 NSMakeRect(xthis, ythis-(rheight+lheight)/2,
1222 (wthis + HSPACING) * labelp / 100 - HSPACING,
1225 ynext = ythis - rheight - VSPACING;
1231 editw = (wthis + HSPACING) * editp / 100 - HSPACING;
1234 NSMakeRect(xthis+wthis-editw, ynext-(rheight+theight)/2,
1237 ch = (ythis - ynext) + theight;
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;
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;
1260 for (j = 0; j < ctrl->radio.nbuttons; j++) {
1261 int col = j % ctrl->radio.ncolumns;
1265 if (j == ctrl->radio.nbuttons - 1)
1266 ncols = ctrl->radio.ncolumns - col;
1270 lx = (wthis + HSPACING) * col / ctrl->radio.ncolumns;
1271 rx = ((wthis + HSPACING) *
1272 (col+ncols) / ctrl->radio.ncolumns) - HSPACING;
1275 * Set the frame size.
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)];
1283 * Advance to next line if we're in the last
1286 if (col + ncols == ctrl->radio.ncolumns)
1287 ynext -= rect.size.height + VSPACING;
1289 ch = (ythis - ynext) - VSPACING;
1292 case CTRL_FILESELECT:
1293 case CTRL_FONTSELECT:
1295 int ynext, eh, bh, th, mx;
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;
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);
1308 mx = (wthis + HSPACING) * 3 / 4 - HSPACING;
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)];
1316 ch = (ythis - ynext) + th + VSPACING;
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);
1327 if (ctrl->listbox.draglist) {
1328 assert(listp == 100);
1332 rect = [list frame];
1333 theight = rect.size.height;
1336 rect = [c->label frame];
1337 lheight = rect.size.height;
1342 rheight = (lheight < theight ? theight : lheight);
1345 NSMakeRect(xthis, ythis-(rheight+lheight)/2,
1346 (wthis + HSPACING) * labelp / 100 - HSPACING,
1348 if (labelp == 100) {
1349 ynext = ythis - rheight - VSPACING;
1359 listw = (wthis + HSPACING) * listp / 100 - HSPACING;
1364 xlist = xthis+wthis-listw;
1366 [list setFrame: NSMakeRect(xlist, ynext-(rheight+theight)/2,
1370 * Size the columns for the table view.
1373 int ncols, *percentages;
1375 int cpercent = 0, cpixels = 0;
1378 if (ctrl->listbox.ncols) {
1379 ncols = ctrl->listbox.ncols;
1380 percentages = ctrl->listbox.percentages;
1383 percentages = &hundred;
1386 cols = [c->tableview tableColumns];
1388 for (j = 0; j < ncols; j++) {
1389 NSTableColumn *col = [cols objectAtIndex:j];
1392 cpercent += percentages[j];
1393 newcpixels = listw * cpercent / 100;
1394 [col setWidth:newcpixels-cpixels];
1395 cpixels = newcpixels;
1399 ch = (ythis - ynext) + theight;
1402 int b2height, centre;
1406 * Place the Up and Down buttons for a drag list.
1410 rect = [c->button frame];
1411 b2height = VSPACING + 2 * rect.size.height;
1413 centre = ynext - rheight/2;
1415 bx = (wthis + HSPACING) * 3 / 4;
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)];
1430 for (j = colstart; j < colend; j++)
1431 cypos[j] = ythis - ch - VSPACING;
1432 if (ret < topy - (ythis - ch))
1433 ret = topy - (ythis - ch);
1437 NSBox *box = find_box(d, s);
1438 assert(box != NULL);
1442 NSRect rect = [box frame];
1443 rect.size.height += [[box titleFont] pointSize];
1444 [box setFrame:rect];
1450 //printf("For controlset %s/%s, returning ret=%d\n",
1451 // s->pathname, s->boxname, ret);
1455 void select_panel(void *dv, struct controlbox *b, const char *name)
1457 struct fe_dlg *d = (struct fe_dlg *)dv;
1459 struct controlset *s;
1460 union control *ctrl;
1464 for (i = 0; i < b->nctrlsets; i++) {
1468 hidden = !strcmp(s->pathname, name) ? NO : YES;
1470 if ((box = find_box(d, s)) != NULL) {
1471 [box setHidden:hidden];
1473 for (j = 0; j < s->ncontrols; j++) {
1475 c = fe_ctrl_byctrl(d, ctrl);
1481 [c->label setHidden:hidden];
1483 [c->button setHidden:hidden];
1485 [c->button2 setHidden:hidden];
1487 [c->editbox setHidden:hidden];
1489 [c->combobox setHidden:hidden];
1491 [c->textview setHidden:hidden];
1493 [c->tableview setHidden:hidden];
1495 [c->scrollview setHidden:hidden];
1497 [c->popupbutton setHidden:hidden];
1498 if (c->radiobuttons) {
1500 for (j = 0; j < c->nradiobuttons; j++)
1501 [c->radiobuttons[j] setHidden:hidden];
1510 void dlg_radiobutton_set(union control *ctrl, void *dv, int whichbutton)
1512 struct fe_dlg *d = (struct fe_dlg *)dv;
1513 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1516 assert(c->radiobuttons);
1517 for (j = 0; j < c->nradiobuttons; j++)
1518 [c->radiobuttons[j] setState:
1519 (j == whichbutton ? NSOnState : NSOffState)];
1522 int dlg_radiobutton_get(union control *ctrl, void *dv)
1524 struct fe_dlg *d = (struct fe_dlg *)dv;
1525 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1528 assert(c->radiobuttons);
1529 for (j = 0; j < c->nradiobuttons; j++)
1530 if ([c->radiobuttons[j] state] == NSOnState)
1533 return 0; /* should never reach here */
1536 void dlg_checkbox_set(union control *ctrl, void *dv, int checked)
1538 struct fe_dlg *d = (struct fe_dlg *)dv;
1539 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1542 [c->button setState:(checked ? NSOnState : NSOffState)];
1545 int dlg_checkbox_get(union control *ctrl, void *dv)
1547 struct fe_dlg *d = (struct fe_dlg *)dv;
1548 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1551 return ([c->button state] == NSOnState);
1554 void dlg_editbox_set(union control *ctrl, void *dv, char const *text)
1556 struct fe_dlg *d = (struct fe_dlg *)dv;
1557 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1560 [c->editbox setStringValue:[NSString stringWithCString:text]];
1562 assert(c->combobox);
1563 [c->combobox setStringValue:[NSString stringWithCString:text]];
1567 void dlg_editbox_get(union control *ctrl, void *dv, char *buffer, int length)
1569 struct fe_dlg *d = (struct fe_dlg *)dv;
1570 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1574 str = [c->editbox stringValue];
1576 assert(c->combobox);
1577 str = [c->combobox stringValue];
1582 /* The length parameter to this method doesn't include a trailing NUL */
1583 [str getCString:buffer maxLength:length-1];
1586 void dlg_listbox_clear(union control *ctrl, void *dv)
1588 struct fe_dlg *d = (struct fe_dlg *)dv;
1589 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1592 [[c->tableview dataSource] clear];
1593 [c->tableview reloadData];
1595 [c->popupbutton removeAllItems];
1599 void dlg_listbox_del(union control *ctrl, void *dv, int index)
1601 struct fe_dlg *d = (struct fe_dlg *)dv;
1602 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1605 [[c->tableview dataSource] removestr:index];
1606 [c->tableview reloadData];
1608 [c->popupbutton removeItemAtIndex:index];
1612 void dlg_listbox_addwithid(union control *ctrl, void *dv,
1613 char const *text, int id)
1615 struct fe_dlg *d = (struct fe_dlg *)dv;
1616 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1619 [[c->tableview dataSource] add:text withId:id];
1620 [c->tableview reloadData];
1622 [c->popupbutton addItemWithTitle:[NSString stringWithCString:text]];
1623 [[c->popupbutton lastItem] setTag:id];
1627 void dlg_listbox_add(union control *ctrl, void *dv, char const *text)
1629 dlg_listbox_addwithid(ctrl, dv, text, -1);
1632 int dlg_listbox_getid(union control *ctrl, void *dv, int index)
1634 struct fe_dlg *d = (struct fe_dlg *)dv;
1635 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1638 return [[c->tableview dataSource] getid:index];
1640 return [[c->popupbutton itemAtIndex:index] tag];
1644 int dlg_listbox_index(union control *ctrl, void *dv)
1646 struct fe_dlg *d = (struct fe_dlg *)dv;
1647 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1650 return [c->tableview selectedRow];
1652 return [c->popupbutton indexOfSelectedItem];
1656 int dlg_listbox_issel(union control *ctrl, void *dv, int index)
1658 struct fe_dlg *d = (struct fe_dlg *)dv;
1659 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1662 return [c->tableview isRowSelected:index];
1664 return [c->popupbutton indexOfSelectedItem] == index;
1668 void dlg_listbox_select(union control *ctrl, void *dv, int index)
1670 struct fe_dlg *d = (struct fe_dlg *)dv;
1671 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1674 [c->tableview selectRow:index byExtendingSelection:NO];
1676 [c->popupbutton selectItemAtIndex:index];
1680 void dlg_text_set(union control *ctrl, void *dv, char const *text)
1682 struct fe_dlg *d = (struct fe_dlg *)dv;
1683 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1685 assert(c->textview);
1686 [c->textview setString:[NSString stringWithCString:text]];
1689 void dlg_label_change(union control *ctrl, void *dlg, char const *text)
1692 * This function is currently only used by the config box to
1693 * switch the labels on the host and port boxes between serial
1694 * and network modes. Since OS X does not (yet?) have a serial
1695 * back end, this function can safely do nothing for the
1700 void dlg_filesel_set(union control *ctrl, void *dv, Filename fn)
1705 void dlg_filesel_get(union control *ctrl, void *dv, Filename *fn)
1710 void dlg_fontsel_set(union control *ctrl, void *dv, FontSpec fn)
1715 void dlg_fontsel_get(union control *ctrl, void *dv, FontSpec *fn)
1720 void dlg_update_start(union control *ctrl, void *dv)
1725 void dlg_update_done(union control *ctrl, void *dv)
1730 void dlg_set_focus(union control *ctrl, void *dv)
1735 union control *dlg_last_focused(union control *ctrl, void *dv)
1737 return NULL; /* FIXME */
1740 void dlg_beep(void *dv)
1745 void dlg_error_msg(void *dv, char *msg)
1750 void dlg_end(void *dv, int value)
1752 struct fe_dlg *d = (struct fe_dlg *)dv;
1753 [d->target performSelector:d->action
1754 withObject:[NSNumber numberWithInt:value]];
1757 void dlg_coloursel_start(union control *ctrl, void *dv,
1758 int r, int g, int b)
1763 int dlg_coloursel_results(union control *ctrl, void *dv,
1764 int *r, int *g, int *b)
1766 return 0; /* FIXME */
1769 void dlg_refresh(union control *ctrl, void *dv)
1771 struct fe_dlg *d = (struct fe_dlg *)dv;
1775 if (ctrl->generic.handler != NULL)
1776 ctrl->generic.handler(ctrl, d, d->data, EVENT_REFRESH);
1780 for (i = 0; (c = index234(d->byctrl, i)) != NULL; i++) {
1781 assert(c->ctrl != NULL);
1782 if (c->ctrl->generic.handler != NULL)
1783 c->ctrl->generic.handler(c->ctrl, d,
1784 d->data, EVENT_REFRESH);
1789 void *dlg_get_privdata(union control *ctrl, void *dv)
1791 struct fe_dlg *d = (struct fe_dlg *)dv;
1792 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1796 void dlg_set_privdata(union control *ctrl, void *dv, void *ptr)
1798 struct fe_dlg *d = (struct fe_dlg *)dv;
1799 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1801 c->privdata_needs_free = FALSE;
1804 void *dlg_alloc_privdata(union control *ctrl, void *dv, size_t size)
1806 struct fe_dlg *d = (struct fe_dlg *)dv;
1807 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1809 * This is an internal allocation routine, so it's allowed to
1810 * use smalloc directly.
1812 c->privdata = smalloc(size);
1813 c->privdata_needs_free = TRUE;