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