]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - dialog.c
The long-awaited config box revamp! I've taken the whole config box
[PuTTY.git] / dialog.c
1 /*
2  * dialog.c - a reasonably platform-independent mechanism for
3  * describing dialog boxes.
4  */
5
6 #include <assert.h>
7 #include <limits.h>
8 #include <stdarg.h>
9
10 #define DEFINE_INTORPTR_FNS
11
12 #include "putty.h"
13 #include "dialog.h"
14
15 int ctrl_path_elements(char *path)
16 {
17     int i = 1;
18     while (*path) {
19         if (*path == '/') i++;
20         path++;
21     }
22     return i;
23 }
24
25 /* Return the number of matching path elements at the starts of p1 and p2,
26  * or INT_MAX if the paths are identical. */
27 int ctrl_path_compare(char *p1, char *p2)
28 {
29     int i = 0;
30     while (*p1 || *p2) {
31         if ((*p1 == '/' || *p1 == '\0') &&
32             (*p2 == '/' || *p2 == '\0'))
33             i++;                       /* a whole element matches, ooh */
34         if (*p1 != *p2)
35             return i;                  /* mismatch */
36         p1++, p2++;
37     }
38     return INT_MAX;                    /* exact match */
39 }
40
41 struct controlbox *ctrl_new_box(void)
42 {
43     struct controlbox *ret = smalloc(sizeof(struct controlbox));
44
45     ret->nctrlsets = ret->ctrlsetsize = 0;
46     ret->ctrlsets = NULL;
47     ret->nfrees = ret->freesize = 0;
48     ret->frees = NULL;
49
50     return ret;
51 }
52
53 void ctrl_free_box(struct controlbox *b)
54 {
55     int i;
56
57     for (i = 0; i < b->nctrlsets; i++) {
58         ctrl_free_set(b->ctrlsets[i]);
59     }
60     for (i = 0; i < b->nfrees; i++)
61         sfree(b->frees[i]);
62     sfree(b->ctrlsets);
63     sfree(b->frees);
64     sfree(b);
65 }
66
67 void ctrl_free_set(struct controlset *s)
68 {
69     int i;
70
71     sfree(s->pathname);
72     sfree(s->boxname);
73     sfree(s->boxtitle);
74     for (i = 0; i < s->ncontrols; i++) {
75         ctrl_free(s->ctrls[i]);
76     }
77     sfree(s->ctrls);
78     sfree(s);
79 }
80
81 /*
82  * Find the index of first controlset in a controlbox for a given
83  * path. If that path doesn't exist, return the index where it
84  * should be inserted.
85  */
86 static int ctrl_find_set(struct controlbox *b, char *path, int start)
87 {
88     int i, last, thisone;
89
90     last = 0;
91     for (i = 0; i < b->nctrlsets; i++) {
92         thisone = ctrl_path_compare(path, b->ctrlsets[i]->pathname);
93         /*
94          * If `start' is true and there exists a controlset with
95          * exactly the path we've been given, we should return the
96          * index of the first such controlset we find. Otherwise,
97          * we should return the index of the first entry in which
98          * _fewer_ path elements match than they did last time.
99          */
100         if ((start && thisone == INT_MAX) || thisone < last)
101             return i;
102         last = thisone;
103     }
104     return b->nctrlsets;               /* insert at end */
105 }
106
107 /*
108  * Find the index of next controlset in a controlbox for a given
109  * path, or -1 if no such controlset exists. If -1 is passed as
110  * input, finds the first.
111  */
112 int ctrl_find_path(struct controlbox *b, char *path, int index)
113 {
114     if (index < 0)
115         index = ctrl_find_set(b, path, 1);
116     else
117         index++;
118
119     if (index < b->nctrlsets && !strcmp(path, b->ctrlsets[index]->pathname))
120         return index;
121     else
122         return -1;
123 }
124
125 /* Set up a panel title. */
126 struct controlset *ctrl_settitle(struct controlbox *b,
127                                  char *path, char *title)
128 {
129     
130     struct controlset *s = smalloc(sizeof(struct controlset));
131     int index = ctrl_find_set(b, path, 1);
132     s->pathname = dupstr(path);
133     s->boxname = NULL;
134     s->boxtitle = dupstr(title);
135     s->ncontrols = s->ctrlsize = 0;
136     s->ncolumns = 0;                   /* this is a title! */
137     s->ctrls = NULL;
138     if (b->nctrlsets >= b->ctrlsetsize) {
139         b->ctrlsetsize = b->nctrlsets + 32;
140         b->ctrlsets = srealloc(b->ctrlsets,
141                                b->ctrlsetsize*sizeof(*b->ctrlsets));
142     }
143     if (index < b->nctrlsets)
144         memmove(&b->ctrlsets[index+1], &b->ctrlsets[index],
145                 (b->nctrlsets-index) * sizeof(*b->ctrlsets));
146     b->ctrlsets[index] = s;
147     b->nctrlsets++;
148     return s;
149 }
150
151 /* Retrieve a pointer to a controlset, creating it if absent. */
152 struct controlset *ctrl_getset(struct controlbox *b,
153                                char *path, char *name, char *boxtitle)
154 {
155     struct controlset *s;
156     int index = ctrl_find_set(b, path, 1);
157     while (index < b->nctrlsets &&
158            !strcmp(b->ctrlsets[index]->pathname, path)) {
159         if (b->ctrlsets[index]->boxname &&
160             !strcmp(b->ctrlsets[index]->boxname, name))
161             return b->ctrlsets[index];
162         index++;
163     }
164     s = smalloc(sizeof(struct controlset));
165     s->pathname = dupstr(path);
166     s->boxname = dupstr(name);
167     s->boxtitle = boxtitle ? dupstr(boxtitle) : NULL;
168     s->ncolumns = 1;
169     s->ncontrols = s->ctrlsize = 0;
170     s->ctrls = NULL;
171     if (b->nctrlsets >= b->ctrlsetsize) {
172         b->ctrlsetsize = b->nctrlsets + 32;
173         b->ctrlsets = srealloc(b->ctrlsets,
174                                b->ctrlsetsize*sizeof(*b->ctrlsets));
175     }
176     if (index < b->nctrlsets)
177         memmove(&b->ctrlsets[index+1], &b->ctrlsets[index],
178                 (b->nctrlsets-index) * sizeof(*b->ctrlsets));
179     b->ctrlsets[index] = s;
180     b->nctrlsets++;
181     return s;
182 }
183
184 /* Allocate some private data in a controlbox. */
185 void *ctrl_alloc(struct controlbox *b, size_t size)
186 {
187     void *p;
188     p = smalloc(size);
189     if (b->nfrees >= b->freesize) {
190         b->freesize = b->nfrees + 32;
191         b->frees = srealloc(b->frees, b->freesize*sizeof(*b->frees));
192     }
193     b->frees[b->nfrees++] = p;
194     return p;
195 }
196
197 static union control *ctrl_new(struct controlset *s, int type,
198                                intorptr helpctx, handler_fn handler,
199                                intorptr context)
200 {
201     union control *c = smalloc(sizeof(union control));
202     if (s->ncontrols >= s->ctrlsize) {
203         s->ctrlsize = s->ncontrols + 32;
204         s->ctrls = srealloc(s->ctrls, s->ctrlsize * sizeof(*s->ctrls));
205     }
206     s->ctrls[s->ncontrols++] = c;
207     /*
208      * Fill in the standard fields.
209      */
210     c->generic.type = type;
211     c->generic.tabdelay = 0;
212     c->generic.column = COLUMN_FIELD(0, s->ncolumns);
213     c->generic.helpctx = helpctx;
214     c->generic.handler = handler;
215     c->generic.context = context;
216     c->generic.label = NULL;
217     return c;
218 }
219
220 /* `ncolumns' is followed by that many percentages, as integers. */
221 union control *ctrl_columns(struct controlset *s, int ncolumns, ...)
222 {
223     union control *c = ctrl_new(s, CTRL_COLUMNS, P(NULL), NULL, P(NULL));
224     assert(s->ncolumns == 1 || ncolumns == 1);
225     c->columns.ncols = ncolumns;
226     s->ncolumns = ncolumns;
227     if (ncolumns == 1) {
228         c->columns.percentages = NULL;
229     } else {
230         va_list ap;
231         int i;
232         c->columns.percentages = smalloc(ncolumns * sizeof(int));
233         va_start(ap, ncolumns);
234         for (i = 0; i < ncolumns; i++)
235             c->columns.percentages[i] = va_arg(ap, int);
236         va_end(ap);
237     }
238     return c;
239 }
240
241 union control *ctrl_editbox(struct controlset *s, char *label, char shortcut,
242                             int percentage,
243                             intorptr helpctx, handler_fn handler,
244                             intorptr context, intorptr context2)
245 {
246     union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);
247     c->editbox.label = label ? dupstr(label) : NULL;
248     c->editbox.shortcut = shortcut;
249     c->editbox.percentwidth = percentage;
250     c->editbox.password = 0;
251     c->editbox.has_list = 0;
252     c->editbox.context2 = context2;
253     return c;
254 }
255
256 union control *ctrl_combobox(struct controlset *s, char *label, char shortcut,
257                              int percentage,
258                              intorptr helpctx, handler_fn handler,
259                              intorptr context, intorptr context2)
260 {
261     union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);
262     c->editbox.label = label ? dupstr(label) : NULL;
263     c->editbox.shortcut = shortcut;
264     c->editbox.percentwidth = percentage;
265     c->editbox.password = 0;
266     c->editbox.has_list = 1;
267     c->editbox.context2 = context2;
268     return c;
269 }
270
271 /*
272  * `ncolumns' is followed by (alternately) radio button titles and
273  * intorptrs, until a NULL in place of a title string is seen. Each
274  * title is expected to be followed by a shortcut _iff_ `shortcut'
275  * is NO_SHORTCUT.
276  */
277 union control *ctrl_radiobuttons(struct controlset *s, char *label,
278                                  char shortcut, int ncolumns, intorptr helpctx,
279                                  handler_fn handler, intorptr context, ...)
280 {
281     va_list ap;
282     int i;
283     union control *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context);
284     c->radio.label = label ? dupstr(label) : NULL;
285     c->radio.shortcut = shortcut;
286     c->radio.ncolumns = ncolumns;
287     /*
288      * Initial pass along variable argument list to count the
289      * buttons.
290      */
291     va_start(ap, context);
292     i = 0;
293     while (va_arg(ap, char *) != NULL) {
294         i++;
295         if (c->radio.shortcut == NO_SHORTCUT)
296             va_arg(ap, int);           /* char promotes to int in arg lists */
297         va_arg(ap, intorptr);
298     }
299     va_end(ap);
300     c->radio.nbuttons = i;
301     if (c->radio.shortcut == NO_SHORTCUT)
302         c->radio.shortcuts = smalloc(c->radio.nbuttons * sizeof(char));
303     else
304         c->radio.shortcuts = NULL;
305     c->radio.buttons = smalloc(c->radio.nbuttons * sizeof(char *));
306     c->radio.buttondata = smalloc(c->radio.nbuttons * sizeof(intorptr));
307     /*
308      * Second pass along variable argument list to actually fill in
309      * the structure.
310      */
311     va_start(ap, context);
312     for (i = 0; i < c->radio.nbuttons; i++) {
313         c->radio.buttons[i] = dupstr(va_arg(ap, char *));
314         if (c->radio.shortcut == NO_SHORTCUT)
315             c->radio.shortcuts[i] = va_arg(ap, int);
316                                        /* char promotes to int in arg lists */
317         c->radio.buttondata[i] = va_arg(ap, intorptr);
318     }
319     va_end(ap);
320     return c;
321 }
322
323 union control *ctrl_pushbutton(struct controlset *s,char *label,char shortcut,
324                                intorptr helpctx, handler_fn handler,
325                                intorptr context)
326 {
327     union control *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context);
328     c->button.label = label ? dupstr(label) : NULL;
329     c->button.shortcut = shortcut;
330     c->button.isdefault = 0;
331     return c;
332 }
333
334 union control *ctrl_listbox(struct controlset *s,char *label,char shortcut,
335                             intorptr helpctx, handler_fn handler,
336                             intorptr context)
337 {
338     union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
339     c->listbox.label = label ? dupstr(label) : NULL;
340     c->listbox.shortcut = shortcut;
341     c->listbox.height = 5;             /* *shrug* a plausible default */
342     c->listbox.draglist = 0;
343     c->listbox.multisel = 0;
344     c->listbox.percentwidth = 100;
345     c->listbox.ncols = 0;
346     c->listbox.percentages = NULL;
347     return c;
348 }
349
350 union control *ctrl_droplist(struct controlset *s, char *label, char shortcut,
351                              int percentage, intorptr helpctx,
352                              handler_fn handler, intorptr context)
353 {
354     union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
355     c->listbox.label = label ? dupstr(label) : NULL;
356     c->listbox.shortcut = shortcut;
357     c->listbox.height = 0;             /* means it's a drop-down list */
358     c->listbox.draglist = 0;
359     c->listbox.multisel = 0;
360     c->listbox.percentwidth = percentage;
361     return c;
362 }
363
364 union control *ctrl_draglist(struct controlset *s,char *label,char shortcut,
365                              intorptr helpctx, handler_fn handler,
366                              intorptr context)
367 {
368     union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
369     c->listbox.label = label ? dupstr(label) : NULL;
370     c->listbox.shortcut = shortcut;
371     c->listbox.height = 5;             /* *shrug* a plausible default */
372     c->listbox.draglist = 1;
373     c->listbox.multisel = 0;
374     c->listbox.percentwidth = 100;
375     return c;
376 }
377
378 union control *ctrl_filesel(struct controlset *s,char *label,char shortcut,
379                             char const *filter, int write, char *title,
380                             intorptr helpctx, handler_fn handler,
381                             intorptr context)
382 {
383     union control *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context);
384     c->fileselect.label = label ? dupstr(label) : NULL;
385     c->fileselect.shortcut = shortcut;
386     c->fileselect.filter = filter;
387     c->fileselect.for_writing = write;
388     c->fileselect.title = dupstr(title);
389     return c;
390 }
391
392 union control *ctrl_fontsel(struct controlset *s,char *label,char shortcut,
393                             intorptr helpctx, handler_fn handler,
394                             intorptr context)
395 {
396     union control *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context);
397     c->fontselect.label = label ? dupstr(label) : NULL;
398     c->fontselect.shortcut = shortcut;
399     return c;
400 }
401
402 union control *ctrl_tabdelay(struct controlset *s, union control *ctrl)
403 {
404     union control *c = ctrl_new(s, CTRL_TABDELAY, P(NULL), NULL, P(NULL));
405     c->tabdelay.ctrl = ctrl;
406     return c;
407 }
408
409 union control *ctrl_text(struct controlset *s, char *text, intorptr helpctx)
410 {
411     union control *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL));
412     c->text.label = dupstr(text);
413     return c;
414 }
415
416 union control *ctrl_checkbox(struct controlset *s, char *label, char shortcut,
417                              intorptr helpctx, handler_fn handler,
418                              intorptr context)
419 {
420     union control *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context);
421     c->checkbox.label = label ? dupstr(label) : NULL;
422     c->checkbox.shortcut = shortcut;
423     return c;
424 }
425
426 void ctrl_free(union control *ctrl)
427 {
428     int i;
429
430     sfree(ctrl->generic.label);
431     switch (ctrl->generic.type) {
432       case CTRL_RADIO:
433         for (i = 0; i < ctrl->radio.nbuttons; i++)
434             sfree(ctrl->radio.buttons[i]);
435         sfree(ctrl->radio.buttons);
436         sfree(ctrl->radio.shortcuts);
437         sfree(ctrl->radio.buttondata);
438         break;
439       case CTRL_COLUMNS:
440         sfree(ctrl->columns.percentages);
441         break;
442       case CTRL_LISTBOX:
443         sfree(ctrl->listbox.percentages);
444         break;
445       case CTRL_FILESELECT:
446         sfree(ctrl->fileselect.title);
447         break;
448     }
449     sfree(ctrl);
450 }
451
452 void dlg_stdradiobutton_handler(union control *ctrl, void *dlg,
453                                 void *data, int event)
454 {
455     int button;
456     /*
457      * For a standard radio button set, the context parameter gives
458      * offsetof(targetfield, Config), and the extra data per button
459      * gives the value the target field should take if that button
460      * is the one selected.
461      */
462     if (event == EVENT_REFRESH) {
463         for (button = 0; button < ctrl->radio.nbuttons; button++)
464             if (*(int *)ATOFFSET(data, ctrl->radio.context.i) ==
465                 ctrl->radio.buttondata[button].i)
466                 break;
467         /* We expected that `break' to happen, in all circumstances. */
468         assert(button < ctrl->radio.nbuttons);
469         dlg_radiobutton_set(ctrl, dlg, button);
470     } else if (event == EVENT_VALCHANGE) {
471         button = dlg_radiobutton_get(ctrl, dlg);
472         assert(button >= 0 && button < ctrl->radio.nbuttons);
473         *(int *)ATOFFSET(data, ctrl->radio.context.i) =
474             ctrl->radio.buttondata[button].i;
475     }
476 }
477
478 void dlg_stdcheckbox_handler(union control *ctrl, void *dlg,
479                                 void *data, int event)
480 {
481     int offset, invert;
482
483     /*
484      * For a standard checkbox, the context parameter gives
485      * offsetof(targetfield, Config), optionally ORed with
486      * CHECKBOX_INVERT.
487      */
488     offset = ctrl->checkbox.context.i;
489     if (offset & CHECKBOX_INVERT) {
490         offset &= ~CHECKBOX_INVERT;
491         invert = 1;
492     } else
493         invert = 0;
494
495     /*
496      * C lacks a logical XOR, so the following code uses the idiom
497      * (!a ^ !b) to obtain the logical XOR of a and b. (That is, 1
498      * iff exactly one of a and b is nonzero, otherwise 0.)
499      */
500
501     if (event == EVENT_REFRESH) {
502         dlg_checkbox_set(ctrl,dlg, (!*(int *)ATOFFSET(data,offset) ^ !invert));
503     } else if (event == EVENT_VALCHANGE) {
504         *(int *)ATOFFSET(data, offset) = !dlg_checkbox_get(ctrl,dlg) ^ !invert;
505     }
506 }
507
508 void dlg_stdeditbox_handler(union control *ctrl, void *dlg,
509                             void *data, int event)
510 {
511     /*
512      * The standard edit-box handler expects the main `context'
513      * field to contain the `offsetof' a field in the structure
514      * pointed to by `data'. The secondary `context2' field
515      * indicates the type of this field:
516      *
517      *  - if context2 > 0, the field is a char array and context2
518      *    gives its size.
519      *  - if context2 == -1, the field is an int and the edit box
520      *    is numeric.
521      *  - if context2 < -1, the field is an int and the edit box is
522      *    _floating_, and (-context2) gives the scale. (E.g. if
523      *    context2 == -1000, then typing 1.2 into the box will set
524      *    the field to 1200.)
525      */
526     int offset = ctrl->editbox.context.i;
527     int length = ctrl->editbox.context2.i;
528
529     if (length > 0) {
530         char *field = (char *)ATOFFSET(data, offset);
531         if (event == EVENT_REFRESH) {
532             dlg_editbox_set(ctrl, dlg, field);
533         } else if (event == EVENT_VALCHANGE) {
534             dlg_editbox_get(ctrl, dlg, field, length);
535         }
536     } else if (length < 0) {
537         int *field = (int *)ATOFFSET(data, offset);
538         char data[80];
539         if (event == EVENT_REFRESH) {
540             if (length == -1)
541                 sprintf(data, "%d", *field);
542             else
543                 sprintf(data, "%g", (double)*field / (double)(-length));
544             dlg_editbox_set(ctrl, dlg, data);
545         } else if (event == EVENT_VALCHANGE) {
546             dlg_editbox_get(ctrl, dlg, data, lenof(data));
547             if (length == -1)
548                 *field = atoi(data);
549             else
550                 *field = (int)((-length) * atof(data));
551         }
552     }
553 }
554
555 void dlg_stdfilesel_handler(union control *ctrl, void *dlg,
556                             void *data, int event)
557 {
558     /*
559      * The standard file-selector handler expects the `context'
560      * field to contain the `offsetof' a Filename field in the
561      * structure pointed to by `data'.
562      */
563     int offset = ctrl->fileselect.context.i;
564
565     if (event == EVENT_REFRESH) {
566         dlg_filesel_set(ctrl, dlg, *(Filename *)ATOFFSET(data, offset));
567     } else if (event == EVENT_VALCHANGE) {
568         dlg_filesel_get(ctrl, dlg, (Filename *)ATOFFSET(data, offset));
569     }
570 }
571
572 void dlg_stdfontsel_handler(union control *ctrl, void *dlg,
573                             void *data, int event)
574 {
575     /*
576      * The standard file-selector handler expects the `context'
577      * field to contain the `offsetof' a FontSpec field in the
578      * structure pointed to by `data'.
579      */
580     int offset = ctrl->fontselect.context.i;
581
582     if (event == EVENT_REFRESH) {
583         dlg_fontsel_set(ctrl, dlg, *(FontSpec *)ATOFFSET(data, offset));
584     } else if (event == EVENT_VALCHANGE) {
585         dlg_fontsel_get(ctrl, dlg, (FontSpec *)ATOFFSET(data, offset));
586     }
587 }