--- /dev/null
+/*\r
+ * dialog.c - a reasonably platform-independent mechanism for\r
+ * describing dialog boxes.\r
+ */\r
+\r
+#include <assert.h>\r
+#include <limits.h>\r
+#include <stdarg.h>\r
+#include <stdlib.h>\r
+\r
+#define DEFINE_INTORPTR_FNS\r
+\r
+#include "putty.h"\r
+#include "dialog.h"\r
+\r
+int ctrl_path_elements(char *path)\r
+{\r
+ int i = 1;\r
+ while (*path) {\r
+ if (*path == '/') i++;\r
+ path++;\r
+ }\r
+ return i;\r
+}\r
+\r
+/* Return the number of matching path elements at the starts of p1 and p2,\r
+ * or INT_MAX if the paths are identical. */\r
+int ctrl_path_compare(char *p1, char *p2)\r
+{\r
+ int i = 0;\r
+ while (*p1 || *p2) {\r
+ if ((*p1 == '/' || *p1 == '\0') &&\r
+ (*p2 == '/' || *p2 == '\0'))\r
+ i++; /* a whole element matches, ooh */\r
+ if (*p1 != *p2)\r
+ return i; /* mismatch */\r
+ p1++, p2++;\r
+ }\r
+ return INT_MAX; /* exact match */\r
+}\r
+\r
+struct controlbox *ctrl_new_box(void)\r
+{\r
+ struct controlbox *ret = snew(struct controlbox);\r
+\r
+ ret->nctrlsets = ret->ctrlsetsize = 0;\r
+ ret->ctrlsets = NULL;\r
+ ret->nfrees = ret->freesize = 0;\r
+ ret->frees = NULL;\r
+\r
+ return ret;\r
+}\r
+\r
+void ctrl_free_box(struct controlbox *b)\r
+{\r
+ int i;\r
+\r
+ for (i = 0; i < b->nctrlsets; i++) {\r
+ ctrl_free_set(b->ctrlsets[i]);\r
+ }\r
+ for (i = 0; i < b->nfrees; i++)\r
+ sfree(b->frees[i]);\r
+ sfree(b->ctrlsets);\r
+ sfree(b->frees);\r
+ sfree(b);\r
+}\r
+\r
+void ctrl_free_set(struct controlset *s)\r
+{\r
+ int i;\r
+\r
+ sfree(s->pathname);\r
+ sfree(s->boxname);\r
+ sfree(s->boxtitle);\r
+ for (i = 0; i < s->ncontrols; i++) {\r
+ ctrl_free(s->ctrls[i]);\r
+ }\r
+ sfree(s->ctrls);\r
+ sfree(s);\r
+}\r
+\r
+/*\r
+ * Find the index of first controlset in a controlbox for a given\r
+ * path. If that path doesn't exist, return the index where it\r
+ * should be inserted.\r
+ */\r
+static int ctrl_find_set(struct controlbox *b, char *path, int start)\r
+{\r
+ int i, last, thisone;\r
+\r
+ last = 0;\r
+ for (i = 0; i < b->nctrlsets; i++) {\r
+ thisone = ctrl_path_compare(path, b->ctrlsets[i]->pathname);\r
+ /*\r
+ * If `start' is true and there exists a controlset with\r
+ * exactly the path we've been given, we should return the\r
+ * index of the first such controlset we find. Otherwise,\r
+ * we should return the index of the first entry in which\r
+ * _fewer_ path elements match than they did last time.\r
+ */\r
+ if ((start && thisone == INT_MAX) || thisone < last)\r
+ return i;\r
+ last = thisone;\r
+ }\r
+ return b->nctrlsets; /* insert at end */\r
+}\r
+\r
+/*\r
+ * Find the index of next controlset in a controlbox for a given\r
+ * path, or -1 if no such controlset exists. If -1 is passed as\r
+ * input, finds the first.\r
+ */\r
+int ctrl_find_path(struct controlbox *b, char *path, int index)\r
+{\r
+ if (index < 0)\r
+ index = ctrl_find_set(b, path, 1);\r
+ else\r
+ index++;\r
+\r
+ if (index < b->nctrlsets && !strcmp(path, b->ctrlsets[index]->pathname))\r
+ return index;\r
+ else\r
+ return -1;\r
+}\r
+\r
+/* Set up a panel title. */\r
+struct controlset *ctrl_settitle(struct controlbox *b,\r
+ char *path, char *title)\r
+{\r
+ \r
+ struct controlset *s = snew(struct controlset);\r
+ int index = ctrl_find_set(b, path, 1);\r
+ s->pathname = dupstr(path);\r
+ s->boxname = NULL;\r
+ s->boxtitle = dupstr(title);\r
+ s->ncontrols = s->ctrlsize = 0;\r
+ s->ncolumns = 0; /* this is a title! */\r
+ s->ctrls = NULL;\r
+ if (b->nctrlsets >= b->ctrlsetsize) {\r
+ b->ctrlsetsize = b->nctrlsets + 32;\r
+ b->ctrlsets = sresize(b->ctrlsets, b->ctrlsetsize,struct controlset *);\r
+ }\r
+ if (index < b->nctrlsets)\r
+ memmove(&b->ctrlsets[index+1], &b->ctrlsets[index],\r
+ (b->nctrlsets-index) * sizeof(*b->ctrlsets));\r
+ b->ctrlsets[index] = s;\r
+ b->nctrlsets++;\r
+ return s;\r
+}\r
+\r
+/* Retrieve a pointer to a controlset, creating it if absent. */\r
+struct controlset *ctrl_getset(struct controlbox *b,\r
+ char *path, char *name, char *boxtitle)\r
+{\r
+ struct controlset *s;\r
+ int index = ctrl_find_set(b, path, 1);\r
+ while (index < b->nctrlsets &&\r
+ !strcmp(b->ctrlsets[index]->pathname, path)) {\r
+ if (b->ctrlsets[index]->boxname &&\r
+ !strcmp(b->ctrlsets[index]->boxname, name))\r
+ return b->ctrlsets[index];\r
+ index++;\r
+ }\r
+ s = snew(struct controlset);\r
+ s->pathname = dupstr(path);\r
+ s->boxname = dupstr(name);\r
+ s->boxtitle = boxtitle ? dupstr(boxtitle) : NULL;\r
+ s->ncolumns = 1;\r
+ s->ncontrols = s->ctrlsize = 0;\r
+ s->ctrls = NULL;\r
+ if (b->nctrlsets >= b->ctrlsetsize) {\r
+ b->ctrlsetsize = b->nctrlsets + 32;\r
+ b->ctrlsets = sresize(b->ctrlsets, b->ctrlsetsize,struct controlset *);\r
+ }\r
+ if (index < b->nctrlsets)\r
+ memmove(&b->ctrlsets[index+1], &b->ctrlsets[index],\r
+ (b->nctrlsets-index) * sizeof(*b->ctrlsets));\r
+ b->ctrlsets[index] = s;\r
+ b->nctrlsets++;\r
+ return s;\r
+}\r
+\r
+/* Allocate some private data in a controlbox. */\r
+void *ctrl_alloc(struct controlbox *b, size_t size)\r
+{\r
+ void *p;\r
+ /*\r
+ * This is an internal allocation routine, so it's allowed to\r
+ * use smalloc directly.\r
+ */\r
+ p = smalloc(size);\r
+ if (b->nfrees >= b->freesize) {\r
+ b->freesize = b->nfrees + 32;\r
+ b->frees = sresize(b->frees, b->freesize, void *);\r
+ }\r
+ b->frees[b->nfrees++] = p;\r
+ return p;\r
+}\r
+\r
+static union control *ctrl_new(struct controlset *s, int type,\r
+ intorptr helpctx, handler_fn handler,\r
+ intorptr context)\r
+{\r
+ union control *c = snew(union control);\r
+ if (s->ncontrols >= s->ctrlsize) {\r
+ s->ctrlsize = s->ncontrols + 32;\r
+ s->ctrls = sresize(s->ctrls, s->ctrlsize, union control *);\r
+ }\r
+ s->ctrls[s->ncontrols++] = c;\r
+ /*\r
+ * Fill in the standard fields.\r
+ */\r
+ c->generic.type = type;\r
+ c->generic.tabdelay = 0;\r
+ c->generic.column = COLUMN_FIELD(0, s->ncolumns);\r
+ c->generic.helpctx = helpctx;\r
+ c->generic.handler = handler;\r
+ c->generic.context = context;\r
+ c->generic.label = NULL;\r
+ return c;\r
+}\r
+\r
+/* `ncolumns' is followed by that many percentages, as integers. */\r
+union control *ctrl_columns(struct controlset *s, int ncolumns, ...)\r
+{\r
+ union control *c = ctrl_new(s, CTRL_COLUMNS, P(NULL), NULL, P(NULL));\r
+ assert(s->ncolumns == 1 || ncolumns == 1);\r
+ c->columns.ncols = ncolumns;\r
+ s->ncolumns = ncolumns;\r
+ if (ncolumns == 1) {\r
+ c->columns.percentages = NULL;\r
+ } else {\r
+ va_list ap;\r
+ int i;\r
+ c->columns.percentages = snewn(ncolumns, int);\r
+ va_start(ap, ncolumns);\r
+ for (i = 0; i < ncolumns; i++)\r
+ c->columns.percentages[i] = va_arg(ap, int);\r
+ va_end(ap);\r
+ }\r
+ return c;\r
+}\r
+\r
+union control *ctrl_editbox(struct controlset *s, char *label, char shortcut,\r
+ int percentage,\r
+ intorptr helpctx, handler_fn handler,\r
+ intorptr context, intorptr context2)\r
+{\r
+ union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);\r
+ c->editbox.label = label ? dupstr(label) : NULL;\r
+ c->editbox.shortcut = shortcut;\r
+ c->editbox.percentwidth = percentage;\r
+ c->editbox.password = 0;\r
+ c->editbox.has_list = 0;\r
+ c->editbox.context2 = context2;\r
+ return c;\r
+}\r
+\r
+union control *ctrl_combobox(struct controlset *s, char *label, char shortcut,\r
+ int percentage,\r
+ intorptr helpctx, handler_fn handler,\r
+ intorptr context, intorptr context2)\r
+{\r
+ union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);\r
+ c->editbox.label = label ? dupstr(label) : NULL;\r
+ c->editbox.shortcut = shortcut;\r
+ c->editbox.percentwidth = percentage;\r
+ c->editbox.password = 0;\r
+ c->editbox.has_list = 1;\r
+ c->editbox.context2 = context2;\r
+ return c;\r
+}\r
+\r
+/*\r
+ * `ncolumns' is followed by (alternately) radio button titles and\r
+ * intorptrs, until a NULL in place of a title string is seen. Each\r
+ * title is expected to be followed by a shortcut _iff_ `shortcut'\r
+ * is NO_SHORTCUT.\r
+ */\r
+union control *ctrl_radiobuttons(struct controlset *s, char *label,\r
+ char shortcut, int ncolumns, intorptr helpctx,\r
+ handler_fn handler, intorptr context, ...)\r
+{\r
+ va_list ap;\r
+ int i;\r
+ union control *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context);\r
+ c->radio.label = label ? dupstr(label) : NULL;\r
+ c->radio.shortcut = shortcut;\r
+ c->radio.ncolumns = ncolumns;\r
+ /*\r
+ * Initial pass along variable argument list to count the\r
+ * buttons.\r
+ */\r
+ va_start(ap, context);\r
+ i = 0;\r
+ while (va_arg(ap, char *) != NULL) {\r
+ i++;\r
+ if (c->radio.shortcut == NO_SHORTCUT)\r
+ (void)va_arg(ap, int); /* char promotes to int in arg lists */\r
+ (void)va_arg(ap, intorptr);\r
+ }\r
+ va_end(ap);\r
+ c->radio.nbuttons = i;\r
+ if (c->radio.shortcut == NO_SHORTCUT)\r
+ c->radio.shortcuts = snewn(c->radio.nbuttons, char);\r
+ else\r
+ c->radio.shortcuts = NULL;\r
+ c->radio.buttons = snewn(c->radio.nbuttons, char *);\r
+ c->radio.buttondata = snewn(c->radio.nbuttons, intorptr);\r
+ /*\r
+ * Second pass along variable argument list to actually fill in\r
+ * the structure.\r
+ */\r
+ va_start(ap, context);\r
+ for (i = 0; i < c->radio.nbuttons; i++) {\r
+ c->radio.buttons[i] = dupstr(va_arg(ap, char *));\r
+ if (c->radio.shortcut == NO_SHORTCUT)\r
+ c->radio.shortcuts[i] = va_arg(ap, int);\r
+ /* char promotes to int in arg lists */\r
+ c->radio.buttondata[i] = va_arg(ap, intorptr);\r
+ }\r
+ va_end(ap);\r
+ return c;\r
+}\r
+\r
+union control *ctrl_pushbutton(struct controlset *s,char *label,char shortcut,\r
+ intorptr helpctx, handler_fn handler,\r
+ intorptr context)\r
+{\r
+ union control *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context);\r
+ c->button.label = label ? dupstr(label) : NULL;\r
+ c->button.shortcut = shortcut;\r
+ c->button.isdefault = 0;\r
+ c->button.iscancel = 0;\r
+ return c;\r
+}\r
+\r
+union control *ctrl_listbox(struct controlset *s,char *label,char shortcut,\r
+ intorptr helpctx, handler_fn handler,\r
+ intorptr context)\r
+{\r
+ union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);\r
+ c->listbox.label = label ? dupstr(label) : NULL;\r
+ c->listbox.shortcut = shortcut;\r
+ c->listbox.height = 5; /* *shrug* a plausible default */\r
+ c->listbox.draglist = 0;\r
+ c->listbox.multisel = 0;\r
+ c->listbox.percentwidth = 100;\r
+ c->listbox.ncols = 0;\r
+ c->listbox.percentages = NULL;\r
+ return c;\r
+}\r
+\r
+union control *ctrl_droplist(struct controlset *s, char *label, char shortcut,\r
+ int percentage, intorptr helpctx,\r
+ handler_fn handler, intorptr context)\r
+{\r
+ union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);\r
+ c->listbox.label = label ? dupstr(label) : NULL;\r
+ c->listbox.shortcut = shortcut;\r
+ c->listbox.height = 0; /* means it's a drop-down list */\r
+ c->listbox.draglist = 0;\r
+ c->listbox.multisel = 0;\r
+ c->listbox.percentwidth = percentage;\r
+ c->listbox.ncols = 0;\r
+ c->listbox.percentages = NULL;\r
+ return c;\r
+}\r
+\r
+union control *ctrl_draglist(struct controlset *s,char *label,char shortcut,\r
+ intorptr helpctx, handler_fn handler,\r
+ intorptr context)\r
+{\r
+ union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);\r
+ c->listbox.label = label ? dupstr(label) : NULL;\r
+ c->listbox.shortcut = shortcut;\r
+ c->listbox.height = 5; /* *shrug* a plausible default */\r
+ c->listbox.draglist = 1;\r
+ c->listbox.multisel = 0;\r
+ c->listbox.percentwidth = 100;\r
+ c->listbox.ncols = 0;\r
+ c->listbox.percentages = NULL;\r
+ return c;\r
+}\r
+\r
+union control *ctrl_filesel(struct controlset *s,char *label,char shortcut,\r
+ char const *filter, int write, char *title,\r
+ intorptr helpctx, handler_fn handler,\r
+ intorptr context)\r
+{\r
+ union control *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context);\r
+ c->fileselect.label = label ? dupstr(label) : NULL;\r
+ c->fileselect.shortcut = shortcut;\r
+ c->fileselect.filter = filter;\r
+ c->fileselect.for_writing = write;\r
+ c->fileselect.title = dupstr(title);\r
+ return c;\r
+}\r
+\r
+union control *ctrl_fontsel(struct controlset *s,char *label,char shortcut,\r
+ intorptr helpctx, handler_fn handler,\r
+ intorptr context)\r
+{\r
+ union control *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context);\r
+ c->fontselect.label = label ? dupstr(label) : NULL;\r
+ c->fontselect.shortcut = shortcut;\r
+ return c;\r
+}\r
+\r
+union control *ctrl_tabdelay(struct controlset *s, union control *ctrl)\r
+{\r
+ union control *c = ctrl_new(s, CTRL_TABDELAY, P(NULL), NULL, P(NULL));\r
+ c->tabdelay.ctrl = ctrl;\r
+ return c;\r
+}\r
+\r
+union control *ctrl_text(struct controlset *s, char *text, intorptr helpctx)\r
+{\r
+ union control *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL));\r
+ c->text.label = dupstr(text);\r
+ return c;\r
+}\r
+\r
+union control *ctrl_checkbox(struct controlset *s, char *label, char shortcut,\r
+ intorptr helpctx, handler_fn handler,\r
+ intorptr context)\r
+{\r
+ union control *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context);\r
+ c->checkbox.label = label ? dupstr(label) : NULL;\r
+ c->checkbox.shortcut = shortcut;\r
+ return c;\r
+}\r
+\r
+void ctrl_free(union control *ctrl)\r
+{\r
+ int i;\r
+\r
+ sfree(ctrl->generic.label);\r
+ switch (ctrl->generic.type) {\r
+ case CTRL_RADIO:\r
+ for (i = 0; i < ctrl->radio.nbuttons; i++)\r
+ sfree(ctrl->radio.buttons[i]);\r
+ sfree(ctrl->radio.buttons);\r
+ sfree(ctrl->radio.shortcuts);\r
+ sfree(ctrl->radio.buttondata);\r
+ break;\r
+ case CTRL_COLUMNS:\r
+ sfree(ctrl->columns.percentages);\r
+ break;\r
+ case CTRL_LISTBOX:\r
+ sfree(ctrl->listbox.percentages);\r
+ break;\r
+ case CTRL_FILESELECT:\r
+ sfree(ctrl->fileselect.title);\r
+ break;\r
+ }\r
+ sfree(ctrl);\r
+}\r
+\r
+void dlg_stdradiobutton_handler(union control *ctrl, void *dlg,\r
+ void *data, int event)\r
+{\r
+ int button;\r
+ /*\r
+ * For a standard radio button set, the context parameter gives\r
+ * offsetof(targetfield, Config), and the extra data per button\r
+ * gives the value the target field should take if that button\r
+ * is the one selected.\r
+ */\r
+ if (event == EVENT_REFRESH) {\r
+ for (button = 0; button < ctrl->radio.nbuttons; button++)\r
+ if (*(int *)ATOFFSET(data, ctrl->radio.context.i) ==\r
+ ctrl->radio.buttondata[button].i)\r
+ break;\r
+ /* We expected that `break' to happen, in all circumstances. */\r
+ assert(button < ctrl->radio.nbuttons);\r
+ dlg_radiobutton_set(ctrl, dlg, button);\r
+ } else if (event == EVENT_VALCHANGE) {\r
+ button = dlg_radiobutton_get(ctrl, dlg);\r
+ assert(button >= 0 && button < ctrl->radio.nbuttons);\r
+ *(int *)ATOFFSET(data, ctrl->radio.context.i) =\r
+ ctrl->radio.buttondata[button].i;\r
+ }\r
+}\r
+\r
+void dlg_stdcheckbox_handler(union control *ctrl, void *dlg,\r
+ void *data, int event)\r
+{\r
+ int offset, invert;\r
+\r
+ /*\r
+ * For a standard checkbox, the context parameter gives\r
+ * offsetof(targetfield, Config), optionally ORed with\r
+ * CHECKBOX_INVERT.\r
+ */\r
+ offset = ctrl->checkbox.context.i;\r
+ if (offset & CHECKBOX_INVERT) {\r
+ offset &= ~CHECKBOX_INVERT;\r
+ invert = 1;\r
+ } else\r
+ invert = 0;\r
+\r
+ /*\r
+ * C lacks a logical XOR, so the following code uses the idiom\r
+ * (!a ^ !b) to obtain the logical XOR of a and b. (That is, 1\r
+ * iff exactly one of a and b is nonzero, otherwise 0.)\r
+ */\r
+\r
+ if (event == EVENT_REFRESH) {\r
+ dlg_checkbox_set(ctrl,dlg, (!*(int *)ATOFFSET(data,offset) ^ !invert));\r
+ } else if (event == EVENT_VALCHANGE) {\r
+ *(int *)ATOFFSET(data, offset) = !dlg_checkbox_get(ctrl,dlg) ^ !invert;\r
+ }\r
+}\r
+\r
+void dlg_stdeditbox_handler(union control *ctrl, void *dlg,\r
+ void *data, int event)\r
+{\r
+ /*\r
+ * The standard edit-box handler expects the main `context'\r
+ * field to contain the `offsetof' a field in the structure\r
+ * pointed to by `data'. The secondary `context2' field\r
+ * indicates the type of this field:\r
+ *\r
+ * - if context2 > 0, the field is a char array and context2\r
+ * gives its size.\r
+ * - if context2 == -1, the field is an int and the edit box\r
+ * is numeric.\r
+ * - if context2 < -1, the field is an int and the edit box is\r
+ * _floating_, and (-context2) gives the scale. (E.g. if\r
+ * context2 == -1000, then typing 1.2 into the box will set\r
+ * the field to 1200.)\r
+ */\r
+ int offset = ctrl->editbox.context.i;\r
+ int length = ctrl->editbox.context2.i;\r
+\r
+ if (length > 0) {\r
+ char *field = (char *)ATOFFSET(data, offset);\r
+ if (event == EVENT_REFRESH) {\r
+ dlg_editbox_set(ctrl, dlg, field);\r
+ } else if (event == EVENT_VALCHANGE) {\r
+ dlg_editbox_get(ctrl, dlg, field, length);\r
+ }\r
+ } else if (length < 0) {\r
+ int *field = (int *)ATOFFSET(data, offset);\r
+ char data[80];\r
+ if (event == EVENT_REFRESH) {\r
+ if (length == -1)\r
+ sprintf(data, "%d", *field);\r
+ else\r
+ sprintf(data, "%g", (double)*field / (double)(-length));\r
+ dlg_editbox_set(ctrl, dlg, data);\r
+ } else if (event == EVENT_VALCHANGE) {\r
+ dlg_editbox_get(ctrl, dlg, data, lenof(data));\r
+ if (length == -1)\r
+ *field = atoi(data);\r
+ else\r
+ *field = (int)((-length) * atof(data));\r
+ }\r
+ }\r
+}\r
+\r
+void dlg_stdfilesel_handler(union control *ctrl, void *dlg,\r
+ void *data, int event)\r
+{\r
+ /*\r
+ * The standard file-selector handler expects the `context'\r
+ * field to contain the `offsetof' a Filename field in the\r
+ * structure pointed to by `data'.\r
+ */\r
+ int offset = ctrl->fileselect.context.i;\r
+\r
+ if (event == EVENT_REFRESH) {\r
+ dlg_filesel_set(ctrl, dlg, *(Filename *)ATOFFSET(data, offset));\r
+ } else if (event == EVENT_VALCHANGE) {\r
+ dlg_filesel_get(ctrl, dlg, (Filename *)ATOFFSET(data, offset));\r
+ }\r
+}\r
+\r
+void dlg_stdfontsel_handler(union control *ctrl, void *dlg,\r
+ void *data, int event)\r
+{\r
+ /*\r
+ * The standard file-selector handler expects the `context'\r
+ * field to contain the `offsetof' a FontSpec field in the\r
+ * structure pointed to by `data'.\r
+ */\r
+ int offset = ctrl->fontselect.context.i;\r
+\r
+ if (event == EVENT_REFRESH) {\r
+ dlg_fontsel_set(ctrl, dlg, *(FontSpec *)ATOFFSET(data, offset));\r
+ } else if (event == EVENT_VALCHANGE) {\r
+ dlg_fontsel_get(ctrl, dlg, (FontSpec *)ATOFFSET(data, offset));\r
+ }\r
+}\r