1ddda1ca |
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 | |
7374c779 |
1689 | void dlg_label_change(union control *ctrl, void *dlg, char const *text) |
1690 | { |
1691 | /* |
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 |
1696 | * moment. |
1697 | */ |
1698 | } |
1699 | |
1ddda1ca |
1700 | void dlg_filesel_set(union control *ctrl, void *dv, Filename fn) |
1701 | { |
1702 | /* FIXME */ |
1703 | } |
1704 | |
1705 | void dlg_filesel_get(union control *ctrl, void *dv, Filename *fn) |
1706 | { |
1707 | /* FIXME */ |
1708 | } |
1709 | |
1710 | void dlg_fontsel_set(union control *ctrl, void *dv, FontSpec fn) |
1711 | { |
1712 | /* FIXME */ |
1713 | } |
1714 | |
1715 | void dlg_fontsel_get(union control *ctrl, void *dv, FontSpec *fn) |
1716 | { |
1717 | /* FIXME */ |
1718 | } |
1719 | |
1720 | void dlg_update_start(union control *ctrl, void *dv) |
1721 | { |
1722 | /* FIXME */ |
1723 | } |
1724 | |
1725 | void dlg_update_done(union control *ctrl, void *dv) |
1726 | { |
1727 | /* FIXME */ |
1728 | } |
1729 | |
1730 | void dlg_set_focus(union control *ctrl, void *dv) |
1731 | { |
1732 | /* FIXME */ |
1733 | } |
1734 | |
1735 | union control *dlg_last_focused(union control *ctrl, void *dv) |
1736 | { |
1737 | return NULL; /* FIXME */ |
1738 | } |
1739 | |
1740 | void dlg_beep(void *dv) |
1741 | { |
1742 | NSBeep(); |
1743 | } |
1744 | |
1745 | void dlg_error_msg(void *dv, char *msg) |
1746 | { |
1747 | /* FIXME */ |
1748 | } |
1749 | |
1750 | void dlg_end(void *dv, int value) |
1751 | { |
1752 | struct fe_dlg *d = (struct fe_dlg *)dv; |
1753 | [d->target performSelector:d->action |
1754 | withObject:[NSNumber numberWithInt:value]]; |
1755 | } |
1756 | |
1757 | void dlg_coloursel_start(union control *ctrl, void *dv, |
1758 | int r, int g, int b) |
1759 | { |
1760 | /* FIXME */ |
1761 | } |
1762 | |
1763 | int dlg_coloursel_results(union control *ctrl, void *dv, |
1764 | int *r, int *g, int *b) |
1765 | { |
1766 | return 0; /* FIXME */ |
1767 | } |
1768 | |
1769 | void dlg_refresh(union control *ctrl, void *dv) |
1770 | { |
1771 | struct fe_dlg *d = (struct fe_dlg *)dv; |
1772 | struct fe_ctrl *c; |
1773 | |
1774 | if (ctrl) { |
1775 | if (ctrl->generic.handler != NULL) |
1776 | ctrl->generic.handler(ctrl, d, d->data, EVENT_REFRESH); |
1777 | } else { |
1778 | int i; |
1779 | |
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); |
1785 | } |
1786 | } |
1787 | } |
1788 | |
1789 | void *dlg_get_privdata(union control *ctrl, void *dv) |
1790 | { |
1791 | struct fe_dlg *d = (struct fe_dlg *)dv; |
1792 | struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); |
1793 | return c->privdata; |
1794 | } |
1795 | |
1796 | void dlg_set_privdata(union control *ctrl, void *dv, void *ptr) |
1797 | { |
1798 | struct fe_dlg *d = (struct fe_dlg *)dv; |
1799 | struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); |
1800 | c->privdata = ptr; |
1801 | c->privdata_needs_free = FALSE; |
1802 | } |
1803 | |
1804 | void *dlg_alloc_privdata(union control *ctrl, void *dv, size_t size) |
1805 | { |
1806 | struct fe_dlg *d = (struct fe_dlg *)dv; |
1807 | struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl); |
1808 | /* |
1809 | * This is an internal allocation routine, so it's allowed to |
1810 | * use smalloc directly. |
1811 | */ |
1812 | c->privdata = smalloc(size); |
1813 | c->privdata_needs_free = TRUE; |
1814 | return c->privdata; |
1815 | } |