Gah, another duplicate keyword. I must stop creating new docs
[u/mdw/putty] / dialog.c
CommitLineData
fe8abbf4 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>
d1582b2e 9#include <stdlib.h>
fe8abbf4 10
11#define DEFINE_INTORPTR_FNS
12
13#include "putty.h"
14#include "dialog.h"
15
16int 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. */
28int 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
42struct controlbox *ctrl_new_box(void)
43{
44 struct controlbox *ret = smalloc(sizeof(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
54void 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
68void 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 */
87static 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 */
113int 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. */
127struct controlset *ctrl_settitle(struct controlbox *b,
128 char *path, char *title)
129{
130
131 struct controlset *s = smalloc(sizeof(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 = srealloc(b->ctrlsets,
142 b->ctrlsetsize*sizeof(*b->ctrlsets));
143 }
144 if (index < b->nctrlsets)
145 memmove(&b->ctrlsets[index+1], &b->ctrlsets[index],
146 (b->nctrlsets-index) * sizeof(*b->ctrlsets));
147 b->ctrlsets[index] = s;
148 b->nctrlsets++;
149 return s;
150}
151
152/* Retrieve a pointer to a controlset, creating it if absent. */
153struct controlset *ctrl_getset(struct controlbox *b,
154 char *path, char *name, char *boxtitle)
155{
156 struct controlset *s;
157 int index = ctrl_find_set(b, path, 1);
158 while (index < b->nctrlsets &&
159 !strcmp(b->ctrlsets[index]->pathname, path)) {
160 if (b->ctrlsets[index]->boxname &&
161 !strcmp(b->ctrlsets[index]->boxname, name))
162 return b->ctrlsets[index];
163 index++;
164 }
165 s = smalloc(sizeof(struct controlset));
166 s->pathname = dupstr(path);
167 s->boxname = dupstr(name);
168 s->boxtitle = boxtitle ? dupstr(boxtitle) : NULL;
169 s->ncolumns = 1;
170 s->ncontrols = s->ctrlsize = 0;
171 s->ctrls = NULL;
172 if (b->nctrlsets >= b->ctrlsetsize) {
173 b->ctrlsetsize = b->nctrlsets + 32;
174 b->ctrlsets = srealloc(b->ctrlsets,
175 b->ctrlsetsize*sizeof(*b->ctrlsets));
176 }
177 if (index < b->nctrlsets)
178 memmove(&b->ctrlsets[index+1], &b->ctrlsets[index],
179 (b->nctrlsets-index) * sizeof(*b->ctrlsets));
180 b->ctrlsets[index] = s;
181 b->nctrlsets++;
182 return s;
183}
184
185/* Allocate some private data in a controlbox. */
186void *ctrl_alloc(struct controlbox *b, size_t size)
187{
188 void *p;
189 p = smalloc(size);
190 if (b->nfrees >= b->freesize) {
191 b->freesize = b->nfrees + 32;
192 b->frees = srealloc(b->frees, b->freesize*sizeof(*b->frees));
193 }
194 b->frees[b->nfrees++] = p;
195 return p;
196}
197
198static union control *ctrl_new(struct controlset *s, int type,
199 intorptr helpctx, handler_fn handler,
200 intorptr context)
201{
202 union control *c = smalloc(sizeof(union control));
203 if (s->ncontrols >= s->ctrlsize) {
204 s->ctrlsize = s->ncontrols + 32;
205 s->ctrls = srealloc(s->ctrls, s->ctrlsize * sizeof(*s->ctrls));
206 }
207 s->ctrls[s->ncontrols++] = c;
208 /*
209 * Fill in the standard fields.
210 */
211 c->generic.type = type;
212 c->generic.tabdelay = 0;
213 c->generic.column = COLUMN_FIELD(0, s->ncolumns);
214 c->generic.helpctx = helpctx;
215 c->generic.handler = handler;
216 c->generic.context = context;
217 c->generic.label = NULL;
218 return c;
219}
220
221/* `ncolumns' is followed by that many percentages, as integers. */
222union control *ctrl_columns(struct controlset *s, int ncolumns, ...)
223{
224 union control *c = ctrl_new(s, CTRL_COLUMNS, P(NULL), NULL, P(NULL));
225 assert(s->ncolumns == 1 || ncolumns == 1);
226 c->columns.ncols = ncolumns;
227 s->ncolumns = ncolumns;
228 if (ncolumns == 1) {
229 c->columns.percentages = NULL;
230 } else {
231 va_list ap;
232 int i;
233 c->columns.percentages = smalloc(ncolumns * sizeof(int));
234 va_start(ap, ncolumns);
235 for (i = 0; i < ncolumns; i++)
236 c->columns.percentages[i] = va_arg(ap, int);
237 va_end(ap);
238 }
239 return c;
240}
241
242union control *ctrl_editbox(struct controlset *s, char *label, char shortcut,
243 int percentage,
244 intorptr helpctx, handler_fn handler,
245 intorptr context, intorptr context2)
246{
247 union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);
248 c->editbox.label = label ? dupstr(label) : NULL;
249 c->editbox.shortcut = shortcut;
250 c->editbox.percentwidth = percentage;
251 c->editbox.password = 0;
252 c->editbox.has_list = 0;
253 c->editbox.context2 = context2;
254 return c;
255}
256
257union control *ctrl_combobox(struct controlset *s, char *label, char shortcut,
258 int percentage,
259 intorptr helpctx, handler_fn handler,
260 intorptr context, intorptr context2)
261{
262 union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);
263 c->editbox.label = label ? dupstr(label) : NULL;
264 c->editbox.shortcut = shortcut;
265 c->editbox.percentwidth = percentage;
266 c->editbox.password = 0;
267 c->editbox.has_list = 1;
268 c->editbox.context2 = context2;
269 return c;
270}
271
272/*
273 * `ncolumns' is followed by (alternately) radio button titles and
274 * intorptrs, until a NULL in place of a title string is seen. Each
275 * title is expected to be followed by a shortcut _iff_ `shortcut'
276 * is NO_SHORTCUT.
277 */
278union control *ctrl_radiobuttons(struct controlset *s, char *label,
279 char shortcut, int ncolumns, intorptr helpctx,
280 handler_fn handler, intorptr context, ...)
281{
282 va_list ap;
283 int i;
284 union control *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context);
285 c->radio.label = label ? dupstr(label) : NULL;
286 c->radio.shortcut = shortcut;
287 c->radio.ncolumns = ncolumns;
288 /*
289 * Initial pass along variable argument list to count the
290 * buttons.
291 */
292 va_start(ap, context);
293 i = 0;
294 while (va_arg(ap, char *) != NULL) {
295 i++;
296 if (c->radio.shortcut == NO_SHORTCUT)
d1582b2e 297 (void)va_arg(ap, int); /* char promotes to int in arg lists */
298 (void)va_arg(ap, intorptr);
fe8abbf4 299 }
300 va_end(ap);
301 c->radio.nbuttons = i;
302 if (c->radio.shortcut == NO_SHORTCUT)
303 c->radio.shortcuts = smalloc(c->radio.nbuttons * sizeof(char));
304 else
305 c->radio.shortcuts = NULL;
306 c->radio.buttons = smalloc(c->radio.nbuttons * sizeof(char *));
307 c->radio.buttondata = smalloc(c->radio.nbuttons * sizeof(intorptr));
308 /*
309 * Second pass along variable argument list to actually fill in
310 * the structure.
311 */
312 va_start(ap, context);
313 for (i = 0; i < c->radio.nbuttons; i++) {
314 c->radio.buttons[i] = dupstr(va_arg(ap, char *));
315 if (c->radio.shortcut == NO_SHORTCUT)
316 c->radio.shortcuts[i] = va_arg(ap, int);
317 /* char promotes to int in arg lists */
318 c->radio.buttondata[i] = va_arg(ap, intorptr);
319 }
320 va_end(ap);
321 return c;
322}
323
324union control *ctrl_pushbutton(struct controlset *s,char *label,char shortcut,
325 intorptr helpctx, handler_fn handler,
326 intorptr context)
327{
328 union control *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context);
329 c->button.label = label ? dupstr(label) : NULL;
330 c->button.shortcut = shortcut;
331 c->button.isdefault = 0;
332 return c;
333}
334
335union control *ctrl_listbox(struct controlset *s,char *label,char shortcut,
336 intorptr helpctx, handler_fn handler,
337 intorptr context)
338{
339 union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
340 c->listbox.label = label ? dupstr(label) : NULL;
341 c->listbox.shortcut = shortcut;
342 c->listbox.height = 5; /* *shrug* a plausible default */
343 c->listbox.draglist = 0;
344 c->listbox.multisel = 0;
345 c->listbox.percentwidth = 100;
346 c->listbox.ncols = 0;
347 c->listbox.percentages = NULL;
348 return c;
349}
350
351union control *ctrl_droplist(struct controlset *s, char *label, char shortcut,
352 int percentage, intorptr helpctx,
353 handler_fn handler, intorptr context)
354{
355 union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
356 c->listbox.label = label ? dupstr(label) : NULL;
357 c->listbox.shortcut = shortcut;
358 c->listbox.height = 0; /* means it's a drop-down list */
359 c->listbox.draglist = 0;
360 c->listbox.multisel = 0;
361 c->listbox.percentwidth = percentage;
362 return c;
363}
364
365union control *ctrl_draglist(struct controlset *s,char *label,char shortcut,
366 intorptr helpctx, handler_fn handler,
367 intorptr context)
368{
369 union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
370 c->listbox.label = label ? dupstr(label) : NULL;
371 c->listbox.shortcut = shortcut;
372 c->listbox.height = 5; /* *shrug* a plausible default */
373 c->listbox.draglist = 1;
374 c->listbox.multisel = 0;
375 c->listbox.percentwidth = 100;
376 return c;
377}
378
379union control *ctrl_filesel(struct controlset *s,char *label,char shortcut,
380 char const *filter, int write, char *title,
381 intorptr helpctx, handler_fn handler,
382 intorptr context)
383{
384 union control *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context);
385 c->fileselect.label = label ? dupstr(label) : NULL;
386 c->fileselect.shortcut = shortcut;
387 c->fileselect.filter = filter;
388 c->fileselect.for_writing = write;
389 c->fileselect.title = dupstr(title);
390 return c;
391}
392
393union control *ctrl_fontsel(struct controlset *s,char *label,char shortcut,
394 intorptr helpctx, handler_fn handler,
395 intorptr context)
396{
397 union control *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context);
398 c->fontselect.label = label ? dupstr(label) : NULL;
399 c->fontselect.shortcut = shortcut;
400 return c;
401}
402
403union control *ctrl_tabdelay(struct controlset *s, union control *ctrl)
404{
405 union control *c = ctrl_new(s, CTRL_TABDELAY, P(NULL), NULL, P(NULL));
406 c->tabdelay.ctrl = ctrl;
407 return c;
408}
409
410union control *ctrl_text(struct controlset *s, char *text, intorptr helpctx)
411{
412 union control *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL));
413 c->text.label = dupstr(text);
414 return c;
415}
416
417union control *ctrl_checkbox(struct controlset *s, char *label, char shortcut,
418 intorptr helpctx, handler_fn handler,
419 intorptr context)
420{
421 union control *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context);
422 c->checkbox.label = label ? dupstr(label) : NULL;
423 c->checkbox.shortcut = shortcut;
424 return c;
425}
426
427void ctrl_free(union control *ctrl)
428{
429 int i;
430
431 sfree(ctrl->generic.label);
432 switch (ctrl->generic.type) {
433 case CTRL_RADIO:
434 for (i = 0; i < ctrl->radio.nbuttons; i++)
435 sfree(ctrl->radio.buttons[i]);
436 sfree(ctrl->radio.buttons);
437 sfree(ctrl->radio.shortcuts);
438 sfree(ctrl->radio.buttondata);
439 break;
440 case CTRL_COLUMNS:
441 sfree(ctrl->columns.percentages);
442 break;
443 case CTRL_LISTBOX:
444 sfree(ctrl->listbox.percentages);
445 break;
446 case CTRL_FILESELECT:
447 sfree(ctrl->fileselect.title);
448 break;
449 }
450 sfree(ctrl);
451}
452
453void dlg_stdradiobutton_handler(union control *ctrl, void *dlg,
454 void *data, int event)
455{
456 int button;
457 /*
458 * For a standard radio button set, the context parameter gives
459 * offsetof(targetfield, Config), and the extra data per button
460 * gives the value the target field should take if that button
461 * is the one selected.
462 */
463 if (event == EVENT_REFRESH) {
464 for (button = 0; button < ctrl->radio.nbuttons; button++)
465 if (*(int *)ATOFFSET(data, ctrl->radio.context.i) ==
466 ctrl->radio.buttondata[button].i)
467 break;
468 /* We expected that `break' to happen, in all circumstances. */
469 assert(button < ctrl->radio.nbuttons);
470 dlg_radiobutton_set(ctrl, dlg, button);
471 } else if (event == EVENT_VALCHANGE) {
472 button = dlg_radiobutton_get(ctrl, dlg);
473 assert(button >= 0 && button < ctrl->radio.nbuttons);
474 *(int *)ATOFFSET(data, ctrl->radio.context.i) =
475 ctrl->radio.buttondata[button].i;
476 }
477}
478
479void dlg_stdcheckbox_handler(union control *ctrl, void *dlg,
480 void *data, int event)
481{
482 int offset, invert;
483
484 /*
485 * For a standard checkbox, the context parameter gives
486 * offsetof(targetfield, Config), optionally ORed with
487 * CHECKBOX_INVERT.
488 */
489 offset = ctrl->checkbox.context.i;
490 if (offset & CHECKBOX_INVERT) {
491 offset &= ~CHECKBOX_INVERT;
492 invert = 1;
493 } else
494 invert = 0;
495
496 /*
497 * C lacks a logical XOR, so the following code uses the idiom
498 * (!a ^ !b) to obtain the logical XOR of a and b. (That is, 1
499 * iff exactly one of a and b is nonzero, otherwise 0.)
500 */
501
502 if (event == EVENT_REFRESH) {
503 dlg_checkbox_set(ctrl,dlg, (!*(int *)ATOFFSET(data,offset) ^ !invert));
504 } else if (event == EVENT_VALCHANGE) {
505 *(int *)ATOFFSET(data, offset) = !dlg_checkbox_get(ctrl,dlg) ^ !invert;
506 }
507}
508
509void dlg_stdeditbox_handler(union control *ctrl, void *dlg,
510 void *data, int event)
511{
512 /*
513 * The standard edit-box handler expects the main `context'
514 * field to contain the `offsetof' a field in the structure
515 * pointed to by `data'. The secondary `context2' field
516 * indicates the type of this field:
517 *
518 * - if context2 > 0, the field is a char array and context2
519 * gives its size.
520 * - if context2 == -1, the field is an int and the edit box
521 * is numeric.
522 * - if context2 < -1, the field is an int and the edit box is
523 * _floating_, and (-context2) gives the scale. (E.g. if
524 * context2 == -1000, then typing 1.2 into the box will set
525 * the field to 1200.)
526 */
527 int offset = ctrl->editbox.context.i;
528 int length = ctrl->editbox.context2.i;
529
530 if (length > 0) {
531 char *field = (char *)ATOFFSET(data, offset);
532 if (event == EVENT_REFRESH) {
533 dlg_editbox_set(ctrl, dlg, field);
534 } else if (event == EVENT_VALCHANGE) {
535 dlg_editbox_get(ctrl, dlg, field, length);
536 }
537 } else if (length < 0) {
538 int *field = (int *)ATOFFSET(data, offset);
539 char data[80];
540 if (event == EVENT_REFRESH) {
541 if (length == -1)
542 sprintf(data, "%d", *field);
543 else
544 sprintf(data, "%g", (double)*field / (double)(-length));
545 dlg_editbox_set(ctrl, dlg, data);
546 } else if (event == EVENT_VALCHANGE) {
547 dlg_editbox_get(ctrl, dlg, data, lenof(data));
548 if (length == -1)
549 *field = atoi(data);
550 else
551 *field = (int)((-length) * atof(data));
552 }
553 }
554}
555
556void dlg_stdfilesel_handler(union control *ctrl, void *dlg,
557 void *data, int event)
558{
559 /*
560 * The standard file-selector handler expects the `context'
561 * field to contain the `offsetof' a Filename field in the
562 * structure pointed to by `data'.
563 */
564 int offset = ctrl->fileselect.context.i;
565
566 if (event == EVENT_REFRESH) {
567 dlg_filesel_set(ctrl, dlg, *(Filename *)ATOFFSET(data, offset));
568 } else if (event == EVENT_VALCHANGE) {
569 dlg_filesel_get(ctrl, dlg, (Filename *)ATOFFSET(data, offset));
570 }
571}
572
573void dlg_stdfontsel_handler(union control *ctrl, void *dlg,
574 void *data, int event)
575{
576 /*
577 * The standard file-selector handler expects the `context'
578 * field to contain the `offsetof' a FontSpec field in the
579 * structure pointed to by `data'.
580 */
581 int offset = ctrl->fontselect.context.i;
582
583 if (event == EVENT_REFRESH) {
584 dlg_fontsel_set(ctrl, dlg, *(FontSpec *)ATOFFSET(data, offset));
585 } else if (event == EVENT_VALCHANGE) {
586 dlg_fontsel_get(ctrl, dlg, (FontSpec *)ATOFFSET(data, offset));
587 }
588}