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;
248 static int fe_ctrl_cmp_by_ctrl(void *av, void *bv)
250 struct fe_ctrl *a = (struct fe_ctrl *)av;
251 struct fe_ctrl *b = (struct fe_ctrl *)bv;
253 if (a->ctrl < b->ctrl)
255 if (a->ctrl > b->ctrl)
260 static int fe_ctrl_find_by_ctrl(void *av, void *bv)
262 union control *a = (union control *)av;
263 struct fe_ctrl *b = (struct fe_ctrl *)bv;
273 struct controlset *s;
277 static int fe_boxcmp(void *av, void *bv)
279 struct fe_box *a = (struct fe_box *)av;
280 struct fe_box *b = (struct fe_box *)bv;
289 static int fe_boxfind(void *av, void *bv)
291 struct controlset *a = (struct controlset *)av;
292 struct fe_box *b = (struct fe_box *)bv;
301 struct fe_backwards { /* map Cocoa widgets back to fe_ctrls */
306 static int fe_backwards_cmp_by_widget(void *av, void *bv)
308 struct fe_backwards *a = (struct fe_backwards *)av;
309 struct fe_backwards *b = (struct fe_backwards *)bv;
311 if (a->widget < b->widget)
313 if (a->widget > b->widget)
318 static int fe_backwards_find_by_widget(void *av, void *bv)
321 struct fe_backwards *b = (struct fe_backwards *)bv;
330 static struct fe_ctrl *fe_ctrl_new(union control *ctrl)
334 c = snew(struct fe_ctrl);
337 c->button = c->button2 = nil;
342 c->popupbutton = nil;
345 c->radiobuttons = NULL;
346 c->nradiobuttons = 0;
351 static void fe_ctrl_free(struct fe_ctrl *c)
353 sfree(c->radiobuttons);
357 static struct fe_ctrl *fe_ctrl_byctrl(struct fe_dlg *d, union control *ctrl)
359 return find234(d->byctrl, ctrl, fe_ctrl_find_by_ctrl);
362 static void add_box(struct fe_dlg *d, struct controlset *s, id box)
364 struct fe_box *b = snew(struct fe_box);
370 static id find_box(struct fe_dlg *d, struct controlset *s)
372 struct fe_box *b = find234(d->boxes, s, fe_boxfind);
373 return b ? b->box : NULL;
376 static void add_widget(struct fe_dlg *d, struct fe_ctrl *c, id widget)
378 struct fe_backwards *b = snew(struct fe_backwards);
381 add234(d->bywidget, b);
384 static struct fe_ctrl *find_widget(struct fe_dlg *d, id widget)
386 struct fe_backwards *b = find234(d->bywidget, widget,
387 fe_backwards_find_by_widget);
388 return b ? b->c : NULL;
391 void *fe_dlg_init(void *data, NSWindow *window, NSObject *target, SEL action)
395 d = snew(struct fe_dlg);
399 d->byctrl = newtree234(fe_ctrl_cmp_by_ctrl);
400 d->bywidget = newtree234(fe_backwards_cmp_by_widget);
401 d->boxes = newtree234(fe_boxcmp);
403 d->rec = [[Receiver alloc] initWithStruct:d];
408 void fe_dlg_free(void *dv)
410 struct fe_dlg *d = (struct fe_dlg *)dv;
414 while ( (c = delpos234(d->byctrl, 0)) != NULL )
416 freetree234(d->byctrl);
418 while ( (c = delpos234(d->bywidget, 0)) != NULL )
420 freetree234(d->bywidget);
422 while ( (b = delpos234(d->boxes, 0)) != NULL )
424 freetree234(d->boxes);
431 @implementation Receiver
432 - (id)initWithStruct:(struct fe_dlg *)aStruct
438 - (void)buttonPushed:(id)sender
440 struct fe_ctrl *c = find_widget(d, sender);
442 assert(c && c->ctrl->generic.type == CTRL_BUTTON);
443 c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
445 - (void)checkboxChanged:(id)sender
447 struct fe_ctrl *c = find_widget(d, sender);
449 assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
450 c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
452 - (void)radioChanged:(id)sender
454 struct fe_ctrl *c = find_widget(d, sender);
457 assert(c && c->radiobuttons);
458 for (j = 0; j < c->nradiobuttons; j++)
459 if (sender != c->radiobuttons[j])
460 [c->radiobuttons[j] setState:NSOffState];
461 c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
463 - (void)popupMenuSelected:(id)sender
465 struct fe_ctrl *c = find_widget(d, sender);
466 c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
468 - (void)controlTextDidChange:(NSNotification *)notification
470 id widget = [notification object];
471 struct fe_ctrl *c = find_widget(d, widget);
472 assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
473 c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
475 - (void)controlTextDidEndEditing:(NSNotification *)notification
477 id widget = [notification object];
478 struct fe_ctrl *c = find_widget(d, widget);
479 assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
480 c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_REFRESH);
482 - (void)tableViewSelectionDidChange:(NSNotification *)notification
484 id widget = [notification object];
485 struct fe_ctrl *c = find_widget(d, widget);
486 assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
487 c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_SELCHANGE);
489 - (BOOL)tableView:(NSTableView *)aTableView
490 shouldEditTableColumn:(NSTableColumn *)aTableColumn
493 return NO; /* no editing permitted */
495 - (void)listDoubleClicked:(id)sender
497 struct fe_ctrl *c = find_widget(d, sender);
498 assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
499 c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
501 - (void)dragListButton:(id)sender
503 struct fe_ctrl *c = find_widget(d, sender);
504 int direction, row, nrows;
505 assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
506 c->ctrl->listbox.draglist);
508 if (sender == c->button)
509 direction = -1; /* up */
511 direction = +1; /* down */
513 row = [c->tableview selectedRow];
514 nrows = [c->tableview numberOfRows];
516 if (row + direction < 0 || row + direction >= nrows) {
521 [[c->tableview dataSource] swap:row with:row+direction];
522 [c->tableview reloadData];
523 [c->tableview selectRow:row+direction byExtendingSelection:NO];
525 c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
529 void create_ctrls(void *dv, NSView *parent, struct controlset *s,
530 int *minw, int *minh)
532 struct fe_dlg *d = (struct fe_dlg *)dv;
533 int ccw[100]; /* cumulative column widths */
536 int wmin = 0, hmin = 0;
539 NSFont *textviewfont = nil;
540 int boxh = 0, boxw = 0;
542 if (!s->boxname && s->boxtitle) {
543 /* This controlset is a panel title. */
547 tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
549 [tf setSelectable:NO];
551 [tf setDrawsBackground:NO];
552 [tf setStringValue:[NSString stringWithCString:s->boxtitle]];
555 [parent addSubview:tf];
558 * I'm going to store this NSTextField in the boxes tree,
559 * because I really can't face having a special tree234
560 * mapping controlsets to panel titles.
564 *minw = rect.size.width;
565 *minh = rect.size.height;
572 * Create an NSBox to contain this subset of controls.
577 box = [[NSBox alloc] initWithFrame:NSMakeRect(0,0,1,1)];
579 [box setTitle:[NSString stringWithCString:s->boxtitle]];
581 [box setTitlePosition:NSNoTitle];
583 tmprect = [box frame];
584 [box setContentViewMargins:NSMakeSize(20,20)];
585 [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
587 [box setFrame:tmprect];
588 boxh = (int)(rect.size.height - 100);
589 boxw = (int)(rect.size.width - 100);
590 [parent addSubview:box];
593 boxh += [[box titleFont] pointSize];
596 * All subsequent controls will be placed within this box.
607 * Now iterate through the controls themselves, create them,
608 * and add their width and height to the overall width/height
611 for (i = 0; i < s->ncontrols; i++) {
612 union control *ctrl = s->ctrls[i];
614 int colstart = COLUMN_START(ctrl->generic.column);
615 int colspan = COLUMN_SPAN(ctrl->generic.column);
616 int colend = colstart + colspan;
619 switch (ctrl->generic.type) {
621 for (j = 1; j < ncols; j++)
622 if (cypos[0] < cypos[j])
625 assert(ctrl->columns.ncols < lenof(ccw));
628 for (j = 0; j < ctrl->columns.ncols; j++) {
629 ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
630 ctrl->columns.percentages[j] : 100);
634 ncols = ctrl->columns.ncols;
636 continue; /* no actual control created */
639 * I'm currently uncertain that we can implement tab
642 continue; /* no actual control created */
645 c = fe_ctrl_new(ctrl);
646 add234(d->byctrl, c);
650 switch (ctrl->generic.type) {
656 b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
657 [b setBezelStyle:NSRoundedBezelStyle];
658 if (ctrl->generic.type == CTRL_CHECKBOX)
659 [b setButtonType:NSSwitchButton];
660 [b setTitle:[NSString stringWithCString:ctrl->generic.label]];
661 if (ctrl->button.isdefault)
662 [b setKeyEquivalent:@"\r"];
663 else if (ctrl->button.iscancel)
664 [b setKeyEquivalent:@"\033"];
668 [parent addSubview:b];
670 [b setTarget:d->rec];
671 if (ctrl->generic.type == CTRL_CHECKBOX)
672 [b setAction:@selector(checkboxChanged:)];
674 [b setAction:@selector(buttonPushed:)];
679 cw = rect.size.width;
680 ch = rect.size.height;
685 int editp = ctrl->editbox.percentwidth;
686 int labelp = editp == 100 ? 100 : 100 - editp;
690 tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
692 [tf setSelectable:NO];
694 [tf setDrawsBackground:NO];
695 [tf setStringValue:[NSString
696 stringWithCString:ctrl->generic.label]];
699 [parent addSubview:tf];
702 cw = rect.size.width * 100 / labelp;
703 ch = rect.size.height;
705 if (ctrl->editbox.has_list) {
706 cb = [[NSComboBox alloc]
707 initWithFrame:NSMakeRect(0,0,1,1)];
708 [cb setStringValue:@"x"];
711 [parent addSubview:cb];
714 if (ctrl->editbox.password)
715 tf = [NSSecureTextField alloc];
717 tf = [NSTextField alloc];
719 tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
720 [tf setEditable:YES];
721 [tf setSelectable:YES];
722 [tf setBordered:YES];
723 [tf setStringValue:@"x"];
726 [parent addSubview:tf];
729 [tf setDelegate:d->rec];
730 add_widget(d, c, tf);
734 /* the edit box and its label are vertically separated */
735 ch += VSPACING + rect.size.height;
737 /* the edit box and its label are horizontally separated */
738 if (ch < rect.size.height)
739 ch = rect.size.height;
742 if (cw < rect.size.width * 100 / editp)
743 cw = rect.size.width * 100 / editp;
753 tf = [[NSTextField alloc] init];
754 textviewfont = [tf font];
758 testwid = (ccw[colend] - ccw[colstart]) * 3;
760 tv = [[NSTextView alloc]
761 initWithFrame:NSMakeRect(0,0,testwid,1)];
763 [tv setSelectable:NO];
764 //[tv setBordered:NO];
765 [tv setDrawsBackground:NO];
766 [tv setFont:textviewfont];
768 [NSString stringWithCString:ctrl->generic.label]];
771 [parent addSubview:tv];
774 cw = rect.size.width;
775 ch = rect.size.height;
783 if (ctrl->generic.label) {
784 tf = [[NSTextField alloc]
785 initWithFrame:NSMakeRect(0,0,1,1)];
787 [tf setSelectable:NO];
789 [tf setDrawsBackground:NO];
791 [NSString stringWithCString:ctrl->generic.label]];
794 [parent addSubview:tf];
797 cw = rect.size.width;
798 ch = rect.size.height;
801 ch = -VSPACING; /* compensate for next advance */
804 c->nradiobuttons = ctrl->radio.nbuttons;
805 c->radiobuttons = snewn(ctrl->radio.nbuttons, NSButton *);
807 for (j = 0; j < ctrl->radio.nbuttons; j++) {
811 b = [[MyButton alloc] initWithFrame:NSMakeRect(0,0,1,1)];
812 [b setBezelStyle:NSRoundedBezelStyle];
813 [b setButtonType:NSRadioButton];
814 [b setTitle:[NSString
815 stringWithCString:ctrl->radio.buttons[j]]];
818 [parent addSubview:b];
820 c->radiobuttons[j] = b;
822 [b setTarget:d->rec];
823 [b setAction:@selector(radioChanged:)];
827 * Add to the height every time we place a
828 * button in column 0.
830 if (j % ctrl->radio.ncolumns == 0) {
831 ch += rect.size.height + VSPACING;
835 * Add to the width by working out how many
836 * columns this button spans.
838 if (j == ctrl->radio.nbuttons - 1)
839 ncols = (ctrl->radio.ncolumns -
840 (j % ctrl->radio.ncolumns));
844 if (cw < rect.size.width * ctrl->radio.ncolumns / ncols)
845 cw = rect.size.width * ctrl->radio.ncolumns / ncols;
849 case CTRL_FILESELECT:
850 case CTRL_FONTSELECT:
856 tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
858 [tf setSelectable:NO];
860 [tf setDrawsBackground:NO];
861 [tf setStringValue:[NSString
862 stringWithCString:ctrl->generic.label]];
865 [parent addSubview:tf];
868 cw = rect.size.width;
869 ch = rect.size.height;
871 tf = [NSTextField alloc];
872 tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
873 if (ctrl->generic.type == CTRL_FILESELECT) {
874 [tf setEditable:YES];
875 [tf setSelectable:YES];
876 [tf setBordered:YES];
879 [tf setSelectable:NO];
881 [tf setDrawsBackground:NO];
883 [tf setStringValue:@"x"];
886 [parent addSubview:tf];
889 kh = rect.size.height;
890 if (cw < rect.size.width * 4 / 3)
891 cw = rect.size.width * 4 / 3;
893 b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
894 [b setBezelStyle:NSRoundedBezelStyle];
895 if (ctrl->generic.type == CTRL_FILESELECT)
896 [b setTitle:@"Browse..."];
898 [b setTitle:@"Change..."];
899 // [b setKeyEquivalent:somethingorother];
900 // [b setTarget:somethingorother];
901 // [b setAction:somethingorother];
904 [parent addSubview:b];
908 if (kh < rect.size.height)
909 kh = rect.size.height;
911 if (cw < rect.size.width * 4)
912 cw = rect.size.width * 4;
917 int listp = ctrl->listbox.percentwidth;
918 int labelp = listp == 100 ? 100 : 100 - listp;
924 if (ctrl->generic.label) {
925 tf = [[NSTextField alloc]
926 initWithFrame:NSMakeRect(0,0,1,1)];
928 [tf setSelectable:NO];
930 [tf setDrawsBackground:NO];
932 [NSString stringWithCString:ctrl->generic.label]];
935 [parent addSubview:tf];
938 cw = rect.size.width;
939 ch = rect.size.height;
942 ch = -VSPACING; /* compensate for next advance */
945 if (ctrl->listbox.height == 0) {
946 pb = [[NSPopUpButton alloc]
947 initWithFrame:NSMakeRect(0,0,1,1)];
950 [parent addSubview:pb];
953 [pb setTarget:d->rec];
954 [pb setAction:@selector(popupMenuSelected:)];
955 add_widget(d, c, pb);
957 assert(listp == 100);
958 if (ctrl->listbox.draglist) {
963 for (bi = 0; bi < 2; bi++) {
965 b = [[MyButton alloc]
966 initWithFrame:NSMakeRect(0, 0, 1, 1)];
967 [b setBezelStyle:NSRoundedBezelStyle];
971 [b setTitle:@"Down"];
974 [parent addSubview:b];
981 [b setTarget:d->rec];
982 [b setAction:@selector(dragListButton:)];
985 if (cw < rect.size.width * 4)
986 cw = rect.size.width * 4;
990 sv = [[NSScrollView alloc] initWithFrame:
991 NSMakeRect(20,20,10,10)];
992 [sv setBorderType:NSLineBorder];
993 tv = [[NSTableView alloc] initWithFrame:[sv frame]];
994 [[tv headerView] setFrame:NSMakeRect(0,0,0,0)];
995 [sv setDocumentView:tv];
996 [parent addSubview:sv];
997 [sv setHasVerticalScroller:YES];
998 [sv setAutohidesScrollers:YES];
999 [tv setAllowsColumnReordering:NO];
1000 [tv setAllowsColumnResizing:NO];
1001 [tv setAllowsMultipleSelection:ctrl->listbox.multisel];
1002 [tv setAllowsEmptySelection:YES];
1003 [tv setAllowsColumnSelection:YES];
1004 [tv setDataSource:[[MyTableSource alloc] init]];
1007 * For some reason this consistently comes out
1008 * one short. Add one.
1010 rect.size.height = (ctrl->listbox.height+1)*[tv rowHeight];
1015 [tv setDelegate:d->rec];
1016 [tv setTarget:d->rec];
1017 [tv setDoubleAction:@selector(listDoubleClicked:)];
1018 add_widget(d, c, tv);
1022 int ncols, *percentages;
1025 if (ctrl->listbox.ncols) {
1026 ncols = ctrl->listbox.ncols;
1027 percentages = ctrl->listbox.percentages;
1030 percentages = &hundred;
1033 for (j = 0; j < ncols; j++) {
1036 col = [[NSTableColumn alloc] initWithIdentifier:
1037 [NSNumber numberWithInt:j]];
1038 [c->tableview addTableColumn:col];
1042 if (labelp == 100) {
1043 /* the list and its label are vertically separated */
1044 ch += VSPACING + rect.size.height;
1046 /* the list and its label are horizontally separated */
1047 if (ch < rect.size.height)
1048 ch = rect.size.height;
1051 if (cw < rect.size.width * 100 / listp)
1052 cw = rect.size.width * 100 / listp;
1058 * Update the width and height data for the control we've
1063 for (j = colstart; j < colend; j++) {
1064 if (ytop < cypos[j])
1068 for (j = colstart; j < colend; j++)
1069 cypos[j] = ytop + ch + VSPACING;
1071 if (hmin < ytop + ch)
1074 wthis = (cw + HSPACING) * 100 / (ccw[colend] - ccw[colstart]);
1083 * Add a bit to the width and height for the box.
1089 //printf("For controlset %s/%s, returning w=%d h=%d\n",
1090 // s->pathname, s->boxname, wmin, hmin);
1095 int place_ctrls(void *dv, struct controlset *s, int leftx, int topy,
1098 struct fe_dlg *d = (struct fe_dlg *)dv;
1099 int ccw[100]; /* cumulative column widths */
1103 int boxh = 0, boxw = 0;
1105 if (!s->boxname && s->boxtitle) {
1106 /* Size and place the panel title. */
1108 NSTextField *tf = find_box(d, s);
1112 [tf setFrame:NSMakeRect(leftx, topy-rect.size.height,
1113 width, rect.size.height)];
1114 return rect.size.height;
1118 NSRect rect, tmprect;
1119 NSBox *box = find_box(d, s);
1121 assert(box != NULL);
1122 tmprect = [box frame];
1123 [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
1125 [box setFrame:tmprect];
1126 boxw = rect.size.width - 100;
1127 boxh = rect.size.height - 100;
1129 boxh += [[box titleFont] pointSize];
1141 * Now iterate through the controls themselves, placing them
1144 for (i = 0; i < s->ncontrols; i++) {
1145 union control *ctrl = s->ctrls[i];
1147 int colstart = COLUMN_START(ctrl->generic.column);
1148 int colspan = COLUMN_SPAN(ctrl->generic.column);
1149 int colend = colstart + colspan;
1150 int xthis, ythis, wthis, ch;
1153 switch (ctrl->generic.type) {
1155 for (j = 1; j < ncols; j++)
1156 if (cypos[0] > cypos[j])
1157 cypos[0] = cypos[j];
1159 assert(ctrl->columns.ncols < lenof(ccw));
1162 for (j = 0; j < ctrl->columns.ncols; j++) {
1163 ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
1164 ctrl->columns.percentages[j] : 100);
1165 cypos[j] = cypos[0];
1168 ncols = ctrl->columns.ncols;
1170 continue; /* no actual control created */
1172 continue; /* nothing to do here, move along */
1175 c = fe_ctrl_byctrl(d, ctrl);
1180 for (j = colstart; j < colend; j++) {
1181 if (ythis > cypos[j])
1185 xthis = (width + HSPACING) * ccw[colstart] / 100;
1186 wthis = (width + HSPACING) * ccw[colend] / 100 - HSPACING - xthis;
1189 switch (ctrl->generic.type) {
1192 rect = [c->button frame];
1193 [c->button setFrame:NSMakeRect(xthis,ythis-rect.size.height,wthis,
1195 ch = rect.size.height;
1199 int editp = ctrl->editbox.percentwidth;
1200 int labelp = editp == 100 ? 100 : 100 - editp;
1201 int lheight, theight, rheight, ynext, editw;
1202 NSControl *edit = (c->editbox ? c->editbox : c->combobox);
1204 rect = [c->label frame];
1205 lheight = rect.size.height;
1206 rect = [edit frame];
1207 theight = rect.size.height;
1212 rheight = (lheight < theight ? theight : lheight);
1215 NSMakeRect(xthis, ythis-(rheight+lheight)/2,
1216 (wthis + HSPACING) * labelp / 100 - HSPACING,
1219 ynext = ythis - rheight - VSPACING;
1225 editw = (wthis + HSPACING) * editp / 100 - HSPACING;
1228 NSMakeRect(xthis+wthis-editw, ynext-(rheight+theight)/2,
1231 ch = (ythis - ynext) + theight;
1235 [c->textview setFrame:NSMakeRect(xthis, 0, wthis, 1)];
1236 [c->textview sizeToFit];
1237 rect = [c->textview frame];
1238 [c->textview setFrame:NSMakeRect(xthis, ythis-rect.size.height,
1239 wthis, rect.size.height)];
1240 ch = rect.size.height;
1247 rect = [c->label frame];
1248 [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
1249 wthis,rect.size.height)];
1250 ynext = ythis - rect.size.height - VSPACING;
1254 for (j = 0; j < ctrl->radio.nbuttons; j++) {
1255 int col = j % ctrl->radio.ncolumns;
1259 if (j == ctrl->radio.nbuttons - 1)
1260 ncols = ctrl->radio.ncolumns - col;
1264 lx = (wthis + HSPACING) * col / ctrl->radio.ncolumns;
1265 rx = ((wthis + HSPACING) *
1266 (col+ncols) / ctrl->radio.ncolumns) - HSPACING;
1269 * Set the frame size.
1271 rect = [c->radiobuttons[j] frame];
1272 [c->radiobuttons[j] setFrame:
1273 NSMakeRect(lx+xthis, ynext-rect.size.height,
1274 rx-lx, rect.size.height)];
1277 * Advance to next line if we're in the last
1280 if (col + ncols == ctrl->radio.ncolumns)
1281 ynext -= rect.size.height + VSPACING;
1283 ch = (ythis - ynext) - VSPACING;
1286 case CTRL_FILESELECT:
1287 case CTRL_FONTSELECT:
1289 int ynext, eh, bh, th, mx;
1291 rect = [c->label frame];
1292 [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
1293 wthis,rect.size.height)];
1294 ynext = ythis - rect.size.height - VSPACING;
1296 rect = [c->editbox frame];
1297 eh = rect.size.height;
1298 rect = [c->button frame];
1299 bh = rect.size.height;
1300 th = (eh > bh ? eh : bh);
1302 mx = (wthis + HSPACING) * 3 / 4 - HSPACING;
1304 [c->editbox setFrame:
1305 NSMakeRect(xthis, ynext-(th+eh)/2, mx, eh)];
1306 [c->button setFrame:
1307 NSMakeRect(xthis+mx+HSPACING, ynext-(th+bh)/2,
1308 wthis-mx-HSPACING, bh)];
1310 ch = (ythis - ynext) + th + VSPACING;
1315 int listp = ctrl->listbox.percentwidth;
1316 int labelp = listp == 100 ? 100 : 100 - listp;
1317 int lheight, theight, rheight, ynext, listw, xlist;
1318 NSControl *list = (c->scrollview ? (id)c->scrollview :
1319 (id)c->popupbutton);
1321 if (ctrl->listbox.draglist) {
1322 assert(listp == 100);
1326 rect = [list frame];
1327 theight = rect.size.height;
1330 rect = [c->label frame];
1331 lheight = rect.size.height;
1336 rheight = (lheight < theight ? theight : lheight);
1339 NSMakeRect(xthis, ythis-(rheight+lheight)/2,
1340 (wthis + HSPACING) * labelp / 100 - HSPACING,
1342 if (labelp == 100) {
1343 ynext = ythis - rheight - VSPACING;
1353 listw = (wthis + HSPACING) * listp / 100 - HSPACING;
1358 xlist = xthis+wthis-listw;
1360 [list setFrame: NSMakeRect(xlist, ynext-(rheight+theight)/2,
1364 * Size the columns for the table view.
1367 int ncols, *percentages;
1369 int cpercent = 0, cpixels = 0;
1372 if (ctrl->listbox.ncols) {
1373 ncols = ctrl->listbox.ncols;
1374 percentages = ctrl->listbox.percentages;
1377 percentages = &hundred;
1380 cols = [c->tableview tableColumns];
1382 for (j = 0; j < ncols; j++) {
1383 NSTableColumn *col = [cols objectAtIndex:j];
1386 cpercent += percentages[j];
1387 newcpixels = listw * cpercent / 100;
1388 [col setWidth:newcpixels-cpixels];
1389 cpixels = newcpixels;
1393 ch = (ythis - ynext) + theight;
1396 int b2height, centre;
1400 * Place the Up and Down buttons for a drag list.
1404 rect = [c->button frame];
1405 b2height = VSPACING + 2 * rect.size.height;
1407 centre = ynext - rheight/2;
1409 bx = (wthis + HSPACING) * 3 / 4;
1413 [c->button setFrame:
1414 NSMakeRect(bx, centre+b2height/2-rect.size.height,
1415 bw, rect.size.height)];
1416 [c->button2 setFrame:
1417 NSMakeRect(bx, centre-b2height/2,
1418 bw, rect.size.height)];
1424 for (j = colstart; j < colend; j++)
1425 cypos[j] = ythis - ch - VSPACING;
1426 if (ret < topy - (ythis - ch))
1427 ret = topy - (ythis - ch);
1431 NSBox *box = find_box(d, s);
1432 assert(box != NULL);
1436 NSRect rect = [box frame];
1437 rect.size.height += [[box titleFont] pointSize];
1438 [box setFrame:rect];
1444 //printf("For controlset %s/%s, returning ret=%d\n",
1445 // s->pathname, s->boxname, ret);
1449 void select_panel(void *dv, struct controlbox *b, const char *name)
1451 struct fe_dlg *d = (struct fe_dlg *)dv;
1453 struct controlset *s;
1454 union control *ctrl;
1458 for (i = 0; i < b->nctrlsets; i++) {
1462 hidden = !strcmp(s->pathname, name) ? NO : YES;
1464 if ((box = find_box(d, s)) != NULL) {
1465 [box setHidden:hidden];
1467 for (j = 0; j < s->ncontrols; j++) {
1469 c = fe_ctrl_byctrl(d, ctrl);
1475 [c->label setHidden:hidden];
1477 [c->button setHidden:hidden];
1479 [c->button2 setHidden:hidden];
1481 [c->editbox setHidden:hidden];
1483 [c->combobox setHidden:hidden];
1485 [c->textview setHidden:hidden];
1487 [c->tableview setHidden:hidden];
1489 [c->scrollview setHidden:hidden];
1491 [c->popupbutton setHidden:hidden];
1492 if (c->radiobuttons) {
1494 for (j = 0; j < c->nradiobuttons; j++)
1495 [c->radiobuttons[j] setHidden:hidden];
1504 void dlg_radiobutton_set(union control *ctrl, void *dv, int whichbutton)
1506 struct fe_dlg *d = (struct fe_dlg *)dv;
1507 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1510 assert(c->radiobuttons);
1511 for (j = 0; j < c->nradiobuttons; j++)
1512 [c->radiobuttons[j] setState:
1513 (j == whichbutton ? NSOnState : NSOffState)];
1516 int dlg_radiobutton_get(union control *ctrl, void *dv)
1518 struct fe_dlg *d = (struct fe_dlg *)dv;
1519 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1522 assert(c->radiobuttons);
1523 for (j = 0; j < c->nradiobuttons; j++)
1524 if ([c->radiobuttons[j] state] == NSOnState)
1527 return 0; /* should never reach here */
1530 void dlg_checkbox_set(union control *ctrl, void *dv, int checked)
1532 struct fe_dlg *d = (struct fe_dlg *)dv;
1533 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1536 [c->button setState:(checked ? NSOnState : NSOffState)];
1539 int dlg_checkbox_get(union control *ctrl, void *dv)
1541 struct fe_dlg *d = (struct fe_dlg *)dv;
1542 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1545 return ([c->button state] == NSOnState);
1548 void dlg_editbox_set(union control *ctrl, void *dv, char const *text)
1550 struct fe_dlg *d = (struct fe_dlg *)dv;
1551 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1554 [c->editbox setStringValue:[NSString stringWithCString:text]];
1556 assert(c->combobox);
1557 [c->combobox setStringValue:[NSString stringWithCString:text]];
1561 void dlg_editbox_get(union control *ctrl, void *dv, char *buffer, int length)
1563 struct fe_dlg *d = (struct fe_dlg *)dv;
1564 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1568 str = [c->editbox stringValue];
1570 assert(c->combobox);
1571 str = [c->combobox stringValue];
1576 /* The length parameter to this method doesn't include a trailing NUL */
1577 [str getCString:buffer maxLength:length-1];
1580 void dlg_listbox_clear(union control *ctrl, void *dv)
1582 struct fe_dlg *d = (struct fe_dlg *)dv;
1583 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1586 [[c->tableview dataSource] clear];
1587 [c->tableview reloadData];
1589 [c->popupbutton removeAllItems];
1593 void dlg_listbox_del(union control *ctrl, void *dv, int index)
1595 struct fe_dlg *d = (struct fe_dlg *)dv;
1596 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1599 [[c->tableview dataSource] removestr:index];
1600 [c->tableview reloadData];
1602 [c->popupbutton removeItemAtIndex:index];
1606 void dlg_listbox_addwithid(union control *ctrl, void *dv,
1607 char const *text, int id)
1609 struct fe_dlg *d = (struct fe_dlg *)dv;
1610 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1613 [[c->tableview dataSource] add:text withId:id];
1614 [c->tableview reloadData];
1616 [c->popupbutton addItemWithTitle:[NSString stringWithCString:text]];
1617 [[c->popupbutton lastItem] setTag:id];
1621 void dlg_listbox_add(union control *ctrl, void *dv, char const *text)
1623 dlg_listbox_addwithid(ctrl, dv, text, -1);
1626 int dlg_listbox_getid(union control *ctrl, void *dv, int index)
1628 struct fe_dlg *d = (struct fe_dlg *)dv;
1629 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1632 return [[c->tableview dataSource] getid:index];
1634 return [[c->popupbutton itemAtIndex:index] tag];
1638 int dlg_listbox_index(union control *ctrl, void *dv)
1640 struct fe_dlg *d = (struct fe_dlg *)dv;
1641 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1644 return [c->tableview selectedRow];
1646 return [c->popupbutton indexOfSelectedItem];
1650 int dlg_listbox_issel(union control *ctrl, void *dv, int index)
1652 struct fe_dlg *d = (struct fe_dlg *)dv;
1653 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1656 return [c->tableview isRowSelected:index];
1658 return [c->popupbutton indexOfSelectedItem] == index;
1662 void dlg_listbox_select(union control *ctrl, void *dv, int index)
1664 struct fe_dlg *d = (struct fe_dlg *)dv;
1665 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1668 [c->tableview selectRow:index byExtendingSelection:NO];
1670 [c->popupbutton selectItemAtIndex:index];
1674 void dlg_text_set(union control *ctrl, void *dv, char const *text)
1676 struct fe_dlg *d = (struct fe_dlg *)dv;
1677 struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
1679 assert(c->textview);
1680 [c->textview setString:[NSString stringWithCString:text]];
1683 void dlg_label_change(union control *ctrl, void *dlg, char const *text)
1686 * This function is currently only used by the config box to
1687 * switch the labels on the host and port boxes between serial
1688 * and network modes. Since OS X does not (yet?) have a serial
1689 * back end, this function can safely do nothing for the
1694 void dlg_filesel_set(union control *ctrl, void *dv, Filename fn)
1699 void dlg_filesel_get(union control *ctrl, void *dv, Filename *fn)
1704 void dlg_fontsel_set(union control *ctrl, void *dv, FontSpec fn)
1709 void dlg_fontsel_get(union control *ctrl, void *dv, FontSpec *fn)
1714 void dlg_update_start(union control *ctrl, void *dv)
1719 void dlg_update_done(union control *ctrl, void *dv)
1724 void dlg_set_focus(union control *ctrl, void *dv)
1729 union control *dlg_last_focused(union control *ctrl, void *dv)
1731 return NULL; /* FIXME */
1734 void dlg_beep(void *dv)
1739 void dlg_error_msg(void *dv, char *msg)
1744 void dlg_end(void *dv, int value)
1746 struct fe_dlg *d = (struct fe_dlg *)dv;
1747 [d->target performSelector:d->action
1748 withObject:[NSNumber numberWithInt:value]];
1751 void dlg_coloursel_start(union control *ctrl, void *dv,
1752 int r, int g, int b)
1757 int dlg_coloursel_results(union control *ctrl, void *dv,
1758 int *r, int *g, int *b)
1760 return 0; /* FIXME */
1763 void dlg_refresh(union control *ctrl, void *dv)
1765 struct fe_dlg *d = (struct fe_dlg *)dv;
1769 if (ctrl->generic.handler != NULL)
1770 ctrl->generic.handler(ctrl, d, d->data, EVENT_REFRESH);
1774 for (i = 0; (c = index234(d->byctrl, i)) != NULL; i++) {
1775 assert(c->ctrl != NULL);
1776 if (c->ctrl->generic.handler != NULL)
1777 c->ctrl->generic.handler(c->ctrl, d,
1778 d->data, EVENT_REFRESH);