2 * dialog.c - a reasonably platform-independent mechanism for
3 * describing dialog boxes.
10 #define DEFINE_INTORPTR_FNS
15 int ctrl_path_elements(char *path
)
19 if (*path
== '/') i
++;
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
)
31 if ((*p1
== '/' || *p1
== '\0') &&
32 (*p2
== '/' || *p2
== '\0'))
33 i
++; /* a whole element matches, ooh */
35 return i
; /* mismatch */
38 return INT_MAX
; /* exact match */
41 struct controlbox
*ctrl_new_box(void)
43 struct controlbox
*ret
= smalloc(sizeof(struct controlbox
));
45 ret
->nctrlsets
= ret
->ctrlsetsize
= 0;
47 ret
->nfrees
= ret
->freesize
= 0;
53 void ctrl_free_box(struct controlbox
*b
)
57 for (i
= 0; i
< b
->nctrlsets
; i
++) {
58 ctrl_free_set(b
->ctrlsets
[i
]);
60 for (i
= 0; i
< b
->nfrees
; i
++)
67 void ctrl_free_set(struct controlset
*s
)
74 for (i
= 0; i
< s
->ncontrols
; i
++) {
75 ctrl_free(s
->ctrls
[i
]);
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
86 static int ctrl_find_set(struct controlbox
*b
, char *path
, int start
)
91 for (i
= 0; i
< b
->nctrlsets
; i
++) {
92 thisone
= ctrl_path_compare(path
, b
->ctrlsets
[i
]->pathname
);
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.
100 if ((start
&& thisone
== INT_MAX
) || thisone
< last
)
104 return b
->nctrlsets
; /* insert at end */
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.
112 int ctrl_find_path(struct controlbox
*b
, char *path
, int index
)
115 index
= ctrl_find_set(b
, path
, 1);
119 if (index
< b
->nctrlsets
&& !strcmp(path
, b
->ctrlsets
[index
]->pathname
))
125 /* Set up a panel title. */
126 struct controlset
*ctrl_settitle(struct controlbox
*b
,
127 char *path
, char *title
)
130 struct controlset
*s
= smalloc(sizeof(struct controlset
));
131 int index
= ctrl_find_set(b
, path
, 1);
132 s
->pathname
= dupstr(path
);
134 s
->boxtitle
= dupstr(title
);
135 s
->ncontrols
= s
->ctrlsize
= 0;
136 s
->ncolumns
= 0; /* this is a title! */
138 if (b
->nctrlsets
>= b
->ctrlsetsize
) {
139 b
->ctrlsetsize
= b
->nctrlsets
+ 32;
140 b
->ctrlsets
= srealloc(b
->ctrlsets
,
141 b
->ctrlsetsize
*sizeof(*b
->ctrlsets
));
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
;
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
)
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
];
164 s
= smalloc(sizeof(struct controlset
));
165 s
->pathname
= dupstr(path
);
166 s
->boxname
= dupstr(name
);
167 s
->boxtitle
= boxtitle ?
dupstr(boxtitle
) : NULL
;
169 s
->ncontrols
= s
->ctrlsize
= 0;
171 if (b
->nctrlsets
>= b
->ctrlsetsize
) {
172 b
->ctrlsetsize
= b
->nctrlsets
+ 32;
173 b
->ctrlsets
= srealloc(b
->ctrlsets
,
174 b
->ctrlsetsize
*sizeof(*b
->ctrlsets
));
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
;
184 /* Allocate some private data in a controlbox. */
185 void *ctrl_alloc(struct controlbox
*b
, size_t size
)
189 if (b
->nfrees
>= b
->freesize
) {
190 b
->freesize
= b
->nfrees
+ 32;
191 b
->frees
= srealloc(b
->frees
, b
->freesize
*sizeof(*b
->frees
));
193 b
->frees
[b
->nfrees
++] = p
;
197 static union control
*ctrl_new(struct controlset
*s
, int type
,
198 intorptr helpctx
, handler_fn handler
,
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
));
206 s
->ctrls
[s
->ncontrols
++] = c
;
208 * Fill in the standard fields.
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
;
220 /* `ncolumns' is followed by that many percentages, as integers. */
221 union control
*ctrl_columns(struct controlset
*s
, int ncolumns
, ...)
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
;
228 c
->columns
.percentages
= NULL
;
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);
241 union control
*ctrl_editbox(struct controlset
*s
, char *label
, char shortcut
,
243 intorptr helpctx
, handler_fn handler
,
244 intorptr context
, intorptr context2
)
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
;
256 union control
*ctrl_combobox(struct controlset
*s
, char *label
, char shortcut
,
258 intorptr helpctx
, handler_fn handler
,
259 intorptr context
, intorptr context2
)
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
;
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'
277 union control
*ctrl_radiobuttons(struct controlset
*s
, char *label
,
278 char shortcut
, int ncolumns
, intorptr helpctx
,
279 handler_fn handler
, intorptr context
, ...)
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
;
288 * Initial pass along variable argument list to count the
291 va_start(ap
, context
);
293 while (va_arg(ap
, char *) != NULL
) {
295 if (c
->radio
.shortcut
== NO_SHORTCUT
)
296 va_arg(ap
, int); /* char promotes to int in arg lists */
297 va_arg(ap
, intorptr
);
300 c
->radio
.nbuttons
= i
;
301 if (c
->radio
.shortcut
== NO_SHORTCUT
)
302 c
->radio
.shortcuts
= smalloc(c
->radio
.nbuttons
* sizeof(char));
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
));
308 * Second pass along variable argument list to actually fill in
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
);
323 union control
*ctrl_pushbutton(struct controlset
*s
,char *label
,char shortcut
,
324 intorptr helpctx
, handler_fn handler
,
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;
334 union control
*ctrl_listbox(struct controlset
*s
,char *label
,char shortcut
,
335 intorptr helpctx
, handler_fn handler
,
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
;
350 union control
*ctrl_droplist(struct controlset
*s
, char *label
, char shortcut
,
351 int percentage
, intorptr helpctx
,
352 handler_fn handler
, intorptr context
)
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
;
364 union control
*ctrl_draglist(struct controlset
*s
,char *label
,char shortcut
,
365 intorptr helpctx
, handler_fn handler
,
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;
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
,
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
);
392 union control
*ctrl_fontsel(struct controlset
*s
,char *label
,char shortcut
,
393 intorptr helpctx
, handler_fn handler
,
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
;
402 union control
*ctrl_tabdelay(struct controlset
*s
, union control
*ctrl
)
404 union control
*c
= ctrl_new(s
, CTRL_TABDELAY
, P(NULL
), NULL
, P(NULL
));
405 c
->tabdelay
.ctrl
= ctrl
;
409 union control
*ctrl_text(struct controlset
*s
, char *text
, intorptr helpctx
)
411 union control
*c
= ctrl_new(s
, CTRL_TEXT
, helpctx
, NULL
, P(NULL
));
412 c
->text
.label
= dupstr(text
);
416 union control
*ctrl_checkbox(struct controlset
*s
, char *label
, char shortcut
,
417 intorptr helpctx
, handler_fn handler
,
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
;
426 void ctrl_free(union control
*ctrl
)
430 sfree(ctrl
->generic
.label
);
431 switch (ctrl
->generic
.type
) {
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
);
440 sfree(ctrl
->columns
.percentages
);
443 sfree(ctrl
->listbox
.percentages
);
445 case CTRL_FILESELECT
:
446 sfree(ctrl
->fileselect
.title
);
452 void dlg_stdradiobutton_handler(union control
*ctrl
, void *dlg
,
453 void *data
, int event
)
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.
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
)
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
;
478 void dlg_stdcheckbox_handler(union control
*ctrl
, void *dlg
,
479 void *data
, int event
)
484 * For a standard checkbox, the context parameter gives
485 * offsetof(targetfield, Config), optionally ORed with
488 offset
= ctrl
->checkbox
.context
.i
;
489 if (offset
& CHECKBOX_INVERT
) {
490 offset
&= ~CHECKBOX_INVERT
;
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.)
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
;
508 void dlg_stdeditbox_handler(union control
*ctrl
, void *dlg
,
509 void *data
, int event
)
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:
517 * - if context2 > 0, the field is a char array and context2
519 * - if context2 == -1, the field is an int and the edit box
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.)
526 int offset
= ctrl
->editbox
.context
.i
;
527 int length
= ctrl
->editbox
.context2
.i
;
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
);
536 } else if (length
< 0) {
537 int *field
= (int *)ATOFFSET(data
, offset
);
539 if (event
== EVENT_REFRESH
) {
541 sprintf(data
, "%d", *field
);
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
));
550 *field
= (int)((-length
) * atof(data
));
555 void dlg_stdfilesel_handler(union control
*ctrl
, void *dlg
,
556 void *data
, int event
)
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'.
563 int offset
= ctrl
->fileselect
.context
.i
;
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
));
572 void dlg_stdfontsel_handler(union control
*ctrl
, void *dlg
,
573 void *data
, int event
)
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'.
580 int offset
= ctrl
->fontselect
.context
.i
;
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
));