2 * dialog.c - a reasonably platform-independent mechanism for
\r
3 * describing dialog boxes.
\r
11 #define DEFINE_INTORPTR_FNS
\r
16 int ctrl_path_elements(char *path)
\r
20 if (*path == '/') i++;
\r
26 /* Return the number of matching path elements at the starts of p1 and p2,
\r
27 * or INT_MAX if the paths are identical. */
\r
28 int ctrl_path_compare(char *p1, char *p2)
\r
31 while (*p1 || *p2) {
\r
32 if ((*p1 == '/' || *p1 == '\0') &&
\r
33 (*p2 == '/' || *p2 == '\0'))
\r
34 i++; /* a whole element matches, ooh */
\r
36 return i; /* mismatch */
\r
39 return INT_MAX; /* exact match */
\r
42 struct controlbox *ctrl_new_box(void)
\r
44 struct controlbox *ret = snew(struct controlbox);
\r
46 ret->nctrlsets = ret->ctrlsetsize = 0;
\r
47 ret->ctrlsets = NULL;
\r
48 ret->nfrees = ret->freesize = 0;
\r
54 void ctrl_free_box(struct controlbox *b)
\r
58 for (i = 0; i < b->nctrlsets; i++) {
\r
59 ctrl_free_set(b->ctrlsets[i]);
\r
61 for (i = 0; i < b->nfrees; i++)
\r
68 void ctrl_free_set(struct controlset *s)
\r
75 for (i = 0; i < s->ncontrols; i++) {
\r
76 ctrl_free(s->ctrls[i]);
\r
83 * Find the index of first controlset in a controlbox for a given
\r
84 * path. If that path doesn't exist, return the index where it
\r
85 * should be inserted.
\r
87 static int ctrl_find_set(struct controlbox *b, char *path, int start)
\r
89 int i, last, thisone;
\r
92 for (i = 0; i < b->nctrlsets; i++) {
\r
93 thisone = ctrl_path_compare(path, b->ctrlsets[i]->pathname);
\r
95 * If `start' is true and there exists a controlset with
\r
96 * exactly the path we've been given, we should return the
\r
97 * index of the first such controlset we find. Otherwise,
\r
98 * we should return the index of the first entry in which
\r
99 * _fewer_ path elements match than they did last time.
\r
101 if ((start && thisone == INT_MAX) || thisone < last)
\r
105 return b->nctrlsets; /* insert at end */
\r
109 * Find the index of next controlset in a controlbox for a given
\r
110 * path, or -1 if no such controlset exists. If -1 is passed as
\r
111 * input, finds the first.
\r
113 int ctrl_find_path(struct controlbox *b, char *path, int index)
\r
116 index = ctrl_find_set(b, path, 1);
\r
120 if (index < b->nctrlsets && !strcmp(path, b->ctrlsets[index]->pathname))
\r
126 /* Set up a panel title. */
\r
127 struct controlset *ctrl_settitle(struct controlbox *b,
\r
128 char *path, char *title)
\r
131 struct controlset *s = snew(struct controlset);
\r
132 int index = ctrl_find_set(b, path, 1);
\r
133 s->pathname = dupstr(path);
\r
135 s->boxtitle = dupstr(title);
\r
136 s->ncontrols = s->ctrlsize = 0;
\r
137 s->ncolumns = 0; /* this is a title! */
\r
139 if (b->nctrlsets >= b->ctrlsetsize) {
\r
140 b->ctrlsetsize = b->nctrlsets + 32;
\r
141 b->ctrlsets = sresize(b->ctrlsets, b->ctrlsetsize,struct controlset *);
\r
143 if (index < b->nctrlsets)
\r
144 memmove(&b->ctrlsets[index+1], &b->ctrlsets[index],
\r
145 (b->nctrlsets-index) * sizeof(*b->ctrlsets));
\r
146 b->ctrlsets[index] = s;
\r
151 /* Retrieve a pointer to a controlset, creating it if absent. */
\r
152 struct controlset *ctrl_getset(struct controlbox *b,
\r
153 char *path, char *name, char *boxtitle)
\r
155 struct controlset *s;
\r
156 int index = ctrl_find_set(b, path, 1);
\r
157 while (index < b->nctrlsets &&
\r
158 !strcmp(b->ctrlsets[index]->pathname, path)) {
\r
159 if (b->ctrlsets[index]->boxname &&
\r
160 !strcmp(b->ctrlsets[index]->boxname, name))
\r
161 return b->ctrlsets[index];
\r
164 s = snew(struct controlset);
\r
165 s->pathname = dupstr(path);
\r
166 s->boxname = dupstr(name);
\r
167 s->boxtitle = boxtitle ? dupstr(boxtitle) : NULL;
\r
169 s->ncontrols = s->ctrlsize = 0;
\r
171 if (b->nctrlsets >= b->ctrlsetsize) {
\r
172 b->ctrlsetsize = b->nctrlsets + 32;
\r
173 b->ctrlsets = sresize(b->ctrlsets, b->ctrlsetsize,struct controlset *);
\r
175 if (index < b->nctrlsets)
\r
176 memmove(&b->ctrlsets[index+1], &b->ctrlsets[index],
\r
177 (b->nctrlsets-index) * sizeof(*b->ctrlsets));
\r
178 b->ctrlsets[index] = s;
\r
183 /* Allocate some private data in a controlbox. */
\r
184 void *ctrl_alloc(struct controlbox *b, size_t size)
\r
188 * This is an internal allocation routine, so it's allowed to
\r
189 * use smalloc directly.
\r
192 if (b->nfrees >= b->freesize) {
\r
193 b->freesize = b->nfrees + 32;
\r
194 b->frees = sresize(b->frees, b->freesize, void *);
\r
196 b->frees[b->nfrees++] = p;
\r
200 static union control *ctrl_new(struct controlset *s, int type,
\r
201 intorptr helpctx, handler_fn handler,
\r
204 union control *c = snew(union control);
\r
205 if (s->ncontrols >= s->ctrlsize) {
\r
206 s->ctrlsize = s->ncontrols + 32;
\r
207 s->ctrls = sresize(s->ctrls, s->ctrlsize, union control *);
\r
209 s->ctrls[s->ncontrols++] = c;
\r
211 * Fill in the standard fields.
\r
213 c->generic.type = type;
\r
214 c->generic.tabdelay = 0;
\r
215 c->generic.column = COLUMN_FIELD(0, s->ncolumns);
\r
216 c->generic.helpctx = helpctx;
\r
217 c->generic.handler = handler;
\r
218 c->generic.context = context;
\r
219 c->generic.label = NULL;
\r
223 /* `ncolumns' is followed by that many percentages, as integers. */
\r
224 union control *ctrl_columns(struct controlset *s, int ncolumns, ...)
\r
226 union control *c = ctrl_new(s, CTRL_COLUMNS, P(NULL), NULL, P(NULL));
\r
227 assert(s->ncolumns == 1 || ncolumns == 1);
\r
228 c->columns.ncols = ncolumns;
\r
229 s->ncolumns = ncolumns;
\r
230 if (ncolumns == 1) {
\r
231 c->columns.percentages = NULL;
\r
235 c->columns.percentages = snewn(ncolumns, int);
\r
236 va_start(ap, ncolumns);
\r
237 for (i = 0; i < ncolumns; i++)
\r
238 c->columns.percentages[i] = va_arg(ap, int);
\r
244 union control *ctrl_editbox(struct controlset *s, char *label, char shortcut,
\r
246 intorptr helpctx, handler_fn handler,
\r
247 intorptr context, intorptr context2)
\r
249 union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);
\r
250 c->editbox.label = label ? dupstr(label) : NULL;
\r
251 c->editbox.shortcut = shortcut;
\r
252 c->editbox.percentwidth = percentage;
\r
253 c->editbox.password = 0;
\r
254 c->editbox.has_list = 0;
\r
255 c->editbox.context2 = context2;
\r
259 union control *ctrl_combobox(struct controlset *s, char *label, char shortcut,
\r
261 intorptr helpctx, handler_fn handler,
\r
262 intorptr context, intorptr context2)
\r
264 union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);
\r
265 c->editbox.label = label ? dupstr(label) : NULL;
\r
266 c->editbox.shortcut = shortcut;
\r
267 c->editbox.percentwidth = percentage;
\r
268 c->editbox.password = 0;
\r
269 c->editbox.has_list = 1;
\r
270 c->editbox.context2 = context2;
\r
275 * `ncolumns' is followed by (alternately) radio button titles and
\r
276 * intorptrs, until a NULL in place of a title string is seen. Each
\r
277 * title is expected to be followed by a shortcut _iff_ `shortcut'
\r
280 union control *ctrl_radiobuttons(struct controlset *s, char *label,
\r
281 char shortcut, int ncolumns, intorptr helpctx,
\r
282 handler_fn handler, intorptr context, ...)
\r
286 union control *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context);
\r
287 c->radio.label = label ? dupstr(label) : NULL;
\r
288 c->radio.shortcut = shortcut;
\r
289 c->radio.ncolumns = ncolumns;
\r
291 * Initial pass along variable argument list to count the
\r
294 va_start(ap, context);
\r
296 while (va_arg(ap, char *) != NULL) {
\r
298 if (c->radio.shortcut == NO_SHORTCUT)
\r
299 (void)va_arg(ap, int); /* char promotes to int in arg lists */
\r
300 (void)va_arg(ap, intorptr);
\r
303 c->radio.nbuttons = i;
\r
304 if (c->radio.shortcut == NO_SHORTCUT)
\r
305 c->radio.shortcuts = snewn(c->radio.nbuttons, char);
\r
307 c->radio.shortcuts = NULL;
\r
308 c->radio.buttons = snewn(c->radio.nbuttons, char *);
\r
309 c->radio.buttondata = snewn(c->radio.nbuttons, intorptr);
\r
311 * Second pass along variable argument list to actually fill in
\r
314 va_start(ap, context);
\r
315 for (i = 0; i < c->radio.nbuttons; i++) {
\r
316 c->radio.buttons[i] = dupstr(va_arg(ap, char *));
\r
317 if (c->radio.shortcut == NO_SHORTCUT)
\r
318 c->radio.shortcuts[i] = va_arg(ap, int);
\r
319 /* char promotes to int in arg lists */
\r
320 c->radio.buttondata[i] = va_arg(ap, intorptr);
\r
326 union control *ctrl_pushbutton(struct controlset *s,char *label,char shortcut,
\r
327 intorptr helpctx, handler_fn handler,
\r
330 union control *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context);
\r
331 c->button.label = label ? dupstr(label) : NULL;
\r
332 c->button.shortcut = shortcut;
\r
333 c->button.isdefault = 0;
\r
334 c->button.iscancel = 0;
\r
338 union control *ctrl_listbox(struct controlset *s,char *label,char shortcut,
\r
339 intorptr helpctx, handler_fn handler,
\r
342 union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
\r
343 c->listbox.label = label ? dupstr(label) : NULL;
\r
344 c->listbox.shortcut = shortcut;
\r
345 c->listbox.height = 5; /* *shrug* a plausible default */
\r
346 c->listbox.draglist = 0;
\r
347 c->listbox.multisel = 0;
\r
348 c->listbox.percentwidth = 100;
\r
349 c->listbox.ncols = 0;
\r
350 c->listbox.percentages = NULL;
\r
354 union control *ctrl_droplist(struct controlset *s, char *label, char shortcut,
\r
355 int percentage, intorptr helpctx,
\r
356 handler_fn handler, intorptr context)
\r
358 union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
\r
359 c->listbox.label = label ? dupstr(label) : NULL;
\r
360 c->listbox.shortcut = shortcut;
\r
361 c->listbox.height = 0; /* means it's a drop-down list */
\r
362 c->listbox.draglist = 0;
\r
363 c->listbox.multisel = 0;
\r
364 c->listbox.percentwidth = percentage;
\r
365 c->listbox.ncols = 0;
\r
366 c->listbox.percentages = NULL;
\r
370 union control *ctrl_draglist(struct controlset *s,char *label,char shortcut,
\r
371 intorptr helpctx, handler_fn handler,
\r
374 union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
\r
375 c->listbox.label = label ? dupstr(label) : NULL;
\r
376 c->listbox.shortcut = shortcut;
\r
377 c->listbox.height = 5; /* *shrug* a plausible default */
\r
378 c->listbox.draglist = 1;
\r
379 c->listbox.multisel = 0;
\r
380 c->listbox.percentwidth = 100;
\r
381 c->listbox.ncols = 0;
\r
382 c->listbox.percentages = NULL;
\r
386 union control *ctrl_filesel(struct controlset *s,char *label,char shortcut,
\r
387 char const *filter, int write, char *title,
\r
388 intorptr helpctx, handler_fn handler,
\r
391 union control *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context);
\r
392 c->fileselect.label = label ? dupstr(label) : NULL;
\r
393 c->fileselect.shortcut = shortcut;
\r
394 c->fileselect.filter = filter;
\r
395 c->fileselect.for_writing = write;
\r
396 c->fileselect.title = dupstr(title);
\r
400 union control *ctrl_fontsel(struct controlset *s,char *label,char shortcut,
\r
401 intorptr helpctx, handler_fn handler,
\r
404 union control *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context);
\r
405 c->fontselect.label = label ? dupstr(label) : NULL;
\r
406 c->fontselect.shortcut = shortcut;
\r
410 union control *ctrl_tabdelay(struct controlset *s, union control *ctrl)
\r
412 union control *c = ctrl_new(s, CTRL_TABDELAY, P(NULL), NULL, P(NULL));
\r
413 c->tabdelay.ctrl = ctrl;
\r
417 union control *ctrl_text(struct controlset *s, char *text, intorptr helpctx)
\r
419 union control *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL));
\r
420 c->text.label = dupstr(text);
\r
424 union control *ctrl_checkbox(struct controlset *s, char *label, char shortcut,
\r
425 intorptr helpctx, handler_fn handler,
\r
428 union control *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context);
\r
429 c->checkbox.label = label ? dupstr(label) : NULL;
\r
430 c->checkbox.shortcut = shortcut;
\r
434 void ctrl_free(union control *ctrl)
\r
438 sfree(ctrl->generic.label);
\r
439 switch (ctrl->generic.type) {
\r
441 for (i = 0; i < ctrl->radio.nbuttons; i++)
\r
442 sfree(ctrl->radio.buttons[i]);
\r
443 sfree(ctrl->radio.buttons);
\r
444 sfree(ctrl->radio.shortcuts);
\r
445 sfree(ctrl->radio.buttondata);
\r
448 sfree(ctrl->columns.percentages);
\r
451 sfree(ctrl->listbox.percentages);
\r
453 case CTRL_FILESELECT:
\r
454 sfree(ctrl->fileselect.title);
\r
460 void dlg_stdradiobutton_handler(union control *ctrl, void *dlg,
\r
461 void *data, int event)
\r
465 * For a standard radio button set, the context parameter gives
\r
466 * offsetof(targetfield, Config), and the extra data per button
\r
467 * gives the value the target field should take if that button
\r
468 * is the one selected.
\r
470 if (event == EVENT_REFRESH) {
\r
471 for (button = 0; button < ctrl->radio.nbuttons; button++)
\r
472 if (*(int *)ATOFFSET(data, ctrl->radio.context.i) ==
\r
473 ctrl->radio.buttondata[button].i)
\r
475 /* We expected that `break' to happen, in all circumstances. */
\r
476 assert(button < ctrl->radio.nbuttons);
\r
477 dlg_radiobutton_set(ctrl, dlg, button);
\r
478 } else if (event == EVENT_VALCHANGE) {
\r
479 button = dlg_radiobutton_get(ctrl, dlg);
\r
480 assert(button >= 0 && button < ctrl->radio.nbuttons);
\r
481 *(int *)ATOFFSET(data, ctrl->radio.context.i) =
\r
482 ctrl->radio.buttondata[button].i;
\r
486 void dlg_stdcheckbox_handler(union control *ctrl, void *dlg,
\r
487 void *data, int event)
\r
489 int offset, invert;
\r
492 * For a standard checkbox, the context parameter gives
\r
493 * offsetof(targetfield, Config), optionally ORed with
\r
496 offset = ctrl->checkbox.context.i;
\r
497 if (offset & CHECKBOX_INVERT) {
\r
498 offset &= ~CHECKBOX_INVERT;
\r
504 * C lacks a logical XOR, so the following code uses the idiom
\r
505 * (!a ^ !b) to obtain the logical XOR of a and b. (That is, 1
\r
506 * iff exactly one of a and b is nonzero, otherwise 0.)
\r
509 if (event == EVENT_REFRESH) {
\r
510 dlg_checkbox_set(ctrl,dlg, (!*(int *)ATOFFSET(data,offset) ^ !invert));
\r
511 } else if (event == EVENT_VALCHANGE) {
\r
512 *(int *)ATOFFSET(data, offset) = !dlg_checkbox_get(ctrl,dlg) ^ !invert;
\r
516 void dlg_stdeditbox_handler(union control *ctrl, void *dlg,
\r
517 void *data, int event)
\r
520 * The standard edit-box handler expects the main `context'
\r
521 * field to contain the `offsetof' a field in the structure
\r
522 * pointed to by `data'. The secondary `context2' field
\r
523 * indicates the type of this field:
\r
525 * - if context2 > 0, the field is a char array and context2
\r
527 * - if context2 == -1, the field is an int and the edit box
\r
529 * - if context2 < -1, the field is an int and the edit box is
\r
530 * _floating_, and (-context2) gives the scale. (E.g. if
\r
531 * context2 == -1000, then typing 1.2 into the box will set
\r
532 * the field to 1200.)
\r
534 int offset = ctrl->editbox.context.i;
\r
535 int length = ctrl->editbox.context2.i;
\r
538 char *field = (char *)ATOFFSET(data, offset);
\r
539 if (event == EVENT_REFRESH) {
\r
540 dlg_editbox_set(ctrl, dlg, field);
\r
541 } else if (event == EVENT_VALCHANGE) {
\r
542 dlg_editbox_get(ctrl, dlg, field, length);
\r
544 } else if (length < 0) {
\r
545 int *field = (int *)ATOFFSET(data, offset);
\r
547 if (event == EVENT_REFRESH) {
\r
549 sprintf(data, "%d", *field);
\r
551 sprintf(data, "%g", (double)*field / (double)(-length));
\r
552 dlg_editbox_set(ctrl, dlg, data);
\r
553 } else if (event == EVENT_VALCHANGE) {
\r
554 dlg_editbox_get(ctrl, dlg, data, lenof(data));
\r
556 *field = atoi(data);
\r
558 *field = (int)((-length) * atof(data));
\r
563 void dlg_stdfilesel_handler(union control *ctrl, void *dlg,
\r
564 void *data, int event)
\r
567 * The standard file-selector handler expects the `context'
\r
568 * field to contain the `offsetof' a Filename field in the
\r
569 * structure pointed to by `data'.
\r
571 int offset = ctrl->fileselect.context.i;
\r
573 if (event == EVENT_REFRESH) {
\r
574 dlg_filesel_set(ctrl, dlg, *(Filename *)ATOFFSET(data, offset));
\r
575 } else if (event == EVENT_VALCHANGE) {
\r
576 dlg_filesel_get(ctrl, dlg, (Filename *)ATOFFSET(data, offset));
\r
580 void dlg_stdfontsel_handler(union control *ctrl, void *dlg,
\r
581 void *data, int event)
\r
584 * The standard file-selector handler expects the `context'
\r
585 * field to contain the `offsetof' a FontSpec field in the
\r
586 * structure pointed to by `data'.
\r
588 int offset = ctrl->fontselect.context.i;
\r
590 if (event == EVENT_REFRESH) {
\r
591 dlg_fontsel_set(ctrl, dlg, *(FontSpec *)ATOFFSET(data, offset));
\r
592 } else if (event == EVENT_VALCHANGE) {
\r
593 dlg_fontsel_get(ctrl, dlg, (FontSpec *)ATOFFSET(data, offset));
\r