+void winctrl_add_shortcuts(struct dlgparam *dp, struct winctrl *c)
+{
+ int i;
+ for (i = 0; i < lenof(c->shortcuts); i++)
+ if (c->shortcuts[i] != NO_SHORTCUT) {
+ unsigned char s = tolower((unsigned char)c->shortcuts[i]);
+ assert(!dp->shortcuts[s]);
+ dp->shortcuts[s] = TRUE;
+ }
+}
+
+void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c)
+{
+ int i;
+ for (i = 0; i < lenof(c->shortcuts); i++)
+ if (c->shortcuts[i] != NO_SHORTCUT) {
+ unsigned char s = tolower((unsigned char)c->shortcuts[i]);
+ assert(dp->shortcuts[s]);
+ dp->shortcuts[s] = FALSE;
+ }
+}
+
+static int winctrl_cmp_byctrl(void *av, void *bv)
+{
+ struct winctrl *a = (struct winctrl *)av;
+ struct winctrl *b = (struct winctrl *)bv;
+ if (a->ctrl < b->ctrl)
+ return -1;
+ else if (a->ctrl > b->ctrl)
+ return +1;
+ else
+ return 0;
+}
+static int winctrl_cmp_byid(void *av, void *bv)
+{
+ struct winctrl *a = (struct winctrl *)av;
+ struct winctrl *b = (struct winctrl *)bv;
+ if (a->base_id < b->base_id)
+ return -1;
+ else if (a->base_id > b->base_id)
+ return +1;
+ else
+ return 0;
+}
+static int winctrl_cmp_byctrl_find(void *av, void *bv)
+{
+ union control *a = (union control *)av;
+ struct winctrl *b = (struct winctrl *)bv;
+ if (a < b->ctrl)
+ return -1;
+ else if (a > b->ctrl)
+ return +1;
+ else
+ return 0;
+}
+static int winctrl_cmp_byid_find(void *av, void *bv)
+{
+ int *a = (int *)av;
+ struct winctrl *b = (struct winctrl *)bv;
+ if (*a < b->base_id)
+ return -1;
+ else if (*a >= b->base_id + b->num_ids)
+ return +1;
+ else
+ return 0;
+}
+
+void winctrl_init(struct winctrls *wc)
+{
+ wc->byctrl = newtree234(winctrl_cmp_byctrl);
+ wc->byid = newtree234(winctrl_cmp_byid);
+}
+void winctrl_cleanup(struct winctrls *wc)
+{
+ struct winctrl *c;
+
+ while ((c = index234(wc->byid, 0)) != NULL) {
+ winctrl_remove(wc, c);
+ sfree(c->data);
+ sfree(c);
+ }
+
+ freetree234(wc->byctrl);
+ freetree234(wc->byid);
+ wc->byctrl = wc->byid = NULL;
+}
+
+void winctrl_add(struct winctrls *wc, struct winctrl *c)
+{
+ struct winctrl *ret;
+ if (c->ctrl) {
+ ret = add234(wc->byctrl, c);
+ assert(ret == c);
+ }
+ ret = add234(wc->byid, c);
+ assert(ret == c);
+}
+
+void winctrl_remove(struct winctrls *wc, struct winctrl *c)
+{
+ struct winctrl *ret;
+ ret = del234(wc->byctrl, c);
+ ret = del234(wc->byid, c);
+ assert(ret == c);
+}
+
+struct winctrl *winctrl_findbyctrl(struct winctrls *wc, union control *ctrl)
+{
+ return find234(wc->byctrl, ctrl, winctrl_cmp_byctrl_find);
+}
+
+struct winctrl *winctrl_findbyid(struct winctrls *wc, int id)
+{
+ return find234(wc->byid, &id, winctrl_cmp_byid_find);
+}
+
+struct winctrl *winctrl_findbyindex(struct winctrls *wc, int index)
+{
+ return index234(wc->byid, index);
+}
+
+void winctrl_layout(struct dlgparam *dp, struct winctrls *wc,
+ struct ctlpos *cp, struct controlset *s, int *id)
+{
+ struct ctlpos columns[16];
+ int ncols, colstart, colspan;
+
+ struct ctlpos tabdelays[16];
+ union control *tabdelayed[16];
+ int ntabdelays;
+
+ struct ctlpos pos;
+
+ char shortcuts[MAX_SHORTCUTS_PER_CTRL];
+ int nshortcuts;
+ char *escaped;
+ int i, base_id, num_ids;
+ void *data;
+
+ base_id = *id;
+
+ /* Start a containing box, if we have a boxname. */
+ if (s->boxname && *s->boxname) {
+ struct winctrl *c = smalloc(sizeof(struct winctrl));
+ c->ctrl = NULL;
+ c->base_id = base_id;
+ c->num_ids = 1;
+ c->data = NULL;
+ memset(c->shortcuts, NO_SHORTCUT, lenof(c->shortcuts));
+ winctrl_add(wc, c);
+ beginbox(cp, s->boxtitle, base_id);
+ base_id++;
+ }
+
+ /* Draw a title, if we have one. */
+ if (!s->boxname && s->boxtitle) {
+ struct winctrl *c = smalloc(sizeof(struct winctrl));
+ c->ctrl = NULL;
+ c->base_id = base_id;
+ c->num_ids = 1;
+ c->data = dupstr(s->boxtitle);
+ memset(c->shortcuts, NO_SHORTCUT, lenof(c->shortcuts));
+ winctrl_add(wc, c);
+ paneltitle(cp, base_id);
+ base_id++;
+ }
+
+ /* Initially we have just one column. */
+ ncols = 1;
+ columns[0] = *cp; /* structure copy */
+
+ /* And initially, there are no pending tab-delayed controls. */
+ ntabdelays = 0;
+
+ /* Loop over each control in the controlset. */
+ for (i = 0; i < s->ncontrols; i++) {
+ union control *ctrl = s->ctrls[i];
+
+ /*
+ * Generic processing that pertains to all control types.
+ * At the end of this if statement, we'll have produced
+ * `ctrl' (a pointer to the control we have to create, or
+ * think about creating, in this iteration of the loop),
+ * `pos' (a suitable ctlpos with which to position it), and
+ * `c' (a winctrl structure to receive details of the
+ * dialog IDs). Or we'll have done a `continue', if it was
+ * CTRL_COLUMNS and doesn't require any control creation at
+ * all.
+ */
+ if (ctrl->generic.type == CTRL_COLUMNS) {
+ assert((ctrl->columns.ncols == 1) ^ (ncols == 1));
+
+ if (ncols == 1) {
+ /*
+ * We're splitting into multiple columns.
+ */
+ int lpercent, rpercent, lx, rx, i;
+
+ ncols = ctrl->columns.ncols;
+ assert(ncols <= lenof(columns));
+ for (i = 1; i < ncols; i++)
+ columns[i] = columns[0]; /* structure copy */
+
+ lpercent = 0;
+ for (i = 0; i < ncols; i++) {
+ rpercent = lpercent + ctrl->columns.percentages[i];
+ lx = columns[i].xoff + lpercent *
+ (columns[i].width + GAPBETWEEN) / 100;
+ rx = columns[i].xoff + rpercent *
+ (columns[i].width + GAPBETWEEN) / 100;
+ columns[i].xoff = lx;
+ columns[i].width = rx - lx - GAPBETWEEN;
+ lpercent = rpercent;
+ }
+ } else {
+ /*
+ * We're recombining the various columns into one.
+ */
+ int maxy = columns[0].ypos;
+ int i;
+ for (i = 1; i < ncols; i++)
+ if (maxy < columns[i].ypos)
+ maxy = columns[i].ypos;
+ ncols = 1;
+ columns[0] = *cp; /* structure copy */
+ columns[0].ypos = maxy;
+ }
+
+ continue;
+ } else if (ctrl->generic.type == CTRL_TABDELAY) {
+ int i;
+
+ assert(!ctrl->generic.tabdelay);
+ ctrl = ctrl->tabdelay.ctrl;
+
+ for (i = 0; i < ntabdelays; i++)
+ if (tabdelayed[i] == ctrl)
+ break;
+ assert(i < ntabdelays); /* we have to have found it */
+
+ pos = tabdelays[i]; /* structure copy */
+
+ colstart = colspan = -1; /* indicate this was tab-delayed */
+
+ } else {
+ /*
+ * If it wasn't one of those, it's a genuine control;
+ * so we'll have to compute a position for it now, by
+ * checking its column span.
+ */
+ int col;
+
+ colstart = COLUMN_START(ctrl->generic.column);
+ colspan = COLUMN_SPAN(ctrl->generic.column);
+
+ pos = columns[colstart]; /* structure copy */
+ pos.width = columns[colstart+colspan-1].width +
+ (columns[colstart+colspan-1].xoff - columns[colstart].xoff);
+
+ for (col = colstart; col < colstart+colspan; col++)
+ if (pos.ypos < columns[col].ypos)
+ pos.ypos = columns[col].ypos;
+
+ /*
+ * If this control is to be tabdelayed, add it to the
+ * tabdelay list, and unset pos.hwnd to inhibit actual
+ * control creation.
+ */
+ if (ctrl->generic.tabdelay) {
+ assert(ntabdelays < lenof(tabdelays));
+ tabdelays[ntabdelays] = pos; /* structure copy */
+ tabdelayed[ntabdelays] = ctrl;
+ ntabdelays++;
+ pos.hwnd = NULL;
+ }
+ }
+
+ /* Most controls don't need anything in c->data. */
+ data = NULL;
+
+ /* And they all start off with no shortcuts registered. */
+ memset(shortcuts, NO_SHORTCUT, lenof(shortcuts));
+ nshortcuts = 0;
+
+ /*
+ * Now we're ready to actually create the control, by
+ * switching on its type.
+ */
+ switch (ctrl->generic.type) {
+ case CTRL_TEXT:
+ {
+ char *wrapped, *escaped;
+ int lines;
+ num_ids = 1;
+ wrapped = staticwrap(&pos, cp->hwnd,
+ ctrl->generic.label, &lines);
+ escaped = shortcut_escape(wrapped, NO_SHORTCUT);
+ statictext(&pos, escaped, lines, base_id);
+ sfree(escaped);
+ sfree(wrapped);
+ }
+ break;
+ case CTRL_EDITBOX:
+ num_ids = 2; /* static, edit */
+ escaped = shortcut_escape(ctrl->editbox.label,
+ ctrl->editbox.shortcut);
+ shortcuts[nshortcuts++] = ctrl->editbox.shortcut;
+ if (ctrl->editbox.percentwidth == 100) {
+ if (ctrl->editbox.has_list)
+ combobox(&pos, escaped,
+ base_id, base_id+1);
+ else
+ multiedit(&pos, ctrl->editbox.password, escaped,
+ base_id, base_id+1, 100, NULL);
+ } else {
+ if (ctrl->editbox.has_list) {
+ staticcombo(&pos, escaped, base_id, base_id+1,
+ ctrl->editbox.percentwidth);
+ } else {
+ (ctrl->editbox.password ? staticpassedit : staticedit)
+ (&pos, escaped, base_id, base_id+1,
+ ctrl->editbox.percentwidth);
+ }
+ }
+ sfree(escaped);
+ break;
+ case CTRL_RADIO:
+ num_ids = ctrl->radio.nbuttons + 1; /* label as well */
+ {
+ struct radio *buttons;
+ int i;
+
+ escaped = shortcut_escape(ctrl->radio.label,
+ ctrl->radio.shortcut);
+ shortcuts[nshortcuts++] = ctrl->radio.shortcut;
+
+ buttons = smalloc(ctrl->radio.nbuttons * sizeof(struct radio));
+
+ for (i = 0; i < ctrl->radio.nbuttons; i++) {
+ buttons[i].text =
+ shortcut_escape(ctrl->radio.buttons[i],
+ (char)(ctrl->radio.shortcuts ?
+ ctrl->radio.shortcuts[i] :
+ NO_SHORTCUT));
+ buttons[i].id = base_id + 1 + i;
+ if (ctrl->radio.shortcuts) {
+ assert(nshortcuts < MAX_SHORTCUTS_PER_CTRL);
+ shortcuts[nshortcuts++] = ctrl->radio.shortcuts[i];
+ }
+ }
+
+ radioline_common(&pos, escaped, base_id,
+ ctrl->radio.ncolumns,
+ buttons, ctrl->radio.nbuttons);
+
+ for (i = 0; i < ctrl->radio.nbuttons; i++) {
+ sfree(buttons[i].text);
+ }
+ sfree(buttons);
+ sfree(escaped);
+ }
+ break;
+ case CTRL_CHECKBOX:
+ num_ids = 1;
+ escaped = shortcut_escape(ctrl->checkbox.label,
+ ctrl->checkbox.shortcut);
+ shortcuts[nshortcuts++] = ctrl->checkbox.shortcut;
+ checkbox(&pos, escaped, base_id);
+ sfree(escaped);
+ break;
+ case CTRL_BUTTON:
+ escaped = shortcut_escape(ctrl->button.label,
+ ctrl->button.shortcut);
+ shortcuts[nshortcuts++] = ctrl->button.shortcut;
+ num_ids = 1;
+ button(&pos, escaped, base_id, ctrl->button.isdefault);
+ sfree(escaped);
+ break;
+ case CTRL_LISTBOX:
+ num_ids = 2;
+ escaped = shortcut_escape(ctrl->listbox.label,
+ ctrl->listbox.shortcut);
+ shortcuts[nshortcuts++] = ctrl->listbox.shortcut;
+ if (ctrl->listbox.draglist) {
+ data = smalloc(sizeof(struct prefslist));
+ num_ids = 4;
+ prefslist(data, &pos, ctrl->listbox.height, escaped,
+ base_id, base_id+1, base_id+2, base_id+3);
+ shortcuts[nshortcuts++] = 'u'; /* Up */
+ shortcuts[nshortcuts++] = 'd'; /* Down */
+ } else if (ctrl->listbox.height == 0) {
+ /* Drop-down list. */
+ if (ctrl->listbox.percentwidth == 100) {
+ staticddlbig(&pos, escaped,
+ base_id, base_id+1);
+ } else {
+ staticddl(&pos, escaped, base_id,
+ base_id+1, ctrl->listbox.percentwidth);
+ }
+ } else {
+ /* Ordinary list. */
+ listbox(&pos, escaped, base_id, base_id+1,
+ ctrl->listbox.height, ctrl->listbox.multisel);